diff --git a/libmamba/include/mamba/specs/version_spec.hpp b/libmamba/include/mamba/specs/version_spec.hpp index e0d8bd325..c37c36ff2 100644 --- a/libmamba/include/mamba/specs/version_spec.hpp +++ b/libmamba/include/mamba/specs/version_spec.hpp @@ -7,6 +7,7 @@ #ifndef MAMBA_SPECS_VERSION_SPEC_HPP #define MAMBA_SPECS_VERSION_SPEC_HPP +#include #include #include #include @@ -128,6 +129,8 @@ namespace mamba::specs static constexpr char left_parenthesis_token = '('; static constexpr char right_parenthesis_token = ')'; + static constexpr std::string_view prefered_free_str = "=*"; + static constexpr std::array all_free_strs = { "", "*", "=*", "==*" }; static constexpr std::string_view starts_with_str = "="; static constexpr std::string_view equal_str = "=="; static constexpr std::string_view not_equal_str = "!="; diff --git a/libmamba/src/specs/version_spec.cpp b/libmamba/src/specs/version_spec.cpp index addd8d5b5..85ebd99e4 100644 --- a/libmamba/src/specs/version_spec.cpp +++ b/libmamba/src/specs/version_spec.cpp @@ -290,16 +290,17 @@ namespace mamba::specs namespace { - auto is_char(std::string_view str, char c) -> bool + template + constexpr auto equal_any(const Val& val, const Range& range) -> bool { - return (str.size() == 1) && (str.front() == c); + return std::find(range.cbegin(), range.cend(), val) != range.cend(); } auto parse_op_and_version(std::string_view str) -> VersionPredicate { str = util::strip(str); // WARNING order is important since some operator are prefix of others. - if (str.empty() || is_char(str, VersionSpec::glob_suffix_token)) + if (str.empty() || equal_any(str, VersionSpec::all_free_strs)) { return VersionPredicate::make_free(); } @@ -331,7 +332,7 @@ namespace mamba::specs { auto ver = Version::parse(str.substr(VersionSpec::compatible_str.size())); // in ``~=1.1`` level is assumed to be 1, in ``~=1.1.1`` level 2, etc. - static constexpr std::size_t one = std::size_t(1); // MSVC + static constexpr auto one = std::size_t(1); // MSVC const std::size_t level = std::max(ver.version().size(), one) - one; return VersionPredicate::make_compatible_with(std::move(ver), level); } @@ -381,7 +382,7 @@ namespace mamba::specs if (util::ends_with(str, VersionSpec::glob_suffix_token)) { // either ".*" or "*" - static constexpr std::size_t one = std::size_t(1); // MSVC + static constexpr auto one = std::size_t(1); // MSVC const std::size_t len = str.size() - std::max(glob_len, one); return VersionPredicate::make_starts_with(Version::parse(str.substr(0, len))); } @@ -409,6 +410,15 @@ namespace mamba::specs auto parser = util::InfixParser(); str = util::lstrip(str); + + // Explicit short-circuiting for "free" spec + // This case would be handled anyway but we can avoid allocating a tree in this + // likely case. + if (str.empty() || equal_any(str, VersionSpec::all_free_strs)) + { + return {}; + } + while (!str.empty()) { if (str.front() == VersionSpec::and_token) @@ -468,11 +478,16 @@ fmt::formatter::format( format_context& ctx ) -> decltype(ctx.out()) { + using VersionSpec = typename mamba::specs::VersionSpec; + auto out = ctx.out(); + if (spec.m_tree.empty()) + { + return fmt::format_to(out, "{}", VersionSpec::prefered_free_str); + } spec.m_tree.infix_for_each( [&](const auto& token) { - using VersionSpec = typename mamba::specs::VersionSpec; using tree_type = typename VersionSpec::tree_type; using Token = std::decay_t; if constexpr (std::is_same_v) diff --git a/libmamba/tests/src/specs/test_version_spec.cpp b/libmamba/tests/src/specs/test_version_spec.cpp index 5e536a35c..a68a7abd2 100644 --- a/libmamba/tests/src/specs/test_version_spec.cpp +++ b/libmamba/tests/src/specs/test_version_spec.cpp @@ -122,6 +122,7 @@ TEST_SUITE("specs::version_spec") { auto spec = VersionSpec(); CHECK(spec.contains(Version())); + CHECK_EQ(spec.str(), "=*"); } SUBCASE("<2.0|(>2.3,<=2.8.0)") @@ -168,6 +169,9 @@ TEST_SUITE("specs::version_spec") CHECK(""_vs.contains("1.6"_v)); CHECK(""_vs.contains("0.6+0.7"_v)); + CHECK("*"_vs.contains("1.4"_v)); + CHECK("=*"_vs.contains("1.4"_v)); + CHECK("1.7"_vs.contains("1.7"_v)); CHECK("1.7"_vs.contains("1.7.0.0"_v)); CHECK_FALSE("1.7"_vs.contains("1.6"_v)); diff --git a/libmambapy/src/libmambapy/bindings/specs.cpp b/libmambapy/src/libmambapy/bindings/specs.cpp index 90de742b5..ae9640ac0 100644 --- a/libmambapy/src/libmambapy/bindings/specs.cpp +++ b/libmambapy/src/libmambapy/bindings/specs.cpp @@ -550,6 +550,8 @@ namespace mambapy .def_readonly_static("or_token", &VersionSpec::or_token) .def_readonly_static("left_parenthesis_token", &VersionSpec::left_parenthesis_token) .def_readonly_static("right_parenthesis_token", &VersionSpec::right_parenthesis_token) + .def_readonly_static("prefered_free_str", &VersionSpec::prefered_free_str) + .def_readonly_static("all_free_strs", &VersionSpec::all_free_strs) .def_readonly_static("starts_with_str", &VersionSpec::starts_with_str) .def_readonly_static("equal_str", &VersionSpec::equal_str) .def_readonly_static("not_equal_str", &VersionSpec::not_equal_str) diff --git a/libmambapy/tests/test_specs.py b/libmambapy/tests/test_specs.py index 05f76eb66..ab6e80b73 100644 --- a/libmambapy/tests/test_specs.py +++ b/libmambapy/tests/test_specs.py @@ -635,6 +635,8 @@ def test_VersionSpec(): assert isinstance(VersionSpec.or_token, str) assert isinstance(VersionSpec.left_parenthesis_token, str) assert isinstance(VersionSpec.right_parenthesis_token, str) + assert isinstance(VersionSpec.prefered_free_str, str) + assert isinstance(VersionSpec.all_free_strs, list) assert isinstance(VersionSpec.starts_with_str, str) assert isinstance(VersionSpec.equal_str, str) assert isinstance(VersionSpec.not_equal_str, str)