diff --git a/source/matplot/backend/backend_interface.h b/source/matplot/backend/backend_interface.h index f499310f..460ff82b 100644 --- a/source/matplot/backend/backend_interface.h +++ b/source/matplot/backend/backend_interface.h @@ -41,6 +41,7 @@ namespace matplot { class MATPLOT_EXPORTS backend_interface { /// Virtual functions you can override to create any backend public: + virtual ~backend_interface() noexcept = default; /// \brief True if backend is in interactive mode /// One backends might support both interactive and /// non-interactive mode. @@ -213,7 +214,30 @@ namespace matplot { /// This is useful when tracing the gnuplot commands /// and when generating a gnuplot file. virtual void include_comment(const std::string &text); - }; + }; // backend_interface + /// Captures (backend) version information. + struct Version { + int major{}, minor{}, patch{}; + constexpr bool operator==(const Version &o) const { + return major == o.major && minor == o.minor && patch == o.patch; + } + constexpr bool operator!=(const Version &other) const { return !(*this == other); } + constexpr bool operator<(const Version &other) const { + if (major < other.major) + return true; + else if (major == other.major) { + if (minor < other.minor) + return true; + else if (minor == other.minor) + return patch < other.patch; + } + return false; + } + constexpr bool operator>(const Version &other) const { return other < *this; } + constexpr bool operator<=(const Version &other) const { return !(other < *this); } + constexpr bool operator>=(const Version &other) const { return !(other > *this); } + constexpr operator bool() const { return *this != Version{0,0,0}; } + }; // struct Version } // namespace backend } // namespace matplot diff --git a/source/matplot/backend/gnuplot.cpp b/source/matplot/backend/gnuplot.cpp index 674e1ff2..a4bccf7b 100644 --- a/source/matplot/backend/gnuplot.cpp +++ b/source/matplot/backend/gnuplot.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include @@ -317,13 +317,28 @@ namespace matplot::backend { } } + /// returns the next word in text after prefix terminated with white space. + std::string_view word_after(std::string_view text, std::string_view prefix) + { + auto res = text.substr(0,0); + if (auto b = text.find(prefix); b != std::string_view::npos) { + b += prefix.length(); + while (b < text.length() && std::isspace(text[b])) + ++b; // skip white space before word + auto e = b; + while (e < text.length() && !std::isspace(text[e])) + ++e; // scan until white space or end + res = text.substr(b, e-b); + } + return res; + } + std::string gnuplot::default_terminal_type() { static std::string terminal_type; const bool dont_know_term_type = terminal_type.empty(); if (dont_know_term_type) { terminal_type = run_and_get_output("gnuplot -e \"show terminal\" 2>&1"); - terminal_type = std::regex_replace(terminal_type, - std::regex("[^]*terminal type is ([^ ]+)[^]*"), "$1"); + terminal_type = word_after(terminal_type, "terminal type is "); const bool still_dont_know_term_type = terminal_type.empty(); if (still_dont_know_term_type) { terminal_type = "qt"; @@ -334,51 +349,25 @@ namespace matplot::backend { bool gnuplot::terminal_is_available(std::string_view term) { std::string msg = run_and_get_output("gnuplot -e \"set terminal " + - std::string(term.data()) + "\" 2>&1"); + std::string{term} + "\" 2>&1"); return msg.empty(); } - std::tuple gnuplot::gnuplot_version() { - static std::tuple version{0, 0, 0}; - const bool dont_know_gnuplot_version_yet = - version == std::tuple({0, 0, 0}); - if (dont_know_gnuplot_version_yet) { - std::string version_str = - run_and_get_output("gnuplot --version 2>&1"); - std::string version_major = std::regex_replace( - version_str, - std::regex("[^]*gnuplot (\\d+)\\.\\d+ patchlevel \\d+ *"), - "$1"); - std::string version_minor = std::regex_replace( - version_str, - std::regex("[^]*gnuplot \\d+\\.(\\d+) patchlevel \\d+ *"), - "$1"); - std::string version_patch = std::regex_replace( - version_str, - std::regex("[^]*gnuplot \\d+\\.\\d+ patchlevel (\\d+) *"), - "$1"); - try { - std::get<0>(version) = std::stoi(version_major); - } catch (...) { - std::get<0>(version) = 0; - } - try { - std::get<1>(version) = std::stoi(version_minor); - } catch (...) { - std::get<1>(version) = 0; - } - try { - std::get<2>(version) = std::stoi(version_patch); - } catch (...) { - std::get<2>(version) = 0; - } - const bool still_dont_know_gnuplot_version = - version == std::tuple({0, 0, 0}); - if (still_dont_know_gnuplot_version) { - // assume it's 5.2.6 by convention - version = std::tuple({5, 2, 6}); + Version gnuplot::gnuplot_version() { + static auto version = Version{0, 0, 0}; + if (!version) { + const auto version_str = run_and_get_output("gnuplot --version 2>&1"); + const auto major = word_after(version_str, "gnuplot"); + const auto minor = word_after(major, "."); + const auto patch = word_after(version_str, "patchlevel"); + if (!major.empty() && !minor.empty() && !patch.empty()) { + std::from_chars(major.begin(), major.end(), version.major); + std::from_chars(minor.begin(), minor.end(), version.minor); + std::from_chars(patch.begin(), patch.end(), version.patch); } } + if (!version) + version = {5, 2, 6}; // assume by convention return version; } diff --git a/source/matplot/backend/gnuplot.h b/source/matplot/backend/gnuplot.h index dc74abef..18230da5 100644 --- a/source/matplot/backend/gnuplot.h +++ b/source/matplot/backend/gnuplot.h @@ -47,7 +47,7 @@ namespace matplot::backend { /// Identify the default terminal type in the system static std::string default_terminal_type(); static bool terminal_is_available(std::string_view); - static std::tuple gnuplot_version(); + static Version gnuplot_version(); static bool terminal_has_title_option(const std::string &t); static bool terminal_has_size_option(const std::string &t); static bool terminal_has_position_option(const std::string &t); diff --git a/source/matplot/core/axes_type.cpp b/source/matplot/core/axes_type.cpp index d7594a60..70b91c87 100644 --- a/source/matplot/core/axes_type.cpp +++ b/source/matplot/core/axes_type.cpp @@ -426,7 +426,7 @@ namespace matplot { run_command("set polar"); } auto set_or_unset_axis = [this](class axis_type &ax, - std::string axis_name, + const std::string &axis_name, bool minor_ticks = false) { // cb is the only axis we don't unset if tics are empty // r-axis labels should still be handled even if axis is invisible since we use the grid @@ -694,8 +694,7 @@ namespace matplot { // Gnuplot version needs to be 5.2.6+ for keyentry bool ok = true; if (parent_->backend_->consumes_gnuplot_commands()) { - if (backend::gnuplot::gnuplot_version() < - std::tuple{5, 2, 6}) { + if (backend::gnuplot::gnuplot_version() < backend::Version{5, 2, 6}) { ok = false; } } @@ -916,9 +915,8 @@ namespace matplot { static bool msg_shown_once = false; // Gnuplot version needs to be 5.2.6+ for keyentry if (parent_->backend_->consumes_gnuplot_commands()) { - std::tuple v = - backend::gnuplot::gnuplot_version(); - if (v < std::tuple{5, 2, 6}) { + const auto v = backend::gnuplot::gnuplot_version(); + if (v < backend::Version{5, 2, 6}) { if (!msg_shown_once) { std::cerr << "You need gnuplot 5.2.6+ to include legends" @@ -2745,9 +2743,9 @@ namespace matplot { } std::vector - axes_type::fplot(std::vector equations, - std::array x_range, - std::vector line_specs) { + axes_type::fplot(const std::vector &equations, + const std::array &x_range, + const std::vector &line_specs) { axes_silencer temp_silencer_{this}; std::vector res; auto it_line_specs = line_specs.begin(); @@ -2764,9 +2762,9 @@ namespace matplot { } std::vector - axes_type::fplot(std::vector equations, - std::vector x_range, - std::vector line_specs) { + axes_type::fplot(const std::vector& equations, + const std::vector& x_range, + const std::vector& line_specs) { return this->fplot(equations, to_array<2>(x_range), line_specs); } diff --git a/source/matplot/core/axes_type.h b/source/matplot/core/axes_type.h index 227003f1..caedf10e 100644 --- a/source/matplot/core/axes_type.h +++ b/source/matplot/core/axes_type.h @@ -979,15 +979,15 @@ namespace matplot { /// Lambda function line plot - list of functions std::vector - fplot(std::vector equations, - std::array x_range = {-5, 5}, - std::vector line_specs = {}); + fplot(const std::vector &equations, + const std::array &x_range = {-5, 5}, + const std::vector &line_specs = {}); /// Lambda function line plot - list of functions and line specs std::vector - fplot(std::vector equations, - std::vector x_range, - std::vector line_specs = {}); + fplot(const std::vector &equations, + const std::vector &x_range, + const std::vector &line_specs = {}); using implicit_function_type = std::function; diff --git a/source/matplot/core/figure_type.cpp b/source/matplot/core/figure_type.cpp index a4ff0903..aab3810d 100644 --- a/source/matplot/core/figure_type.cpp +++ b/source/matplot/core/figure_type.cpp @@ -634,8 +634,7 @@ namespace matplot { // In gnuplot 5.5 we have the wall function to set the axes color // with a rectangle workaround, which does not work well for 3d. static const auto v = backend::gnuplot::gnuplot_version(); - const bool has_wall_option = - std::get<0>(v) > 5 || (std::get<0>(v) == 5 && std::get<1>(v) >= 5); + const bool has_wall_option = v >= backend::Version{5,5,0}; // So we only plot the default background if it's not 3d or version is // higher than 5.5. Otherwise, gnuplot won't be able to set the axes // colors.