Fix VersionSpec free ranges (#3088)

This commit is contained in:
Antoine Prouvost 2023-12-28 13:33:48 +01:00 committed by GitHub
parent 4ee7531fd6
commit 3f6484c23c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 32 additions and 6 deletions

View File

@ -7,6 +7,7 @@
#ifndef MAMBA_SPECS_VERSION_SPEC_HPP
#define MAMBA_SPECS_VERSION_SPEC_HPP
#include <array>
#include <functional>
#include <string_view>
#include <variant>
@ -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<std::string_view, 4> 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 = "!=";

View File

@ -290,16 +290,17 @@ namespace mamba::specs
namespace
{
auto is_char(std::string_view str, char c) -> bool
template <typename Val, typename Range>
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<VersionPredicate, util::BoolOperator>();
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<mamba::specs::VersionSpec>::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<decltype(token)>;
if constexpr (std::is_same_v<Token, tree_type::LeftParenthesis>)

View File

@ -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));

View File

@ -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)

View File

@ -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)