mirror of https://github.com/mamba-org/mamba.git
Add VersionSpec::str (#3081)
* Add flat_bool_expr_tree:infix_for_each * Add level to Version repr * Add VersionSpec::str
This commit is contained in:
parent
2d85759c13
commit
152857f059
|
@ -7,6 +7,7 @@
|
|||
#ifndef MAMBA_SPECS_VERSION_HPP
|
||||
#define MAMBA_SPECS_VERSION_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
@ -110,8 +111,24 @@ namespace mamba::specs
|
|||
[[nodiscard]] auto version() const noexcept -> const CommonVersion&;
|
||||
[[nodiscard]] auto local() const noexcept -> const CommonVersion&;
|
||||
|
||||
/**
|
||||
* A string representation of the version.
|
||||
*
|
||||
* May not always be the same as the parsed string (due to reconstruction) but reparsing
|
||||
* this string will give the same version.
|
||||
* ``v == Version::parse(v.str())``.
|
||||
*/
|
||||
[[nodiscard]] auto str() const -> std::string;
|
||||
|
||||
/**
|
||||
* A string truncated of extended representation of the version.
|
||||
*
|
||||
* Represent the string with the desired number of parts.
|
||||
* If the actual number of parts is larger, then the string is truncated.
|
||||
* If the actual number of parts is smalle, then the string is expanded with zeros.
|
||||
*/
|
||||
[[nodiscard]] auto str(std::size_t level) const -> std::string;
|
||||
|
||||
[[nodiscard]] auto operator==(const Version& other) const -> bool;
|
||||
[[nodiscard]] auto operator!=(const Version& other) const -> bool;
|
||||
[[nodiscard]] auto operator<(const Version& other) const -> bool;
|
||||
|
@ -156,73 +173,20 @@ namespace mamba::specs
|
|||
template <>
|
||||
struct fmt::formatter<mamba::specs::VersionPartAtom>
|
||||
{
|
||||
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin())
|
||||
{
|
||||
// make sure that range is empty
|
||||
if (ctx.begin() != ctx.end() && *ctx.begin() != '}')
|
||||
{
|
||||
throw fmt::format_error("Invalid format");
|
||||
}
|
||||
return ctx.begin();
|
||||
}
|
||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin());
|
||||
|
||||
template <class FormatContext>
|
||||
auto format(const ::mamba::specs::VersionPartAtom atom, FormatContext& ctx)
|
||||
{
|
||||
return fmt::format_to(ctx.out(), "{}{}", atom.numeral(), atom.literal());
|
||||
}
|
||||
auto format(const ::mamba::specs::VersionPartAtom atom, format_context& ctx)
|
||||
-> decltype(ctx.out());
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<mamba::specs::Version>
|
||||
{
|
||||
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin())
|
||||
{
|
||||
// make sure that range is empty
|
||||
if (ctx.begin() != ctx.end() && *ctx.begin() != '}')
|
||||
{
|
||||
throw fmt::format_error("Invalid format");
|
||||
}
|
||||
return ctx.begin();
|
||||
}
|
||||
std::optional<std::size_t> m_level;
|
||||
|
||||
template <class FormatContext>
|
||||
auto format(const ::mamba::specs::Version v, FormatContext& ctx)
|
||||
{
|
||||
auto out = ctx.out();
|
||||
if (v.epoch() != 0)
|
||||
{
|
||||
out = fmt::format_to(ctx.out(), "{}!", v.epoch());
|
||||
}
|
||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin());
|
||||
|
||||
auto format_version_to = [](auto l_out, const auto& version)
|
||||
{
|
||||
bool first = true;
|
||||
for (const auto& part : version)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
l_out = fmt::format_to(l_out, ".");
|
||||
}
|
||||
for (const auto& atom : part)
|
||||
{
|
||||
l_out = fmt::format_to(l_out, "{}", atom);
|
||||
}
|
||||
}
|
||||
return l_out;
|
||||
};
|
||||
out = format_version_to(out, v.version());
|
||||
if (!v.local().empty())
|
||||
{
|
||||
out = fmt::format_to(out, "+");
|
||||
out = format_version_to(out, v.local());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
auto format(const ::mamba::specs::Version v, format_context& ctx) -> decltype(ctx.out());
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -11,11 +11,16 @@
|
|||
#include <string_view>
|
||||
#include <variant>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "mamba/specs/version.hpp"
|
||||
#include "mamba/util/flat_bool_expr_tree.hpp"
|
||||
|
||||
namespace mamba::specs
|
||||
{
|
||||
/**
|
||||
* A stateful unary boolean function on the Version space.
|
||||
*/
|
||||
class VersionPredicate
|
||||
{
|
||||
public:
|
||||
|
@ -35,8 +40,13 @@ namespace mamba::specs
|
|||
/** Construct an free interval. */
|
||||
VersionPredicate() = default;
|
||||
|
||||
/**
|
||||
* True if the predicate contains the given version.
|
||||
*/
|
||||
[[nodiscard]] auto contains(const Version& point) const -> bool;
|
||||
|
||||
[[nodiscard]] auto str() const -> std::string;
|
||||
|
||||
private:
|
||||
|
||||
struct free_interval
|
||||
|
@ -90,11 +100,23 @@ namespace mamba::specs
|
|||
friend auto operator==(not_starts_with, not_starts_with) -> bool;
|
||||
friend auto operator==(compatible_with, compatible_with) -> bool;
|
||||
friend auto operator==(const VersionPredicate& lhs, const VersionPredicate& rhs) -> bool;
|
||||
friend class ::fmt::formatter<VersionPredicate>;
|
||||
};
|
||||
|
||||
auto operator==(const VersionPredicate& lhs, const VersionPredicate& rhs) -> bool;
|
||||
auto operator!=(const VersionPredicate& lhs, const VersionPredicate& rhs) -> bool;
|
||||
|
||||
/**
|
||||
* Represent a set of versions.
|
||||
*
|
||||
* Internally, a VersionSpec is a binary expression tree of union (or) or intersections (and)
|
||||
* of the sets represented by VersionPredicate.
|
||||
*
|
||||
* The VersionSpec can itself be considered a complex predicate on the space of Version.
|
||||
*
|
||||
* Due to the complex nature of the expression system (comutativity, associativity, etc.), there
|
||||
* is no easy way to say if two VersionSpecs are equal.
|
||||
*/
|
||||
class VersionSpec
|
||||
{
|
||||
public:
|
||||
|
@ -123,12 +145,24 @@ namespace mamba::specs
|
|||
VersionSpec() = default;
|
||||
explicit VersionSpec(tree_type&& tree) noexcept;
|
||||
|
||||
/**
|
||||
* A string representation of the version spec.
|
||||
*
|
||||
* May not always be the same as the parsed string (due to reconstruction) but reparsing
|
||||
* this string will give the same version spec.
|
||||
*/
|
||||
[[nodiscard]] auto str() const -> std::string;
|
||||
|
||||
/**
|
||||
* True if the set described by the VersionSpec contains the given version.
|
||||
*/
|
||||
[[nodiscard]] auto contains(const Version& point) const -> bool;
|
||||
|
||||
private:
|
||||
|
||||
tree_type m_tree;
|
||||
|
||||
friend class ::fmt::formatter<VersionSpec>;
|
||||
};
|
||||
|
||||
namespace version_spec_literals
|
||||
|
@ -137,4 +171,20 @@ namespace mamba::specs
|
|||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<mamba::specs::VersionPredicate>
|
||||
{
|
||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin());
|
||||
|
||||
auto format(const ::mamba::specs::VersionPredicate& pred, format_context& ctx)
|
||||
-> decltype(ctx.out());
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<mamba::specs::VersionSpec>
|
||||
{
|
||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin());
|
||||
|
||||
auto format(const ::mamba::specs::VersionSpec& spec, format_context& ctx) -> decltype(ctx.out());
|
||||
};
|
||||
#endif
|
||||
|
|
|
@ -90,6 +90,9 @@ namespace mamba::util
|
|||
[[nodiscard]] auto right(idx_type idx) const -> idx_type;
|
||||
[[nodiscard]] auto root() const -> idx_type;
|
||||
|
||||
template <typename Visitor>
|
||||
void dfs_raw(Visitor&& visitor, idx_type start) const;
|
||||
|
||||
private:
|
||||
|
||||
node_list m_nodes;
|
||||
|
@ -245,5 +248,28 @@ namespace mamba::util
|
|||
{
|
||||
return add_branch_impl(std::move(branch), left_child, right_child);
|
||||
}
|
||||
|
||||
template <typename B, typename L>
|
||||
template <typename Visitor>
|
||||
void flat_binary_tree<B, L>::dfs_raw(Visitor&& visitor, idx_type start_idx) const
|
||||
{
|
||||
if (is_leaf(start_idx))
|
||||
{
|
||||
visitor.on_leaf(*this, start_idx);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto left_idx = left(start_idx);
|
||||
const auto right_idx = right(start_idx);
|
||||
|
||||
visitor.on_branch_left_before(*this, start_idx, left_idx);
|
||||
dfs_raw(visitor, left_idx);
|
||||
|
||||
visitor.on_branch_infix(*this, start_idx, left_idx, right_idx);
|
||||
|
||||
dfs_raw(visitor, right_idx);
|
||||
visitor.on_branch_right_after(*this, start_idx, right_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -9,9 +9,7 @@
|
|||
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
@ -150,6 +148,14 @@ namespace mamba::util
|
|||
using tree_type = flat_binary_tree<operator_type, variable_type>;
|
||||
using size_type = typename tree_type::size_type;
|
||||
|
||||
struct LeftParenthesis
|
||||
{
|
||||
};
|
||||
|
||||
struct RightParenthesis
|
||||
{
|
||||
};
|
||||
|
||||
flat_bool_expr_tree() = default;
|
||||
flat_bool_expr_tree(const flat_bool_expr_tree&) = default;
|
||||
flat_bool_expr_tree(flat_bool_expr_tree&&) = default;
|
||||
|
@ -169,6 +175,9 @@ namespace mamba::util
|
|||
[[nodiscard]] auto evaluate(UnaryFunc&& var_evaluator = {}, bool empty_val = true) const
|
||||
-> bool;
|
||||
|
||||
template <typename UnaryFunc>
|
||||
void infix_for_each(UnaryFunc&& func) const;
|
||||
|
||||
private:
|
||||
|
||||
using idx_type = typename tree_type::idx_type;
|
||||
|
@ -179,6 +188,42 @@ namespace mamba::util
|
|||
tree_type m_tree = {};
|
||||
};
|
||||
|
||||
template <typename V>
|
||||
constexpr auto operator==(
|
||||
typename flat_bool_expr_tree<V>::LeftParenthesis,
|
||||
typename flat_bool_expr_tree<V>::LeftParenthesis
|
||||
) -> bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
constexpr auto operator!=(
|
||||
typename flat_bool_expr_tree<V>::LeftParenthesis,
|
||||
typename flat_bool_expr_tree<V>::LeftParenthesis
|
||||
) -> bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
constexpr auto operator==(
|
||||
typename flat_bool_expr_tree<V>::RightParenthesis,
|
||||
typename flat_bool_expr_tree<V>::RightParenthesis
|
||||
) -> bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
constexpr auto operator!=(
|
||||
typename flat_bool_expr_tree<V>::RightParenthesis,
|
||||
typename flat_bool_expr_tree<V>::RightParenthesis
|
||||
) -> bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/*************************************
|
||||
* Implementation of PostfixParser *
|
||||
*************************************/
|
||||
|
@ -551,5 +596,54 @@ namespace mamba::util
|
|||
|| evaluate_impl(var_eval, m_tree.right(idx));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
template <typename UnaryFunc>
|
||||
void flat_bool_expr_tree<V>::infix_for_each(UnaryFunc&& func) const
|
||||
{
|
||||
struct TreeVisitor
|
||||
{
|
||||
using idx_type = typename tree_type::idx_type;
|
||||
|
||||
void on_leaf(const tree_type& tree, idx_type idx)
|
||||
{
|
||||
m_func(tree.leaf(idx));
|
||||
}
|
||||
|
||||
void on_branch_left_before(const tree_type& tree, idx_type, idx_type left_idx)
|
||||
{
|
||||
if (!tree.is_leaf(left_idx))
|
||||
{
|
||||
m_func(LeftParenthesis{});
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
on_branch_infix(const tree_type& tree, idx_type branch_idx, idx_type left_idx, idx_type right_idx)
|
||||
{
|
||||
if (!tree.is_leaf(left_idx))
|
||||
{
|
||||
m_func(RightParenthesis{});
|
||||
}
|
||||
m_func(tree.branch(branch_idx));
|
||||
if (!tree.is_leaf(right_idx))
|
||||
{
|
||||
m_func(LeftParenthesis{});
|
||||
}
|
||||
}
|
||||
|
||||
void on_branch_right_after(const tree_type& tree, idx_type, idx_type right_idx)
|
||||
{
|
||||
if (!tree.is_leaf(right_idx))
|
||||
{
|
||||
m_func(RightParenthesis{});
|
||||
}
|
||||
}
|
||||
|
||||
UnaryFunc m_func;
|
||||
} tree_visitor{ std::forward<UnaryFunc>(func) };
|
||||
|
||||
m_tree.dfs_raw(tree_visitor, m_tree.root());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -174,6 +174,31 @@ namespace mamba::specs
|
|||
{
|
||||
return compare_three_way(*this, other) != strong_ordering::less;
|
||||
}
|
||||
}
|
||||
|
||||
auto
|
||||
fmt::formatter<mamba::specs::VersionPartAtom>::parse(format_parse_context& ctx)
|
||||
-> decltype(ctx.begin())
|
||||
{
|
||||
// make sure that range is empty
|
||||
if (ctx.begin() != ctx.end() && *ctx.begin() != '}')
|
||||
{
|
||||
throw fmt::format_error("Invalid format");
|
||||
}
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
auto
|
||||
fmt::formatter<mamba::specs::VersionPartAtom>::format(
|
||||
const ::mamba::specs::VersionPartAtom atom,
|
||||
format_context& ctx
|
||||
) -> decltype(ctx.out())
|
||||
{
|
||||
return fmt::format_to(ctx.out(), "{}{}", atom.numeral(), atom.literal());
|
||||
}
|
||||
|
||||
namespace mamba::specs
|
||||
{
|
||||
|
||||
/*******************************
|
||||
* Implementation of Version *
|
||||
|
@ -206,6 +231,15 @@ namespace mamba::specs
|
|||
return fmt::format("{}", *this);
|
||||
}
|
||||
|
||||
auto Version::str(std::size_t level) const -> std::string
|
||||
{
|
||||
// We should be able to do, as it works with numbers but it is not clear how this works
|
||||
// with the cusotm parser
|
||||
// return fmt::format("{:{}}", *this, level);
|
||||
auto fmt = fmt::format("{{:{}}}", level);
|
||||
return fmt::format(fmt, *this);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
/**
|
||||
|
@ -713,3 +747,64 @@ namespace mamba::specs
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto
|
||||
fmt::formatter<mamba::specs::Version>::parse(format_parse_context& ctx) -> decltype(ctx.begin())
|
||||
{
|
||||
// make sure that range is not empty
|
||||
if (ctx.begin() == ctx.end() || *ctx.begin() == '}')
|
||||
{
|
||||
return ctx.begin();
|
||||
}
|
||||
std::size_t val = 0;
|
||||
auto [ptr, ec] = std::from_chars(ctx.begin(), ctx.end(), val);
|
||||
if (ec != std::errc())
|
||||
{
|
||||
throw fmt::format_error("Invalid format" + std::string(ctx.begin(), ctx.end()));
|
||||
}
|
||||
m_level = val;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
auto
|
||||
fmt::formatter<mamba::specs::Version>::format(const ::mamba::specs::Version v, format_context& ctx)
|
||||
-> decltype(ctx.out())
|
||||
{
|
||||
auto out = ctx.out();
|
||||
if (v.epoch() != 0)
|
||||
{
|
||||
out = fmt::format_to(ctx.out(), "{}!", v.epoch());
|
||||
}
|
||||
|
||||
|
||||
auto format_version_to = [this](auto l_out, const auto& version)
|
||||
{
|
||||
auto const n_levels = m_level.value_or(version.size());
|
||||
for (std::size_t i = 0; i < n_levels; ++i)
|
||||
{
|
||||
if (i != 0)
|
||||
{
|
||||
l_out = fmt::format_to(l_out, ".");
|
||||
}
|
||||
if (i < version.size())
|
||||
{
|
||||
for (const auto& atom : version[i])
|
||||
{
|
||||
l_out = fmt::format_to(l_out, "{}", atom);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
l_out = fmt::format_to(l_out, "0");
|
||||
}
|
||||
}
|
||||
return l_out;
|
||||
};
|
||||
out = format_version_to(out, v.version());
|
||||
if (!v.local().empty())
|
||||
{
|
||||
out = fmt::format_to(out, "+");
|
||||
out = format_version_to(out, v.local());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
|
@ -156,6 +155,11 @@ namespace mamba::specs
|
|||
return VersionPredicate(std::move(ver), compatible_with{ level });
|
||||
}
|
||||
|
||||
auto VersionPredicate::str() const -> std::string
|
||||
{
|
||||
return fmt::format("{}", *this);
|
||||
}
|
||||
|
||||
VersionPredicate::VersionPredicate(Version ver, BinaryOperator op)
|
||||
: m_version(std::move(ver))
|
||||
, m_operator(std::move(op))
|
||||
|
@ -171,6 +175,99 @@ namespace mamba::specs
|
|||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
}
|
||||
|
||||
auto
|
||||
fmt::formatter<mamba::specs::VersionPredicate>::parse(format_parse_context& ctx)
|
||||
-> decltype(ctx.begin())
|
||||
{
|
||||
// make sure that range is empty
|
||||
if (ctx.begin() != ctx.end() && *ctx.begin() != '}')
|
||||
{
|
||||
throw fmt::format_error("Invalid format");
|
||||
}
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
auto
|
||||
fmt::formatter<mamba::specs::VersionPredicate>::format(
|
||||
const ::mamba::specs::VersionPredicate& pred,
|
||||
format_context& ctx
|
||||
) -> decltype(ctx.out())
|
||||
{
|
||||
using VersionPredicate = typename mamba::specs::VersionPredicate;
|
||||
using VersionSpec = typename mamba::specs::VersionSpec;
|
||||
using Version = typename mamba::specs::Version;
|
||||
|
||||
auto out = ctx.out();
|
||||
std::visit(
|
||||
[&](const auto& op)
|
||||
{
|
||||
using Op = std::decay_t<decltype(op)>;
|
||||
if constexpr (std::is_same_v<Op, VersionPredicate::free_interval>)
|
||||
{
|
||||
out = fmt::format_to(
|
||||
out,
|
||||
"{}{}",
|
||||
VersionSpec::starts_with_str,
|
||||
VersionSpec::glob_suffix_token
|
||||
);
|
||||
}
|
||||
if constexpr (std::is_same_v<Op, std::equal_to<Version>>)
|
||||
{
|
||||
out = fmt::format_to(out, "{}{}", VersionSpec::equal_str, pred.m_version);
|
||||
}
|
||||
if constexpr (std::is_same_v<Op, std::not_equal_to<Version>>)
|
||||
{
|
||||
out = fmt::format_to(out, "{}{}", VersionSpec::not_equal_str, pred.m_version);
|
||||
}
|
||||
if constexpr (std::is_same_v<Op, std::greater<Version>>)
|
||||
{
|
||||
out = fmt::format_to(out, "{}{}", VersionSpec::greater_str, pred.m_version);
|
||||
}
|
||||
if constexpr (std::is_same_v<Op, std::greater_equal<Version>>)
|
||||
{
|
||||
out = fmt::format_to(out, "{}{}", VersionSpec::greater_equal_str, pred.m_version);
|
||||
}
|
||||
if constexpr (std::is_same_v<Op, std::less<Version>>)
|
||||
{
|
||||
out = fmt::format_to(out, "{}{}", VersionSpec::less_str, pred.m_version);
|
||||
}
|
||||
if constexpr (std::is_same_v<Op, std::less_equal<Version>>)
|
||||
{
|
||||
out = fmt::format_to(out, "{}{}", VersionSpec::less_equal_str, pred.m_version);
|
||||
}
|
||||
if constexpr (std::is_same_v<Op, VersionPredicate::starts_with>)
|
||||
{
|
||||
out = fmt::format_to(out, "{}{}", VersionSpec::starts_with_str, pred.m_version);
|
||||
}
|
||||
if constexpr (std::is_same_v<Op, VersionPredicate::not_starts_with>)
|
||||
{
|
||||
out = fmt::format_to(
|
||||
out,
|
||||
"{}{}{}",
|
||||
VersionSpec::not_equal_str,
|
||||
pred.m_version,
|
||||
VersionSpec::glob_suffix_str
|
||||
);
|
||||
}
|
||||
if constexpr (std::is_same_v<Op, VersionPredicate::compatible_with>)
|
||||
{
|
||||
out = fmt::format_to(
|
||||
out,
|
||||
"{}{}",
|
||||
VersionSpec::compatible_str,
|
||||
pred.m_version.str(op.level)
|
||||
);
|
||||
}
|
||||
},
|
||||
pred.m_operator
|
||||
);
|
||||
return out;
|
||||
}
|
||||
|
||||
namespace mamba::specs
|
||||
{
|
||||
|
||||
/********************************
|
||||
* VersionSpec Implementation *
|
||||
|
@ -186,6 +283,11 @@ namespace mamba::specs
|
|||
return m_tree.evaluate([&point](const auto& node) { return node.contains(point); });
|
||||
}
|
||||
|
||||
auto VersionSpec::str() const -> std::string
|
||||
{
|
||||
return fmt::format("{}", *this);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
auto is_char(std::string_view str, char c) -> bool
|
||||
|
@ -229,15 +331,15 @@ 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 std::size_t constexpr one = std::size_t(1); // MSVC
|
||||
static constexpr std::size_t 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);
|
||||
}
|
||||
const bool has_glob_suffix = util::ends_with(str, VersionSpec::glob_suffix_str);
|
||||
std::size_t const glob_len = has_glob_suffix * VersionSpec::glob_suffix_str.size();
|
||||
const std::size_t glob_len = has_glob_suffix * VersionSpec::glob_suffix_str.size();
|
||||
if (util::starts_with(str, VersionSpec::equal_str))
|
||||
{
|
||||
std::size_t const start = VersionSpec::equal_str.size();
|
||||
const std::size_t start = VersionSpec::equal_str.size();
|
||||
// Glob suffix changes meaning for ==1.3.*
|
||||
if (has_glob_suffix)
|
||||
{
|
||||
|
@ -252,7 +354,7 @@ namespace mamba::specs
|
|||
}
|
||||
if (util::starts_with(str, VersionSpec::not_equal_str))
|
||||
{
|
||||
std::size_t const start = VersionSpec::not_equal_str.size();
|
||||
const std::size_t start = VersionSpec::not_equal_str.size();
|
||||
// Glob suffix changes meaning for !=1.3.*
|
||||
if (has_glob_suffix)
|
||||
{
|
||||
|
@ -267,7 +369,7 @@ namespace mamba::specs
|
|||
}
|
||||
if (util::starts_with(str, VersionSpec::starts_with_str))
|
||||
{
|
||||
std::size_t const start = VersionSpec::starts_with_str.size();
|
||||
const std::size_t start = VersionSpec::starts_with_str.size();
|
||||
// Glob suffix does not change meaning for =1.3.*
|
||||
return VersionPredicate::make_starts_with(
|
||||
Version::parse(str.substr(start, str.size() - glob_len - start))
|
||||
|
@ -279,8 +381,8 @@ namespace mamba::specs
|
|||
if (util::ends_with(str, VersionSpec::glob_suffix_token))
|
||||
{
|
||||
// either ".*" or "*"
|
||||
static std::size_t constexpr one = std::size_t(1); // MSVC
|
||||
std::size_t const len = str.size() - std::max(glob_len, one);
|
||||
static constexpr std::size_t 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)));
|
||||
}
|
||||
else
|
||||
|
@ -348,3 +450,55 @@ namespace mamba::specs
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto
|
||||
fmt::formatter<mamba::specs::VersionSpec>::parse(format_parse_context& ctx) -> decltype(ctx.begin())
|
||||
{
|
||||
// make sure that range is empty
|
||||
if (ctx.begin() != ctx.end() && *ctx.begin() != '}')
|
||||
{
|
||||
throw fmt::format_error("Invalid format");
|
||||
}
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
auto
|
||||
fmt::formatter<mamba::specs::VersionSpec>::format(
|
||||
const ::mamba::specs::VersionSpec& spec,
|
||||
format_context& ctx
|
||||
) -> decltype(ctx.out())
|
||||
{
|
||||
auto out = ctx.out();
|
||||
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>)
|
||||
{
|
||||
out = fmt::format_to(out, "{}", VersionSpec::left_parenthesis_token);
|
||||
}
|
||||
if constexpr (std::is_same_v<Token, tree_type::RightParenthesis>)
|
||||
{
|
||||
out = fmt::format_to(out, "{}", VersionSpec::right_parenthesis_token);
|
||||
}
|
||||
if constexpr (std::is_same_v<Token, tree_type::operator_type>)
|
||||
{
|
||||
if (token == tree_type::operator_type::logical_or)
|
||||
{
|
||||
out = fmt::format_to(out, "{}", VersionSpec::or_token);
|
||||
}
|
||||
else
|
||||
{
|
||||
out = fmt::format_to(out, "{}", VersionSpec::and_token);
|
||||
}
|
||||
}
|
||||
if constexpr (std::is_same_v<Token, tree_type::variable_type>)
|
||||
{
|
||||
out = fmt::format_to(out, "{}", token);
|
||||
}
|
||||
}
|
||||
);
|
||||
return out;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
@ -306,20 +305,40 @@ TEST_SUITE("specs::version")
|
|||
|
||||
TEST_CASE("version_format")
|
||||
{
|
||||
// clang-format off
|
||||
CHECK_EQ(
|
||||
Version(0, {{{11, "a"}, {0, "post"}}, {{3}}, {{4, "dev"}}}).str(),
|
||||
"11a0post.3.4dev"
|
||||
SUBCASE("11a0post.3.4dev")
|
||||
{
|
||||
auto v = Version(0, { { { 11, "a" }, { 0, "post" } }, { { 3 } }, { { 4, "dev" } } });
|
||||
CHECK_EQ(v.str(), "11a0post.3.4dev");
|
||||
CHECK_EQ(v.str(1), "11a0post");
|
||||
CHECK_EQ(v.str(2), "11a0post.3");
|
||||
CHECK_EQ(v.str(3), "11a0post.3.4dev");
|
||||
CHECK_EQ(v.str(4), "11a0post.3.4dev.0");
|
||||
CHECK_EQ(v.str(5), "11a0post.3.4dev.0.0");
|
||||
}
|
||||
|
||||
SUBCASE("1!11a0.3.4dev")
|
||||
{
|
||||
auto v = Version(1, { { { 11, "a" }, { 0 } }, { { 3 } }, { { 4, "dev" } } });
|
||||
CHECK_EQ(v.str(), "1!11a0.3.4dev");
|
||||
CHECK_EQ(v.str(1), "1!11a0");
|
||||
CHECK_EQ(v.str(2), "1!11a0.3");
|
||||
CHECK_EQ(v.str(3), "1!11a0.3.4dev");
|
||||
CHECK_EQ(v.str(4), "1!11a0.3.4dev.0");
|
||||
}
|
||||
|
||||
SUBCASE("1!11a0.3.4dev+1.2")
|
||||
{
|
||||
auto v = Version(
|
||||
1,
|
||||
{ { { 11, "a" }, { 0 } }, { { 3 } }, { { 4, "dev" } } },
|
||||
{ { { 1 } }, { { 2 } } }
|
||||
);
|
||||
CHECK_EQ(
|
||||
Version(1, {{{11, "a"}, {0}}, {{3}}, {{4, "dev"}}}).str(),
|
||||
"1!11a0.3.4dev"
|
||||
);
|
||||
CHECK_EQ(
|
||||
Version(1, {{{11, "a"}, {0}}, {{3}}, {{4, "dev"}}}, {{{1}}, {{2}}}).str(),
|
||||
"1!11a0.3.4dev+1.2"
|
||||
);
|
||||
// clang-format on
|
||||
CHECK_EQ(v.str(), "1!11a0.3.4dev+1.2");
|
||||
CHECK_EQ(v.str(1), "1!11a0+1");
|
||||
CHECK_EQ(v.str(2), "1!11a0.3+1.2");
|
||||
CHECK_EQ(v.str(3), "1!11a0.3.4dev+1.2.0");
|
||||
CHECK_EQ(v.str(4), "1!11a0.3.4dev.0+1.2.0.0");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -29,42 +29,49 @@ TEST_SUITE("specs::version_spec")
|
|||
CHECK(free.contains(v2));
|
||||
CHECK(free.contains(v3));
|
||||
CHECK(free.contains(v4));
|
||||
CHECK_EQ(free.str(), "=*");
|
||||
|
||||
const auto eq = VersionPredicate::make_equal_to(v2);
|
||||
CHECK_FALSE(eq.contains(v1));
|
||||
CHECK(eq.contains(v2));
|
||||
CHECK_FALSE(eq.contains(v3));
|
||||
CHECK_FALSE(eq.contains(v4));
|
||||
CHECK_EQ(eq.str(), "==2.0");
|
||||
|
||||
const auto ne = VersionPredicate::make_not_equal_to(v2);
|
||||
CHECK(ne.contains(v1));
|
||||
CHECK_FALSE(ne.contains(v2));
|
||||
CHECK(ne.contains(v3));
|
||||
CHECK(ne.contains(v4));
|
||||
CHECK_EQ(ne.str(), "!=2.0");
|
||||
|
||||
const auto gt = VersionPredicate::make_greater(v2);
|
||||
CHECK_FALSE(gt.contains(v1));
|
||||
CHECK_FALSE(gt.contains(v2));
|
||||
CHECK(gt.contains(v3));
|
||||
CHECK(gt.contains(v4));
|
||||
CHECK_EQ(gt.str(), ">2.0");
|
||||
|
||||
const auto ge = VersionPredicate::make_greater_equal(v2);
|
||||
CHECK_FALSE(ge.contains(v1));
|
||||
CHECK(ge.contains(v2));
|
||||
CHECK(ge.contains(v3));
|
||||
CHECK(ge.contains(v4));
|
||||
CHECK_EQ(ge.str(), ">=2.0");
|
||||
|
||||
const auto lt = VersionPredicate::make_less(v2);
|
||||
CHECK(lt.contains(v1));
|
||||
CHECK_FALSE(lt.contains(v2));
|
||||
CHECK_FALSE(lt.contains(v3));
|
||||
CHECK_FALSE(lt.contains(v4));
|
||||
CHECK_EQ(lt.str(), "<2.0");
|
||||
|
||||
const auto le = VersionPredicate::make_less_equal(v2);
|
||||
CHECK(le.contains(v1));
|
||||
CHECK(le.contains(v2));
|
||||
CHECK_FALSE(le.contains(v3));
|
||||
CHECK_FALSE(le.contains(v4));
|
||||
CHECK_EQ(le.str(), "<=2.0");
|
||||
|
||||
const auto sw = VersionPredicate::make_starts_with(v2);
|
||||
CHECK_FALSE(sw.contains(v1));
|
||||
|
@ -72,6 +79,7 @@ TEST_SUITE("specs::version_spec")
|
|||
CHECK(sw.contains(v201));
|
||||
CHECK_FALSE(sw.contains(v3));
|
||||
CHECK_FALSE(sw.contains(v4));
|
||||
CHECK_EQ(sw.str(), "=2.0");
|
||||
|
||||
const auto nsw = VersionPredicate::make_not_starts_with(v2);
|
||||
CHECK(nsw.contains(v1));
|
||||
|
@ -79,6 +87,7 @@ TEST_SUITE("specs::version_spec")
|
|||
CHECK_FALSE(nsw.contains(v201));
|
||||
CHECK(nsw.contains(v3));
|
||||
CHECK(nsw.contains(v4));
|
||||
CHECK_EQ(nsw.str(), "!=2.0.*");
|
||||
|
||||
const auto cp2 = VersionPredicate::make_compatible_with(v2, 2);
|
||||
CHECK_FALSE(cp2.contains(v1));
|
||||
|
@ -86,6 +95,7 @@ TEST_SUITE("specs::version_spec")
|
|||
CHECK(cp2.contains(v201));
|
||||
CHECK_FALSE(cp2.contains(v3));
|
||||
CHECK_FALSE(cp2.contains(v4));
|
||||
CHECK_EQ(cp2.str(), "~=2.0");
|
||||
|
||||
const auto cp3 = VersionPredicate::make_compatible_with(v2, 3);
|
||||
CHECK_FALSE(cp3.contains(v1));
|
||||
|
@ -93,6 +103,7 @@ TEST_SUITE("specs::version_spec")
|
|||
CHECK_FALSE(cp3.contains(v201));
|
||||
CHECK_FALSE(cp3.contains(v3));
|
||||
CHECK_FALSE(cp3.contains(v4));
|
||||
CHECK_EQ(cp3.str(), "~=2.0.0");
|
||||
|
||||
const auto predicates = std::array{ free, eq, ne, lt, le, gt, ge, sw, cp2, cp3 };
|
||||
for (std::size_t i = 0; i < predicates.size(); ++i)
|
||||
|
@ -140,6 +151,10 @@ TEST_SUITE("specs::version_spec")
|
|||
CHECK_FALSE(spec.contains(Version(0, { { { 2 } }, { { 0 } }, { { 0 } } }))); // 2.0.0
|
||||
CHECK_FALSE(spec.contains(Version(0, { { { 2 } }, { { 1 } } }))); // 2.1
|
||||
CHECK_FALSE(spec.contains(Version(0, { { { 2 } }, { { 3 } } }))); // 2.3
|
||||
|
||||
// Note this won't always be the same as the parsed string because of the tree
|
||||
// serialization
|
||||
CHECK_EQ(spec.str(), "<2.0|(>2.3,<=2.8.0)");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include <array>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
@ -466,6 +465,7 @@ TEST_SUITE("util::flat_bool_expr_tree")
|
|||
{
|
||||
const auto reference_eval = [](std::array<bool, 5> x) -> bool
|
||||
{ return (x[0] || x[1]) && (x[2] && (x[3] || x[4])); };
|
||||
// Infix: ((x3 or x4) and x2) and (x0 or x1)
|
||||
// Postfix: x0 x1 or x2 x3 x4 or and and
|
||||
auto parser = PostfixParser<std::size_t, BoolOperator>{};
|
||||
parser.push_variable(0);
|
||||
|
@ -493,6 +493,7 @@ TEST_SUITE("util::flat_bool_expr_tree")
|
|||
const auto reference_eval = [](std::array<bool, 7> x) -> bool
|
||||
{ return ((x[0] || x[1]) && (x[2] || x[3] || x[4]) && x[5]) || x[6]; };
|
||||
auto parser = InfixParser<std::size_t, BoolOperator>{};
|
||||
// Infix: ((x0 or x1) and (x2 or x3 or x4) and x5) or x6
|
||||
parser.push_left_parenthesis();
|
||||
parser.push_left_parenthesis();
|
||||
parser.push_variable(0);
|
||||
|
@ -524,4 +525,59 @@ TEST_SUITE("util::flat_bool_expr_tree")
|
|||
CHECK_EQ(tree.evaluate(eval), reference_eval(values));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Infix traversal")
|
||||
{
|
||||
auto parser = InfixParser<std::size_t, BoolOperator>{};
|
||||
// Infix: ((x0 or x1) and (x2 or x3 or x4) and x5) or x6
|
||||
parser.push_left_parenthesis();
|
||||
parser.push_left_parenthesis();
|
||||
parser.push_variable(0);
|
||||
parser.push_operator(BoolOperator::logical_or);
|
||||
parser.push_variable(1);
|
||||
parser.push_right_parenthesis();
|
||||
parser.push_operator(BoolOperator::logical_and);
|
||||
parser.push_left_parenthesis();
|
||||
parser.push_variable(2);
|
||||
parser.push_operator(BoolOperator::logical_or);
|
||||
parser.push_variable(3);
|
||||
parser.push_operator(BoolOperator::logical_or);
|
||||
parser.push_variable(4);
|
||||
parser.push_right_parenthesis();
|
||||
parser.push_operator(BoolOperator::logical_and);
|
||||
parser.push_variable(5);
|
||||
parser.push_right_parenthesis();
|
||||
parser.push_operator(BoolOperator::logical_or);
|
||||
parser.push_variable(6);
|
||||
parser.finalize();
|
||||
auto tree = flat_bool_expr_tree(std::move(parser).tree());
|
||||
|
||||
auto result = std::string();
|
||||
tree.infix_for_each(
|
||||
[&](const auto& token)
|
||||
{
|
||||
using tree_type = decltype(tree);
|
||||
using Token = std::decay_t<decltype(token)>;
|
||||
if constexpr (std::is_same_v<Token, tree_type::LeftParenthesis>)
|
||||
{
|
||||
result += '(';
|
||||
}
|
||||
if constexpr (std::is_same_v<Token, tree_type::RightParenthesis>)
|
||||
{
|
||||
result += ')';
|
||||
}
|
||||
if constexpr (std::is_same_v<Token, BoolOperator>)
|
||||
{
|
||||
result += (token == BoolOperator::logical_or) ? " or " : " and ";
|
||||
}
|
||||
if constexpr (std::is_same_v<Token, tree_type::variable_type>)
|
||||
{
|
||||
result += 'x';
|
||||
result += std::to_string(token);
|
||||
}
|
||||
}
|
||||
);
|
||||
// There could be many representations, here is one
|
||||
CHECK_EQ(result, "((x0 or x1) and ((x2 or (x3 or x4)) and x5)) or x6");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -525,7 +525,13 @@ namespace mambapy
|
|||
.def_property_readonly("local", &Version::local)
|
||||
.def("starts_with", &Version::starts_with, py::arg("prefix"))
|
||||
.def("compatible_with", &Version::compatible_with, py::arg("older"), py::arg("level"))
|
||||
.def("__str__", &Version::str)
|
||||
.def("__str__", [](const Version& v) { return v.str(); })
|
||||
.def("str", [](const Version& v) { return v.str(); })
|
||||
.def(
|
||||
"str",
|
||||
[](const Version& v, std::size_t level) { return v.str(level); },
|
||||
py::arg("level")
|
||||
)
|
||||
.def(py::self == py::self)
|
||||
.def(py::self != py::self)
|
||||
.def(py::self < py::self)
|
||||
|
@ -555,6 +561,7 @@ namespace mambapy
|
|||
.def_readonly_static("glob_suffix_token", &VersionSpec::glob_suffix_token)
|
||||
.def_static("parse", &VersionSpec::parse, py::arg("str"))
|
||||
.def("contains", &VersionSpec::contains, py::arg("point"))
|
||||
.def("__str__", &VersionSpec::str)
|
||||
.def("__copy__", ©<VersionSpec>)
|
||||
.def("__deepcopy__", &deepcopy<VersionSpec>, py::arg("memo"));
|
||||
}
|
||||
|
|
|
@ -604,6 +604,7 @@ def test_Version():
|
|||
|
||||
# str
|
||||
assert str(v) == "3!1.3ab2.4+42.0alpha"
|
||||
assert v.str(level=1) == "3!1+42"
|
||||
|
||||
# Copy
|
||||
assert copy.deepcopy(v) == v
|
||||
|
@ -650,5 +651,8 @@ def test_VersionSpec():
|
|||
assert not vs.contains(Version.parse("1.1"))
|
||||
assert vs.contains(Version.parse("2.1"))
|
||||
|
||||
# Copy
|
||||
copy.deepcopy(vs) # No easy comaprison
|
||||
# str
|
||||
assert str(vs) == ">2.0,<3.0"
|
||||
|
||||
# Copy, no easy comparison, this may not work for all specs
|
||||
assert str(copy.deepcopy(vs)) == str(vs)
|
||||
|
|
Loading…
Reference in New Issue