mirror of https://github.com/mamba-org/mamba.git
Refactor MatchSpec::str (#3215)
* Add split_once_on_any * Split MatchSpec::track_features * Add VersionSpec::expression_size * Add UnresolvedChannel::is_package * Refactor MatchSpec::str to use fmt * Forbid version of in MatchSpec in attributes * Document more Conda discrepencies
This commit is contained in:
parent
a00ef35675
commit
c54ab6ce7b
|
@ -12,11 +12,14 @@
|
|||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include "mamba/specs/build_number_spec.hpp"
|
||||
#include "mamba/specs/error.hpp"
|
||||
#include "mamba/specs/glob_spec.hpp"
|
||||
#include "mamba/specs/unresolved_channel.hpp"
|
||||
#include "mamba/specs/version_spec.hpp"
|
||||
#include "mamba/util/flat_set.hpp"
|
||||
#include "mamba/util/heap_optional.hpp"
|
||||
|
||||
namespace mamba::specs
|
||||
|
@ -29,6 +32,8 @@ namespace mamba::specs
|
|||
using BuildStringSpec = GlobSpec;
|
||||
using platform_set = typename UnresolvedChannel::platform_set;
|
||||
using platform_set_const_ref = std::reference_wrapper<const platform_set>;
|
||||
using string_set = typename util::flat_set<std::string>;
|
||||
using string_set_const_ref = typename std::reference_wrapper<const string_set>;
|
||||
|
||||
inline static constexpr char url_md5_sep = '#';
|
||||
inline static constexpr char prefered_list_open = '[';
|
||||
|
@ -41,6 +46,7 @@ namespace mamba::specs
|
|||
inline static constexpr char attribute_sep = ',';
|
||||
inline static constexpr char attribute_assign = '=';
|
||||
inline static constexpr auto package_version_sep = std::array{ ' ', '=', '<', '>', '~', '!' };
|
||||
inline static constexpr auto feature_sep = std::array{ ' ', ',' };
|
||||
|
||||
|
||||
[[nodiscard]] static auto parse(std::string_view spec) -> expected_parse_t<MatchSpec>;
|
||||
|
@ -88,8 +94,8 @@ namespace mamba::specs
|
|||
[[nodiscard]] auto features() const -> std::string_view;
|
||||
void set_features(std::string val);
|
||||
|
||||
[[nodiscard]] auto track_features() const -> std::string_view;
|
||||
void set_track_features(std::string val);
|
||||
[[nodiscard]] auto track_features() const -> std::optional<string_set_const_ref>;
|
||||
void set_track_features(string_set val);
|
||||
|
||||
[[nodiscard]] auto optional() const -> bool;
|
||||
void set_optional(bool opt);
|
||||
|
@ -112,7 +118,7 @@ namespace mamba::specs
|
|||
std::string license = {};
|
||||
std::string license_family = {};
|
||||
std::string features = {};
|
||||
std::string track_features = {};
|
||||
string_set track_features = {};
|
||||
bool optional = false;
|
||||
};
|
||||
|
||||
|
@ -142,4 +148,12 @@ namespace mamba::specs
|
|||
auto operator""_ms(const char* str, std::size_t len) -> MatchSpec;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<::mamba::specs::MatchSpec>
|
||||
{
|
||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin());
|
||||
|
||||
auto format(const ::mamba::specs::MatchSpec& spec, format_context& ctx) -> decltype(ctx.out());
|
||||
};
|
||||
#endif
|
||||
|
|
|
@ -107,6 +107,8 @@ namespace mamba::specs
|
|||
[[nodiscard]] auto platform_filters() && -> platform_set;
|
||||
auto clear_platform_filters() -> platform_set;
|
||||
|
||||
[[nodiscard]] auto is_package() const -> bool;
|
||||
|
||||
[[nodiscard]] auto str() const -> std::string;
|
||||
|
||||
private:
|
||||
|
|
|
@ -185,6 +185,11 @@ namespace mamba::specs
|
|||
*/
|
||||
[[nodiscard]] auto contains(const Version& point) const -> bool;
|
||||
|
||||
/**
|
||||
* Return the size of the boolean expression tree.
|
||||
*/
|
||||
[[nodiscard]] auto expression_size() const -> std::size_t;
|
||||
|
||||
private:
|
||||
|
||||
tree_type m_tree;
|
||||
|
|
|
@ -228,6 +228,18 @@ namespace mamba::util
|
|||
[[nodiscard]] auto rsplit_once(std::string_view str, std::string_view sep)
|
||||
-> std::tuple<std::optional<std::string_view>, std::string_view>;
|
||||
|
||||
[[nodiscard]] auto split_once_on_any(std::string_view str, std::string_view many_seps)
|
||||
-> std::tuple<std::string_view, std::optional<std::string_view>>;
|
||||
template <std::size_t N>
|
||||
[[nodiscard]] auto split_once_on_any(std::string_view str, std::array<char, N> many_seps)
|
||||
-> std::tuple<std::string_view, std::optional<std::string_view>>;
|
||||
|
||||
[[nodiscard]] auto rsplit_once_on_any(std::string_view str, std::string_view many_seps)
|
||||
-> std::tuple<std::optional<std::string_view>, std::string_view>;
|
||||
template <std::size_t N>
|
||||
[[nodiscard]] auto rsplit_once_on_any(std::string_view str, std::array<char, N> many_seps)
|
||||
-> std::tuple<std::optional<std::string_view>, std::string_view>;
|
||||
|
||||
[[nodiscard]] auto
|
||||
split(std::string_view input, std::string_view sep, std::size_t max_split = SIZE_MAX)
|
||||
-> std::vector<std::string>;
|
||||
|
@ -543,6 +555,20 @@ namespace mamba::util
|
|||
return detail::strip_if_parts_impl(input, std::move(should_strip));
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
auto split_once_on_any(std::string_view str, std::array<char, N> many_seps)
|
||||
-> std::tuple<std::string_view, std::optional<std::string_view>>
|
||||
{
|
||||
return split_once_on_any(str, std::string_view{ many_seps.data(), many_seps.size() });
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
auto rsplit_once_on_any(std::string_view str, std::array<char, N> many_seps)
|
||||
-> std::tuple<std::optional<std::string_view>, std::string_view>
|
||||
{
|
||||
return rsplit_once_on_any(str, std::string_view{ many_seps.data(), many_seps.size() });
|
||||
}
|
||||
|
||||
/**************************************
|
||||
* Implementation of join functions *
|
||||
**************************************/
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
//
|
||||
// The full license is in the file LICENSE, distributed with this software.
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
|
@ -184,6 +183,24 @@ namespace mamba::specs
|
|||
return util::starts_with_any(str, std::array{ 'y', 'Y', 't', 'T', '1' });
|
||||
}
|
||||
|
||||
auto split_features(std::string_view str) -> MatchSpec::string_set
|
||||
{
|
||||
auto out = MatchSpec::string_set();
|
||||
|
||||
auto feat = std::string_view();
|
||||
auto rest = std::optional<std::string_view>(str);
|
||||
while (rest.has_value())
|
||||
{
|
||||
std::tie(feat, rest) = util::split_once_on_any(rest.value(), MatchSpec::feature_sep);
|
||||
feat = util::strip(feat);
|
||||
if (!feat.empty())
|
||||
{
|
||||
out.insert(std::string(feat));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto set_single_matchspec_attribute_impl( //
|
||||
MatchSpec& spec,
|
||||
std::string_view attr,
|
||||
|
@ -252,7 +269,7 @@ namespace mamba::specs
|
|||
}
|
||||
if (attr == "track_features")
|
||||
{
|
||||
spec.set_track_features(std::string(val));
|
||||
spec.set_track_features(split_features(val));
|
||||
return {};
|
||||
}
|
||||
if (attr == "optional")
|
||||
|
@ -284,6 +301,31 @@ namespace mamba::specs
|
|||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto split_attribute_val(std::string_view key_val)
|
||||
-> expected_parse_t<std::tuple<std::string_view, std::optional<std::string_view>>>
|
||||
{
|
||||
// Forbid known ambiguity
|
||||
if (util::starts_with(key_val, "version"))
|
||||
{
|
||||
const auto op_val = util::lstrip(key_val, "version");
|
||||
if ( //
|
||||
util::starts_with(op_val, "==") //
|
||||
|| util::starts_with(op_val, "!=")
|
||||
|| util::starts_with(op_val, "~=") //
|
||||
|| util::starts_with(op_val, '>') //
|
||||
|| util::starts_with(op_val, '<'))
|
||||
{
|
||||
return make_unexpected_parse(fmt::format(
|
||||
R"(Implicit format "{}" is not allowed, use "version='{}'" instead.)",
|
||||
key_val,
|
||||
op_val
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return { util::split_once(key_val, MatchSpec::attribute_assign) };
|
||||
}
|
||||
|
||||
[[nodiscard]] auto set_matchspec_attributes( //
|
||||
MatchSpec& spec,
|
||||
std::string_view attrs
|
||||
|
@ -291,21 +333,41 @@ namespace mamba::specs
|
|||
{
|
||||
return find_attribute_split(attrs)
|
||||
.and_then(
|
||||
[&](std::size_t next_pos)
|
||||
[&](std::size_t next_pos) -> expected_parse_t<std::size_t>
|
||||
{
|
||||
auto [key, value] = util::split_once(
|
||||
attrs.substr(0, next_pos),
|
||||
MatchSpec::attribute_assign
|
||||
);
|
||||
|
||||
return set_single_matchspec_attribute(
|
||||
spec,
|
||||
util::to_lower(util::strip(key)),
|
||||
strip_whitespace_quotes(value.value_or("true"))
|
||||
)
|
||||
return split_attribute_val(attrs.substr(0, next_pos))
|
||||
.and_then(
|
||||
[&](auto&& key_val)
|
||||
{
|
||||
auto [key, value] = std::forward<decltype(key_val)>(key_val);
|
||||
return set_single_matchspec_attribute(
|
||||
spec,
|
||||
util::to_lower(util::strip(key)),
|
||||
strip_whitespace_quotes(value.value_or("true"))
|
||||
);
|
||||
}
|
||||
)
|
||||
.transform([&]() { return next_pos; });
|
||||
}
|
||||
)
|
||||
.and_then(
|
||||
[&](std::size_t next_pos) -> expected_parse_t<std::size_t>
|
||||
{
|
||||
return split_attribute_val(attrs.substr(0, next_pos))
|
||||
.and_then(
|
||||
[&](auto&& key_val)
|
||||
{
|
||||
auto [key, value] = std::forward<decltype(key_val)>(key_val);
|
||||
return set_single_matchspec_attribute(
|
||||
spec,
|
||||
util::to_lower(util::strip(key)),
|
||||
strip_whitespace_quotes(value.value_or("true"))
|
||||
)
|
||||
.transform([&]() { return next_pos; });
|
||||
}
|
||||
);
|
||||
}
|
||||
)
|
||||
.and_then(
|
||||
[&](std::size_t next_pos) -> expected_parse_t<void>
|
||||
{
|
||||
|
@ -529,9 +591,7 @@ namespace mamba::specs
|
|||
{
|
||||
if (const auto& chan = channel(); chan.has_value())
|
||||
{
|
||||
auto type = chan->type();
|
||||
using Type = typename UnresolvedChannel::Type;
|
||||
return (type == Type::PackageURL) || (type == Type::PackagePath);
|
||||
return chan->is_package();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -801,18 +861,18 @@ namespace mamba::specs
|
|||
}
|
||||
}
|
||||
|
||||
auto MatchSpec::track_features() const -> std::string_view
|
||||
auto MatchSpec::track_features() const -> std::optional<string_set_const_ref>
|
||||
{
|
||||
if (m_extra.has_value())
|
||||
{
|
||||
return m_extra->track_features;
|
||||
}
|
||||
return "";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void MatchSpec::set_track_features(std::string val)
|
||||
void MatchSpec::set_track_features(string_set val)
|
||||
{
|
||||
if (val != track_features()) // Avoid allocating extra to set the default value
|
||||
if (!val.empty()) // Avoid allocating extra if empty
|
||||
{
|
||||
extra().track_features = std::move(val);
|
||||
}
|
||||
|
@ -853,69 +913,13 @@ namespace mamba::specs
|
|||
return fmt::format("{}", m_name);
|
||||
}
|
||||
|
||||
auto MatchSpec::str() const -> std::string
|
||||
namespace
|
||||
{
|
||||
std::stringstream res;
|
||||
// builder = []
|
||||
// brackets = []
|
||||
|
||||
// channel_matcher = self._match_components.get('channel')
|
||||
// if channel_matcher and channel_matcher.exact_value:
|
||||
// builder.append(text_type(channel_matcher))
|
||||
// elif channel_matcher and not channel_matcher.matches_all:
|
||||
// brackets.append("channel=%s" % text_type(channel_matcher))
|
||||
|
||||
// subdir_matcher = self._match_components.get('subdir')
|
||||
// if subdir_matcher:
|
||||
// if channel_matcher and channel_matcher.exact_value:
|
||||
// builder.append('/%s' % subdir_matcher)
|
||||
// else:
|
||||
// brackets.append("subdir=%s" % subdir_matcher)
|
||||
|
||||
// TODO change as attribute if complex URL, and has "url" if PackageUrl
|
||||
if (m_channel.has_value())
|
||||
{
|
||||
res << fmt::format("{}::", *m_channel);
|
||||
}
|
||||
// TODO when namespaces are implemented!
|
||||
// if (!ns.empty())
|
||||
// {
|
||||
// res << ns;
|
||||
// res << ":";
|
||||
// }
|
||||
res << m_name.str();
|
||||
std::vector<std::string> formatted_brackets;
|
||||
|
||||
auto is_complex_relation = [](const std::string& s)
|
||||
{ return s.find_first_of("><$^|,") != s.npos; };
|
||||
|
||||
if (!m_version.is_explicitly_free())
|
||||
{
|
||||
auto ver = m_version.str();
|
||||
if (is_complex_relation(ver)) // TODO do on VersionSpec
|
||||
{
|
||||
formatted_brackets.push_back(util::concat("version='", ver, "'"));
|
||||
}
|
||||
else
|
||||
{
|
||||
res << ver;
|
||||
// version_exact = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_build_string.is_free())
|
||||
{
|
||||
if (m_build_string.is_exact())
|
||||
{
|
||||
res << "=" << m_build_string.str();
|
||||
}
|
||||
else
|
||||
{
|
||||
formatted_brackets.push_back(util::concat("build='", m_build_string.str(), '\''));
|
||||
}
|
||||
}
|
||||
|
||||
auto maybe_quote = [](std::string_view data) -> std::string_view
|
||||
/**
|
||||
* Find if the string needs a quote, and if so return it.
|
||||
* Otherwise return the empty string.
|
||||
*/
|
||||
auto find_needed_quote(std::string_view data) -> std::string_view
|
||||
{
|
||||
if (auto pos = data.find_first_of(R"( =")"); pos != std::string_view::npos)
|
||||
{
|
||||
|
@ -927,53 +931,11 @@ namespace mamba::specs
|
|||
}
|
||||
return "";
|
||||
};
|
||||
}
|
||||
|
||||
if (const auto& num = build_number(); !num.is_explicitly_free())
|
||||
{
|
||||
formatted_brackets.push_back(util::concat("build_number=", num.str()));
|
||||
}
|
||||
if (const auto& tf = track_features(); !tf.empty())
|
||||
{
|
||||
const auto& q = maybe_quote(tf);
|
||||
formatted_brackets.push_back(util::concat("track_features=", q, tf, q));
|
||||
}
|
||||
if (const auto& feats = features(); !feats.empty())
|
||||
{
|
||||
const auto& q = maybe_quote(feats);
|
||||
formatted_brackets.push_back(util::concat("features=", q, feats, q));
|
||||
}
|
||||
else if (const auto& fn = filename(); !fn.empty() && !channel_is_file())
|
||||
{
|
||||
// No "fn" when we have a URL
|
||||
const auto& q = maybe_quote(fn);
|
||||
formatted_brackets.push_back(util::concat("fn=", q, fn, q));
|
||||
}
|
||||
if (const auto& hash = md5(); !hash.empty())
|
||||
{
|
||||
formatted_brackets.push_back(util::concat("md5=", hash));
|
||||
}
|
||||
if (const auto& hash = sha256(); !hash.empty())
|
||||
{
|
||||
formatted_brackets.push_back(util::concat("sha256=", hash));
|
||||
}
|
||||
if (const auto& l = license(); !l.empty())
|
||||
{
|
||||
formatted_brackets.push_back(util::concat("license=", l));
|
||||
}
|
||||
if (const auto& lf = license_family(); !lf.empty())
|
||||
{
|
||||
formatted_brackets.push_back(util::concat("license_family=", lf));
|
||||
}
|
||||
if (optional())
|
||||
{
|
||||
formatted_brackets.emplace_back("optional");
|
||||
}
|
||||
|
||||
if (!formatted_brackets.empty())
|
||||
{
|
||||
res << "[" << util::join(",", formatted_brackets) << "]";
|
||||
}
|
||||
return res.str();
|
||||
auto MatchSpec::str() const -> std::string
|
||||
{
|
||||
return fmt::format("{}", *this);
|
||||
}
|
||||
|
||||
auto MatchSpec::is_simple() const -> bool
|
||||
|
@ -1001,3 +963,159 @@ namespace mamba::specs
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto
|
||||
fmt::formatter<::mamba::specs::MatchSpec>::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::MatchSpec>::format(
|
||||
const ::mamba::specs::MatchSpec& spec,
|
||||
format_context& ctx
|
||||
) -> decltype(ctx.out())
|
||||
{
|
||||
using MatchSpec = ::mamba::specs::MatchSpec;
|
||||
|
||||
auto out = ctx.out();
|
||||
|
||||
if (const auto& chan = spec.channel(); chan.has_value() && chan->is_package())
|
||||
{
|
||||
out = fmt::format_to(out, "{}", chan.value());
|
||||
if (const auto& md5 = spec.md5(); !md5.empty())
|
||||
{
|
||||
out = fmt::format_to(out, "{}{}", MatchSpec::url_md5_sep, md5);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
if (const auto& chan = spec.channel())
|
||||
{
|
||||
out = fmt::format_to(
|
||||
out,
|
||||
"{}{}{}{}",
|
||||
chan.value(),
|
||||
MatchSpec::channel_namespace_spec_sep,
|
||||
spec.name_space(),
|
||||
MatchSpec::channel_namespace_spec_sep
|
||||
);
|
||||
}
|
||||
else if (auto ns = spec.name_space(); !ns.empty())
|
||||
{
|
||||
out = fmt::format_to(out, "{}{}", ns, MatchSpec::channel_namespace_spec_sep);
|
||||
}
|
||||
out = fmt::format_to(out, "{}", spec.name());
|
||||
|
||||
const bool is_complex_version = spec.version().expression_size() > 1;
|
||||
const bool is_complex_build_string = !(
|
||||
spec.build_string().is_exact() || spec.build_string().is_free()
|
||||
);
|
||||
|
||||
// Any relation is complex, we'll write them all inside the attribute section.
|
||||
// For package filename, we avoid writing the version and build string again as they are part
|
||||
// of the url.
|
||||
if (!is_complex_version && !is_complex_build_string)
|
||||
{
|
||||
if (!spec.build_string().is_free())
|
||||
{
|
||||
out = fmt::format_to(out, "{}={}", spec.version(), spec.build_string());
|
||||
}
|
||||
else if (!spec.version().is_explicitly_free())
|
||||
{
|
||||
out = fmt::format_to(out, "{}", spec.version());
|
||||
}
|
||||
}
|
||||
|
||||
bool bracket_written = false;
|
||||
auto ensure_bracket_open_or_comma = [&]()
|
||||
{
|
||||
out = fmt::format_to(
|
||||
out,
|
||||
"{}",
|
||||
bracket_written ? MatchSpec::attribute_sep : MatchSpec::prefered_list_open
|
||||
);
|
||||
bracket_written = true;
|
||||
};
|
||||
auto ensure_bracket_close = [&]()
|
||||
{
|
||||
if (bracket_written)
|
||||
{
|
||||
out = fmt::format_to(out, "{}", MatchSpec::prefered_list_close);
|
||||
}
|
||||
};
|
||||
|
||||
if (is_complex_version || is_complex_build_string)
|
||||
{
|
||||
if (const auto& ver = spec.version(); !ver.is_explicitly_free())
|
||||
{
|
||||
ensure_bracket_open_or_comma();
|
||||
out = fmt::format_to(out, "version={0}{1}{0}", MatchSpec::prefered_quote, ver);
|
||||
}
|
||||
if (const auto& bs = spec.build_string(); !bs.is_free())
|
||||
{
|
||||
ensure_bracket_open_or_comma();
|
||||
out = fmt::format_to(out, "build={0}{1}{0}", MatchSpec::prefered_quote, bs);
|
||||
}
|
||||
}
|
||||
if (const auto& num = spec.build_number(); !num.is_explicitly_free())
|
||||
{
|
||||
ensure_bracket_open_or_comma();
|
||||
out = fmt::format_to(out, "build_number={0}{1}{0}", MatchSpec::prefered_quote, num);
|
||||
}
|
||||
if (const auto& tf = spec.track_features(); tf.has_value() && !tf->get().empty())
|
||||
{
|
||||
ensure_bracket_open_or_comma();
|
||||
out = fmt::format_to(
|
||||
out,
|
||||
"track_features={0}{1}{0}",
|
||||
MatchSpec::prefered_quote,
|
||||
fmt::join(tf->get(), std::string_view(&MatchSpec::feature_sep.front(), 1))
|
||||
);
|
||||
}
|
||||
if (const auto& feats = spec.features(); !feats.empty())
|
||||
{
|
||||
ensure_bracket_open_or_comma();
|
||||
const auto& q = mamba::specs::find_needed_quote(feats);
|
||||
out = fmt::format_to(out, "features={0}{1}{0}", q, feats);
|
||||
}
|
||||
if (const auto& fn = spec.filename(); !fn.empty())
|
||||
{
|
||||
ensure_bracket_open_or_comma();
|
||||
const auto& q = mamba::specs::find_needed_quote(fn);
|
||||
out = fmt::format_to(out, "fn={0}{1}{0}", q, fn);
|
||||
}
|
||||
if (const auto& hash = spec.md5(); !hash.empty())
|
||||
{
|
||||
ensure_bracket_open_or_comma();
|
||||
out = fmt::format_to(out, "md5={}", hash);
|
||||
}
|
||||
if (const auto& hash = spec.sha256(); !hash.empty())
|
||||
{
|
||||
ensure_bracket_open_or_comma();
|
||||
out = fmt::format_to(out, "sha256={}", hash);
|
||||
}
|
||||
if (const auto& license = spec.license(); !license.empty())
|
||||
{
|
||||
ensure_bracket_open_or_comma();
|
||||
out = fmt::format_to(out, "license={}", license);
|
||||
}
|
||||
if (const auto& lf = spec.license_family(); !lf.empty())
|
||||
{
|
||||
ensure_bracket_open_or_comma();
|
||||
out = fmt::format_to(out, "license_family={}", lf);
|
||||
}
|
||||
if (spec.optional())
|
||||
{
|
||||
ensure_bracket_open_or_comma();
|
||||
out = fmt::format_to(out, "optional");
|
||||
}
|
||||
ensure_bracket_close();
|
||||
|
||||
return out;
|
||||
}
|
||||
|
|
|
@ -227,6 +227,11 @@ namespace mamba::specs
|
|||
return std::exchange(m_platform_filters, {});
|
||||
}
|
||||
|
||||
auto UnresolvedChannel::is_package() const -> bool
|
||||
{
|
||||
return (type() == Type::PackageURL) || (type() == Type::PackagePath);
|
||||
}
|
||||
|
||||
auto UnresolvedChannel::str() const -> std::string
|
||||
{
|
||||
return fmt::format("{}", *this);
|
||||
|
|
|
@ -311,6 +311,11 @@ namespace mamba::specs
|
|||
return fmt::format("{:b}", *this);
|
||||
}
|
||||
|
||||
auto VersionSpec::expression_size() const -> std::size_t
|
||||
{
|
||||
return m_tree.size();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename Val, typename Range>
|
||||
|
|
|
@ -628,6 +628,32 @@ namespace mamba::util
|
|||
return rsplit_once_impl(str, sep);
|
||||
}
|
||||
|
||||
/***************************************************
|
||||
* Implementation of split_once_on_any functions *
|
||||
***************************************************/
|
||||
|
||||
auto split_once_on_any(std::string_view str, std::string_view many_seps)
|
||||
-> std::tuple<std::string_view, std::optional<std::string_view>>
|
||||
{
|
||||
static constexpr auto npos = std::string_view::npos;
|
||||
if (const auto pos = str.find_first_of(many_seps); pos != npos)
|
||||
{
|
||||
return { str.substr(0, pos), str.substr(pos + 1) };
|
||||
}
|
||||
return { str, std::nullopt };
|
||||
}
|
||||
|
||||
auto rsplit_once_on_any(std::string_view str, std::string_view many_seps)
|
||||
-> std::tuple<std::optional<std::string_view>, std::string_view>
|
||||
{
|
||||
static constexpr auto npos = std::string_view::npos;
|
||||
if (const auto pos = str.find_last_of(many_seps); pos != npos)
|
||||
{
|
||||
return { str.substr(0, pos), str.substr(pos + 1) };
|
||||
}
|
||||
return { std::nullopt, str };
|
||||
}
|
||||
|
||||
/***************************************
|
||||
* Implementation of split functions *
|
||||
***************************************/
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <doctest/doctest.h>
|
||||
|
||||
#include "mamba/specs/match_spec.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
|
||||
using namespace mamba;
|
||||
using namespace mamba::specs;
|
||||
|
@ -17,39 +18,44 @@ TEST_SUITE("specs::match_spec")
|
|||
|
||||
TEST_CASE("parse")
|
||||
{
|
||||
SUBCASE("xtensor==0.12.3")
|
||||
{
|
||||
auto ms = MatchSpec::parse("xtensor==0.12.3").value();
|
||||
CHECK_EQ(ms.version().str(), "==0.12.3");
|
||||
CHECK_EQ(ms.name().str(), "xtensor");
|
||||
}
|
||||
|
||||
SUBCASE("<empty>")
|
||||
{
|
||||
auto ms = MatchSpec::parse("").value();
|
||||
CHECK_EQ(ms.version().str(), "=*");
|
||||
CHECK_EQ(ms.name().str(), "*");
|
||||
CHECK(ms.name().is_free());
|
||||
CHECK(ms.version().is_explicitly_free());
|
||||
CHECK(ms.build_string().is_free());
|
||||
CHECK(ms.build_number().is_explicitly_free());
|
||||
CHECK_EQ(ms.str(), "*");
|
||||
}
|
||||
|
||||
SUBCASE("ipykernel ")
|
||||
SUBCASE("xtensor==0.12.3")
|
||||
{
|
||||
auto ms = MatchSpec::parse("xtensor==0.12.3").value();
|
||||
CHECK_EQ(ms.name().str(), "xtensor");
|
||||
CHECK_EQ(ms.version().str(), "==0.12.3");
|
||||
CHECK_EQ(ms.str(), "xtensor==0.12.3");
|
||||
}
|
||||
|
||||
SUBCASE("ipykernel")
|
||||
{
|
||||
auto ms = MatchSpec::parse("ipykernel").value();
|
||||
CHECK_EQ(ms.version().str(), "=*");
|
||||
CHECK_EQ(ms.name().str(), "ipykernel");
|
||||
CHECK(ms.version().is_explicitly_free());
|
||||
CHECK_EQ(ms.str(), "ipykernel");
|
||||
}
|
||||
|
||||
SUBCASE("ipykernel ")
|
||||
{
|
||||
auto ms = MatchSpec::parse("ipykernel ").value();
|
||||
CHECK_EQ(ms.version().str(), "=*");
|
||||
CHECK_EQ(ms.name().str(), "ipykernel");
|
||||
CHECK(ms.version().is_explicitly_free());
|
||||
}
|
||||
|
||||
SUBCASE("numpy 1.7*")
|
||||
{
|
||||
auto ms = MatchSpec::parse("numpy 1.7*").value();
|
||||
CHECK_EQ(ms.version().str(), "=1.7");
|
||||
CHECK_EQ(ms.name().str(), "numpy");
|
||||
CHECK_EQ(ms.version().str(), "=1.7");
|
||||
CHECK_EQ(ms.conda_build_form(), "numpy 1.7.*");
|
||||
CHECK_EQ(ms.str(), "numpy=1.7");
|
||||
}
|
||||
|
@ -58,8 +64,10 @@ TEST_SUITE("specs::match_spec")
|
|||
{
|
||||
auto ms = MatchSpec::parse("conda-forge:pypi:xtensor==0.12.3").value();
|
||||
CHECK_EQ(ms.name().str(), "xtensor");
|
||||
CHECK_EQ(ms.version().str(), "==0.12.3");
|
||||
CHECK_EQ(ms.channel().value().str(), "conda-forge");
|
||||
CHECK_EQ(ms.name_space(), "pypi");
|
||||
CHECK_EQ(ms.str(), "conda-forge:pypi:xtensor==0.12.3");
|
||||
}
|
||||
|
||||
SUBCASE("conda-forge/linux-64::xtensor==0.12.3")
|
||||
|
@ -67,36 +75,47 @@ TEST_SUITE("specs::match_spec")
|
|||
auto ms = MatchSpec::parse("numpy[version='1.7|1.8']").value();
|
||||
CHECK_EQ(ms.name().str(), "numpy");
|
||||
CHECK_EQ(ms.version().str(), "==1.7|==1.8");
|
||||
CHECK_EQ(ms.str(), "numpy[version='==1.7|==1.8']");
|
||||
CHECK_EQ(ms.str(), R"(numpy[version="==1.7|==1.8"])");
|
||||
}
|
||||
|
||||
SUBCASE("conda-forge/linux-64::xtensor==0.12.3")
|
||||
{
|
||||
auto ms = MatchSpec::parse("conda-forge/linux-64::xtensor==0.12.3").value();
|
||||
CHECK_EQ(ms.version().str(), "==0.12.3");
|
||||
CHECK_EQ(ms.name().str(), "xtensor");
|
||||
CHECK_EQ(ms.version().str(), "==0.12.3");
|
||||
REQUIRE(ms.channel().has_value());
|
||||
CHECK_EQ(ms.channel()->location(), "conda-forge");
|
||||
CHECK_EQ(ms.channel()->platform_filters(), PlatformSet{ "linux-64" });
|
||||
CHECK_EQ(ms.optional(), false);
|
||||
CHECK_EQ(ms.platforms().value().get(), PlatformSet{ "linux-64" });
|
||||
CHECK_EQ(ms.str(), "conda-forge[linux-64]::xtensor==0.12.3");
|
||||
}
|
||||
|
||||
SUBCASE("conda-forge::foo[build=3](target=blarg,optional)")
|
||||
SUBCASE("conda-forge::foo[build=bld](target=blarg,optional)")
|
||||
{
|
||||
auto ms = MatchSpec::parse("conda-forge::foo[build=3](target=blarg,optional)").value();
|
||||
CHECK_EQ(ms.version().str(), "=*");
|
||||
auto ms = MatchSpec::parse("conda-forge::foo[build=bld](target=blarg,optional)").value();
|
||||
CHECK_EQ(ms.name().str(), "foo");
|
||||
CHECK(ms.version().is_explicitly_free());
|
||||
REQUIRE(ms.channel().has_value());
|
||||
CHECK_EQ(ms.channel()->location(), "conda-forge");
|
||||
CHECK_EQ(ms.build_string().str(), "3");
|
||||
CHECK_EQ(ms.build_string().str(), "bld");
|
||||
CHECK_EQ(ms.optional(), true);
|
||||
CHECK_EQ(ms.str(), "conda-forge::foo=*=bld[optional]");
|
||||
}
|
||||
|
||||
SUBCASE("python[build_number=3]")
|
||||
{
|
||||
auto ms = MatchSpec::parse("python[build_number=3]").value();
|
||||
CHECK_EQ(ms.name().str(), "python");
|
||||
CHECK_EQ(ms.version().str(), "=*");
|
||||
CHECK_EQ(ms.build_number().str(), "=3");
|
||||
CHECK_EQ(ms.str(), R"(python[build_number="=3"])");
|
||||
}
|
||||
|
||||
SUBCASE(R"(blas[track_features="mkl avx"])")
|
||||
{
|
||||
auto ms = MatchSpec::parse(R"(blas[track_features="mkl avx"])").value();
|
||||
CHECK_EQ(ms.name().str(), "blas");
|
||||
CHECK_EQ(ms.track_features().value().get(), MatchSpec::string_set{ "avx", "mkl" });
|
||||
CHECK_EQ(ms.str(), R"(blas[track_features="avx mkl"])");
|
||||
}
|
||||
|
||||
SUBCASE("python[build_number='<=3']")
|
||||
|
@ -104,16 +123,18 @@ TEST_SUITE("specs::match_spec")
|
|||
auto ms = MatchSpec::parse("python[build_number='<=3']").value();
|
||||
CHECK_EQ(ms.name().str(), "python");
|
||||
CHECK_EQ(ms.build_number().str(), "<=3");
|
||||
CHECK_EQ(ms.str(), R"(python[build_number="<=3"])");
|
||||
}
|
||||
|
||||
SUBCASE("https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.4-h59595ed_2.conda#7dbaa197d7ba6032caf7ae7f32c1efa0"
|
||||
)
|
||||
{
|
||||
auto ms = MatchSpec::parse(
|
||||
"https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.4-h59595ed_2.conda"
|
||||
"#7dbaa197d7ba6032caf7ae7f32c1efa0"
|
||||
)
|
||||
.value();
|
||||
constexpr auto str = std::string_view{
|
||||
"https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.4-h59595ed_2.conda"
|
||||
"#7dbaa197d7ba6032caf7ae7f32c1efa0"
|
||||
};
|
||||
|
||||
auto ms = MatchSpec::parse(str).value();
|
||||
CHECK_EQ(ms.name().str(), "ncurses");
|
||||
CHECK_EQ(ms.version().str(), "==6.4");
|
||||
CHECK_EQ(ms.build_string().str(), "h59595ed_2");
|
||||
|
@ -123,14 +144,15 @@ TEST_SUITE("specs::match_spec")
|
|||
);
|
||||
CHECK_EQ(ms.filename(), "ncurses-6.4-h59595ed_2.conda");
|
||||
CHECK_EQ(ms.md5(), "7dbaa197d7ba6032caf7ae7f32c1efa0");
|
||||
CHECK_EQ(ms.str(), str);
|
||||
}
|
||||
|
||||
SUBCASE("https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2")
|
||||
{
|
||||
auto ms = MatchSpec::parse(
|
||||
"https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2"
|
||||
)
|
||||
.value();
|
||||
constexpr auto str = std::string_view{
|
||||
"https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2"
|
||||
};
|
||||
auto ms = MatchSpec::parse(str).value();
|
||||
CHECK_EQ(ms.name().str(), "_libgcc_mutex");
|
||||
CHECK_EQ(ms.version().str(), "==0.1");
|
||||
CHECK_EQ(ms.build_string().str(), "conda_forge");
|
||||
|
@ -139,14 +161,15 @@ TEST_SUITE("specs::match_spec")
|
|||
"https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2"
|
||||
);
|
||||
CHECK_EQ(ms.filename(), "_libgcc_mutex-0.1-conda_forge.tar.bz2");
|
||||
CHECK_EQ(ms.str(), str);
|
||||
}
|
||||
|
||||
SUBCASE("https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_13.tar.bz2")
|
||||
{
|
||||
auto ms = MatchSpec::parse(
|
||||
"https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_13.tar.bz2"
|
||||
)
|
||||
.value();
|
||||
constexpr auto str = std::string_view{
|
||||
"https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_13.tar.bz2"
|
||||
};
|
||||
auto ms = MatchSpec::parse(str).value();
|
||||
CHECK_EQ(ms.name().str(), "libgcc-ng");
|
||||
CHECK_EQ(ms.version().str(), "==11.2.0");
|
||||
CHECK_EQ(ms.build_string().str(), "h1d223b6_13");
|
||||
|
@ -155,26 +178,28 @@ TEST_SUITE("specs::match_spec")
|
|||
"https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_13.tar.bz2"
|
||||
);
|
||||
CHECK_EQ(ms.filename(), "libgcc-ng-11.2.0-h1d223b6_13.tar.bz2");
|
||||
CHECK_EQ(ms.str(), str);
|
||||
}
|
||||
|
||||
SUBCASE("https://conda.anaconda.org/conda-canary/linux-64/conda-4.3.21.post699+1dab973-py36h4a561cd_0.tar.bz2"
|
||||
)
|
||||
{
|
||||
auto ms = MatchSpec::parse(
|
||||
"https://conda.anaconda.org/conda-canary/linux-64/conda-4.3.21.post699+1dab973-py36h4a561cd_0.tar.bz2"
|
||||
)
|
||||
.value();
|
||||
constexpr auto str = std::string_view{
|
||||
"https://conda.anaconda.org/conda-canary/linux-64/conda-4.3.21.post699+1dab973-py36h4a561cd_0.tar.bz2"
|
||||
};
|
||||
auto ms = MatchSpec::parse(str).value();
|
||||
CHECK_EQ(ms.name().str(), "conda");
|
||||
CHECK_EQ(ms.version().str(), "==4.3.21.0post699+1dab973"); // Note the ``.0post``
|
||||
CHECK_EQ(ms.build_string().str(), "py36h4a561cd_0");
|
||||
CHECK_EQ(ms.str(), str);
|
||||
}
|
||||
|
||||
SUBCASE("/home/randomguy/Downloads/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2")
|
||||
{
|
||||
auto ms = MatchSpec::parse(
|
||||
"/home/randomguy/Downloads/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2"
|
||||
)
|
||||
.value();
|
||||
constexpr auto str = std::string_view{
|
||||
"/home/randomguy/Downloads/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2"
|
||||
};
|
||||
auto ms = MatchSpec::parse(str).value();
|
||||
CHECK_EQ(ms.name().str(), "_libgcc_mutex");
|
||||
CHECK_EQ(ms.version().str(), "==0.1");
|
||||
CHECK_EQ(ms.build_string().str(), "conda_forge");
|
||||
|
@ -183,6 +208,7 @@ TEST_SUITE("specs::match_spec")
|
|||
"/home/randomguy/Downloads/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2"
|
||||
);
|
||||
CHECK_EQ(ms.filename(), "_libgcc_mutex-0.1-conda_forge.tar.bz2");
|
||||
CHECK_EQ(ms.str(), str);
|
||||
}
|
||||
|
||||
SUBCASE("xtensor[url=file:///home/wolfv/Downloads/xtensor-0.21.4-hc9558a2_0.tar.bz2]")
|
||||
|
@ -196,6 +222,7 @@ TEST_SUITE("specs::match_spec")
|
|||
ms.channel().value().str(),
|
||||
"file:///home/wolfv/Downloads/xtensor-0.21.4-hc9558a2_0.tar.bz2"
|
||||
);
|
||||
CHECK_EQ(ms.str(), "file:///home/wolfv/Downloads/xtensor-0.21.4-hc9558a2_0.tar.bz2");
|
||||
}
|
||||
|
||||
SUBCASE("foo=1.0=2")
|
||||
|
@ -236,29 +263,32 @@ TEST_SUITE("specs::match_spec")
|
|||
);
|
||||
}
|
||||
|
||||
SUBCASE(R"(defaults::numpy=1.8=py27_0 [name="pytorch" channel='anaconda',version=">=1.8,<2|1.9", build='3'])"
|
||||
SUBCASE(R"(defaults::numpy=1.8=py27_0 [name="pytorch",channel='anaconda',version=">=1.8,<2|1.9", build='3'])"
|
||||
)
|
||||
{
|
||||
auto ms = MatchSpec::parse(
|
||||
R"(defaults::numpy=1.8=py27_0 [name="pytorch" channel='anaconda',version=">=1.8,<2|1.9", build='3'])"
|
||||
R"(defaults::numpy=1.8=py27_0 [name="pytorch",channel='anaconda',version=">=1.8,<2|1.9", build='3'])"
|
||||
)
|
||||
.value();
|
||||
CHECK_EQ(ms.channel().value().str(), "defaults");
|
||||
CHECK_EQ(ms.channel().value().str(), "anaconda");
|
||||
CHECK_EQ(ms.name().str(), "numpy");
|
||||
CHECK_EQ(ms.version().str(), "=1.8");
|
||||
CHECK_EQ(ms.build_string().str(), "py27_0");
|
||||
CHECK_EQ(ms.str(), R"(anaconda::numpy=1.8=py27_0)");
|
||||
}
|
||||
|
||||
SUBCASE(R"(defaults::numpy [ "pytorch" channel='anaconda',version=">=1.8,<2|1.9", build='3'])")
|
||||
SUBCASE(R"(defaults::numpy [ name="pytorch",channel='anaconda',version=">=1.8,<2|1.9", build='3'])"
|
||||
)
|
||||
{
|
||||
auto ms = MatchSpec::parse(
|
||||
R"(defaults::numpy [ "pytorch" channel='anaconda',version=">=1.8,<2|1.9", build='3'])"
|
||||
R"(defaults::numpy [ name="pytorch",channel='anaconda',version=">=1.8,<2|1.9", build='3'])"
|
||||
)
|
||||
.value();
|
||||
CHECK_EQ(ms.channel().value().str(), "defaults");
|
||||
CHECK_EQ(ms.channel().value().str(), "anaconda");
|
||||
CHECK_EQ(ms.name().str(), "numpy");
|
||||
CHECK_EQ(ms.version().str(), ">=1.8,(<2|==1.9)");
|
||||
CHECK_EQ(ms.build_string().str(), "3");
|
||||
CHECK_EQ(ms.str(), R"ms(anaconda::numpy[version=">=1.8,(<2|==1.9)",build="3"])ms");
|
||||
}
|
||||
|
||||
SUBCASE("numpy >1.8,<2|==1.7,!=1.9,~=1.7.1 py34_0")
|
||||
|
@ -267,6 +297,7 @@ TEST_SUITE("specs::match_spec")
|
|||
CHECK_EQ(ms.name().str(), "numpy");
|
||||
CHECK_EQ(ms.version().str(), ">1.8,((<2|==1.7),(!=1.9,~=1.7))");
|
||||
CHECK_EQ(ms.build_string().str(), "py34_0");
|
||||
CHECK_EQ(ms.str(), R"ms(numpy[version=">1.8,((<2|==1.7),(!=1.9,~=1.7))",build="py34_0"])ms");
|
||||
}
|
||||
|
||||
SUBCASE("*[md5=fewjaflknd]")
|
||||
|
@ -274,118 +305,120 @@ TEST_SUITE("specs::match_spec")
|
|||
auto ms = MatchSpec::parse("*[md5=fewjaflknd]").value();
|
||||
CHECK(ms.name().is_free());
|
||||
CHECK_EQ(ms.md5(), "fewjaflknd");
|
||||
CHECK_EQ(ms.str(), "*[md5=fewjaflknd]");
|
||||
}
|
||||
|
||||
SUBCASE("libblas=*=*mkl")
|
||||
{
|
||||
auto ms = MatchSpec::parse("libblas=*=*mkl").value();
|
||||
CHECK_EQ(ms.conda_build_form(), "libblas * *mkl");
|
||||
CHECK_EQ(ms.name().str(), "libblas");
|
||||
CHECK_EQ(ms.version().str(), "=*");
|
||||
CHECK(ms.version().is_explicitly_free());
|
||||
CHECK_EQ(ms.build_string().str(), "*mkl");
|
||||
// CHECK_EQ(ms.str(), "foo==1.0=2");
|
||||
CHECK_EQ(ms.str(), R"(libblas[build="*mkl"])");
|
||||
CHECK_EQ(ms.conda_build_form(), "libblas * *mkl");
|
||||
}
|
||||
|
||||
SUBCASE("libblas=0.15*")
|
||||
{
|
||||
// '*' is part of the version, not the glob
|
||||
auto ms = MatchSpec::parse("libblas=0.15*").value();
|
||||
CHECK_EQ(ms.conda_build_form(), "libblas 0.15*.*");
|
||||
CHECK_EQ(ms.name().str(), "libblas");
|
||||
CHECK_EQ(ms.version().str(), "=0.15*");
|
||||
CHECK(ms.build_string().is_free());
|
||||
CHECK_EQ(ms.str(), "libblas=0.15*");
|
||||
CHECK_EQ(ms.conda_build_form(), "libblas 0.15*.*");
|
||||
}
|
||||
|
||||
SUBCASE("xtensor =0.15*")
|
||||
{
|
||||
// '*' is part of the version, not the glob
|
||||
auto ms = MatchSpec::parse("xtensor =0.15*").value();
|
||||
CHECK_EQ(ms.conda_build_form(), "xtensor 0.15*.*");
|
||||
CHECK_EQ(ms.str(), "xtensor=0.15*");
|
||||
CHECK_EQ(ms.name().str(), "xtensor");
|
||||
CHECK_EQ(ms.version().str(), "=0.15*");
|
||||
CHECK(ms.build_string().is_free());
|
||||
CHECK_EQ(ms.str(), "xtensor=0.15*");
|
||||
CHECK_EQ(ms.conda_build_form(), "xtensor 0.15*.*");
|
||||
}
|
||||
|
||||
SUBCASE("numpy=1.20")
|
||||
{
|
||||
auto ms = MatchSpec::parse("numpy=1.20").value();
|
||||
CHECK_EQ(ms.str(), "numpy=1.20");
|
||||
CHECK_EQ(ms.name().str(), "numpy");
|
||||
CHECK_EQ(ms.version().str(), "=1.20");
|
||||
CHECK(ms.build_string().is_free());
|
||||
CHECK_EQ(ms.str(), "numpy=1.20");
|
||||
}
|
||||
|
||||
SUBCASE("conda-forge::tzdata")
|
||||
{
|
||||
auto ms = MatchSpec::parse("conda-forge::tzdata").value();
|
||||
CHECK_EQ(ms.str(), "conda-forge::tzdata");
|
||||
CHECK_EQ(ms.channel().value().str(), "conda-forge");
|
||||
CHECK_EQ(ms.name().str(), "tzdata");
|
||||
CHECK(ms.version().is_explicitly_free());
|
||||
CHECK(ms.build_string().is_free());
|
||||
CHECK_EQ(ms.str(), "conda-forge::tzdata");
|
||||
}
|
||||
|
||||
SUBCASE("conda-forge/noarch::tzdata")
|
||||
{
|
||||
auto ms = MatchSpec::parse("conda-forge/noarch::tzdata").value();
|
||||
CHECK_EQ(ms.str(), "conda-forge[noarch]::tzdata");
|
||||
CHECK_EQ(ms.channel().value().str(), "conda-forge[noarch]");
|
||||
CHECK_EQ(ms.name().str(), "tzdata");
|
||||
CHECK(ms.version().is_explicitly_free());
|
||||
CHECK(ms.build_string().is_free());
|
||||
CHECK_EQ(ms.str(), "conda-forge[noarch]::tzdata");
|
||||
}
|
||||
|
||||
SUBCASE("conda-forge[noarch]::tzdata")
|
||||
{
|
||||
auto ms = MatchSpec::parse("conda-forge/noarch::tzdata").value();
|
||||
CHECK_EQ(ms.str(), "conda-forge[noarch]::tzdata");
|
||||
CHECK_EQ(ms.channel().value().str(), "conda-forge[noarch]");
|
||||
CHECK_EQ(ms.name().str(), "tzdata");
|
||||
CHECK(ms.version().is_explicitly_free());
|
||||
CHECK(ms.build_string().is_free());
|
||||
CHECK_EQ(ms.str(), "conda-forge[noarch]::tzdata");
|
||||
}
|
||||
|
||||
SUBCASE("pkgs/main::tzdata")
|
||||
{
|
||||
auto ms = MatchSpec::parse("pkgs/main::tzdata").value();
|
||||
CHECK_EQ(ms.str(), "pkgs/main::tzdata");
|
||||
CHECK_EQ(ms.channel().value().str(), "pkgs/main");
|
||||
CHECK_EQ(ms.name().str(), "tzdata");
|
||||
CHECK(ms.version().is_explicitly_free());
|
||||
CHECK(ms.build_string().is_free());
|
||||
CHECK_EQ(ms.str(), "pkgs/main::tzdata");
|
||||
}
|
||||
|
||||
SUBCASE("pkgs/main/noarch::tzdata")
|
||||
{
|
||||
auto ms = MatchSpec::parse("pkgs/main/noarch::tzdata").value();
|
||||
CHECK_EQ(ms.str(), "pkgs/main[noarch]::tzdata");
|
||||
CHECK_EQ(ms.channel().value().str(), "pkgs/main[noarch]");
|
||||
CHECK_EQ(ms.name().str(), "tzdata");
|
||||
CHECK(ms.version().is_explicitly_free());
|
||||
CHECK(ms.build_string().is_free());
|
||||
CHECK_EQ(ms.str(), "pkgs/main[noarch]::tzdata");
|
||||
}
|
||||
|
||||
SUBCASE("conda-forge[noarch]::tzdata[subdir=linux64]")
|
||||
{
|
||||
auto ms = MatchSpec::parse("conda-forge[noarch]::tzdata[subdir=linux64]").value();
|
||||
CHECK_EQ(ms.str(), "conda-forge[noarch]::tzdata");
|
||||
CHECK_EQ(ms.channel().value().str(), "conda-forge[noarch]");
|
||||
CHECK_EQ(ms.platforms().value().get(), MatchSpec::platform_set{ "noarch" });
|
||||
CHECK_EQ(ms.name().str(), "tzdata");
|
||||
CHECK(ms.version().is_explicitly_free());
|
||||
CHECK(ms.build_string().is_free());
|
||||
CHECK_EQ(ms.str(), "conda-forge[noarch]::tzdata");
|
||||
}
|
||||
|
||||
SUBCASE("conda-forge::tzdata[subdir=mamba-37]")
|
||||
{
|
||||
auto ms = MatchSpec::parse("conda-forge::tzdata[subdir=mamba-37]").value();
|
||||
CHECK_EQ(ms.str(), "conda-forge[mamba-37]::tzdata");
|
||||
CHECK_EQ(ms.channel().value().str(), "conda-forge[mamba-37]");
|
||||
CHECK_EQ(ms.platforms().value().get(), MatchSpec::platform_set{ "mamba-37" });
|
||||
CHECK_EQ(ms.name().str(), "tzdata");
|
||||
CHECK(ms.version().is_explicitly_free());
|
||||
CHECK(ms.build_string().is_free());
|
||||
CHECK_EQ(ms.str(), "conda-forge[mamba-37]::tzdata");
|
||||
}
|
||||
|
||||
SUBCASE("conda-canary/linux-64::conda==4.3.21.post699+1dab973=py36h4a561cd_0")
|
||||
|
@ -399,6 +432,35 @@ TEST_SUITE("specs::match_spec")
|
|||
CHECK_EQ(ms.name().str(), "conda");
|
||||
CHECK_EQ(ms.version().str(), "==4.3.21.0post699+1dab973"); // Not ``.0post`` diff
|
||||
CHECK_EQ(ms.build_string().str(), "py36h4a561cd_0");
|
||||
CHECK_EQ(ms.str(), "conda-canary[linux-64]::conda==4.3.21.0post699+1dab973=py36h4a561cd_0");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Conda discrepencies")
|
||||
{
|
||||
SUBCASE("python=3.7=bld")
|
||||
{
|
||||
// For some reason, conda parses version differently in `python=3.7` and
|
||||
// `python=3.7=bld`.
|
||||
// It is `=3.7` and `==3.7` in the later.
|
||||
auto ms = MatchSpec::parse("python=3.7=bld").value();
|
||||
CHECK_EQ(ms.version().str(), "=3.7");
|
||||
CHECK_EQ(ms.build_string().str(), "bld");
|
||||
}
|
||||
|
||||
SUBCASE("python[version>3]")
|
||||
{
|
||||
// Supported by conda but we consider to be already served by `version=">3"`
|
||||
auto error = MatchSpec::parse("python[version>3]").error();
|
||||
CHECK(util::contains(error.what(), R"(use "version='>3'" instead)"));
|
||||
}
|
||||
|
||||
SUBCASE("python[version=3.7]")
|
||||
{
|
||||
// Ambiguous, `version=` parsed as attribute assignment, which leads to
|
||||
// `3.7` (similar to `==3.7`) being parsed as VersionSpec
|
||||
auto ms = MatchSpec::parse("python[version=3.7]").value();
|
||||
CHECK_EQ(ms.version().str(), "==3.7");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -402,6 +402,32 @@ namespace
|
|||
CHECK_EQ(rsplit_once("hello//my/world", "//"), Out{ "hello", "my/world" });
|
||||
}
|
||||
|
||||
TEST_CASE("split_once_on_any")
|
||||
{
|
||||
using Out = std::tuple<std::string_view, std::optional<std::string_view>>;
|
||||
|
||||
CHECK_EQ(split_once_on_any("", "/"), Out{ "", std::nullopt });
|
||||
CHECK_EQ(split_once_on_any("hello,dear world", ", "), Out{ "hello", "dear world" });
|
||||
CHECK_EQ(split_once_on_any("hello dear,world", ", "), Out{ "hello", "dear,world" });
|
||||
CHECK_EQ(split_once_on_any("hello/world", "/"), Out{ "hello", "world" });
|
||||
CHECK_EQ(split_once_on_any("hello//world", "//"), Out{ "hello", "/world" });
|
||||
CHECK_EQ(split_once_on_any("hello/my//world", "/"), Out{ "hello", "my//world" });
|
||||
CHECK_EQ(split_once_on_any("hello/my//world", "//"), Out{ "hello", "my//world" });
|
||||
}
|
||||
|
||||
TEST_CASE("rsplit_once_on_any")
|
||||
{
|
||||
using Out = std::tuple<std::optional<std::string_view>, std::string_view>;
|
||||
|
||||
CHECK_EQ(rsplit_once_on_any("", "/"), Out{ std::nullopt, "" });
|
||||
CHECK_EQ(rsplit_once_on_any("hello,dear world", ", "), Out{ "hello,dear", "world" });
|
||||
CHECK_EQ(rsplit_once_on_any("hello dear,world", ", "), Out{ "hello dear", "world" });
|
||||
CHECK_EQ(rsplit_once_on_any("hello/world", "/"), Out{ "hello", "world" });
|
||||
CHECK_EQ(rsplit_once_on_any("hello//world", "//"), Out{ "hello/", "world" });
|
||||
CHECK_EQ(rsplit_once_on_any("hello/my//world", "/"), Out{ "hello/my/", "world" });
|
||||
CHECK_EQ(rsplit_once_on_any("hello/my//world", "//"), Out{ "hello/my/", "world" });
|
||||
}
|
||||
|
||||
TEST_CASE("split")
|
||||
{
|
||||
std::string a = "hello.again.it's.me.mario";
|
||||
|
|
|
@ -853,15 +853,16 @@ def test_MatchSpec():
|
|||
assert ms.sha256 == "s"
|
||||
assert ms.license == "l"
|
||||
assert ms.license_family == "lf"
|
||||
assert ms.track_features == "ft"
|
||||
assert ms.track_features == {"ft"}
|
||||
assert ms.optional
|
||||
assert not ms.is_file()
|
||||
assert not ms.is_simple()
|
||||
|
||||
# str
|
||||
assert str(ms) == (
|
||||
"conda-forge[plat]::python=3.7"
|
||||
"[build='*pypy',track_features=ft,md5=m,sha256=s,license=l,license_family=lf,optional]"
|
||||
"conda-forge[plat]:ns:python"
|
||||
"""[version="=3.7",build="*pypy",track_features="ft",md5=m,sha256=s,"""
|
||||
"""license=l,license_family=lf,optional]"""
|
||||
)
|
||||
|
||||
# Copy
|
||||
|
|
Loading…
Reference in New Issue