mirror of https://github.com/mamba-org/mamba.git
Handle regex in build string (#3239)
This commit is contained in:
parent
cad6793e21
commit
7942d3feaf
|
@ -166,11 +166,13 @@ set(
|
|||
${LIBMAMBA_SOURCE_DIR}/specs/authentication_info.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/build_number_spec.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/channel.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/chimera_string_spec.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/conda_url.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/glob_spec.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/match_spec.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/package_info.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/platform.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/regex_spec.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/repo_data.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/unresolved_channel.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/version.cpp
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright (c) 2024, QuantStack and Mamba Contributors
|
||||
//
|
||||
// Distributed under the terms of the BSD 3-Clause License.
|
||||
//
|
||||
// The full license is in the file LICENSE, distributed with this software.
|
||||
|
||||
#ifndef MAMBA_SPECS_CHIMERA_STRING_SPEC
|
||||
#define MAMBA_SPECS_CHIMERA_STRING_SPEC
|
||||
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include "mamba/specs/error.hpp"
|
||||
#include "mamba/specs/glob_spec.hpp"
|
||||
#include "mamba/specs/regex_spec.hpp"
|
||||
|
||||
namespace mamba::specs
|
||||
{
|
||||
/**
|
||||
* A matcher for either a glob or a regex expression.
|
||||
*/
|
||||
class ChimeraStringSpec
|
||||
{
|
||||
public:
|
||||
|
||||
using Chimera = std::variant<GlobSpec, RegexSpec>;
|
||||
|
||||
[[nodiscard]] static auto parse(std::string pattern) -> expected_parse_t<ChimeraStringSpec>;
|
||||
|
||||
ChimeraStringSpec();
|
||||
explicit ChimeraStringSpec(Chimera spec);
|
||||
|
||||
[[nodiscard]] auto contains(std::string_view str) const -> bool;
|
||||
|
||||
/**
|
||||
* Return true if the spec will match true on any input.
|
||||
*/
|
||||
[[nodiscard]] auto is_explicitly_free() const -> bool;
|
||||
|
||||
/**
|
||||
* Return true if the spec will match exactly one input.
|
||||
*/
|
||||
[[nodiscard]] auto is_exact() const -> bool;
|
||||
|
||||
/**
|
||||
* Return true if the spec is a glob and not a regex.
|
||||
*/
|
||||
[[nodiscard]] auto is_glob() const -> bool;
|
||||
|
||||
[[nodiscard]] auto str() const -> const std::string&;
|
||||
|
||||
private:
|
||||
|
||||
Chimera m_spec;
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<mamba::specs::ChimeraStringSpec>
|
||||
{
|
||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin());
|
||||
|
||||
auto format(const ::mamba::specs::ChimeraStringSpec& spec, format_context& ctx)
|
||||
-> decltype(ctx.out());
|
||||
};
|
||||
|
||||
#endif
|
|
@ -15,6 +15,7 @@
|
|||
#include <fmt/core.h>
|
||||
|
||||
#include "mamba/specs/build_number_spec.hpp"
|
||||
#include "mamba/specs/chimera_string_spec.hpp"
|
||||
#include "mamba/specs/error.hpp"
|
||||
#include "mamba/specs/glob_spec.hpp"
|
||||
#include "mamba/specs/unresolved_channel.hpp"
|
||||
|
@ -31,7 +32,7 @@ namespace mamba::specs
|
|||
public:
|
||||
|
||||
using NameSpec = GlobSpec;
|
||||
using BuildStringSpec = GlobSpec;
|
||||
using BuildStringSpec = ChimeraStringSpec;
|
||||
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>;
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright (c) 2024, QuantStack and Mamba Contributors
|
||||
//
|
||||
// Distributed under the terms of the BSD 3-Clause License.
|
||||
//
|
||||
// The full license is in the file LICENSE, distributed with this software.
|
||||
|
||||
#ifndef MAMBA_SPECS_REGEX_SPEC
|
||||
#define MAMBA_SPECS_REGEX_SPEC
|
||||
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include "mamba/specs/error.hpp"
|
||||
|
||||
namespace mamba::specs
|
||||
{
|
||||
/**
|
||||
* A matcher for regex expression.
|
||||
*/
|
||||
class RegexSpec
|
||||
{
|
||||
public:
|
||||
|
||||
inline static constexpr std::string_view free_pattern = ".*";
|
||||
inline static constexpr char pattern_start = '^';
|
||||
inline static constexpr char pattern_end = '$';
|
||||
|
||||
[[nodiscard]] static auto parse(std::string pattern) -> expected_parse_t<RegexSpec>;
|
||||
|
||||
RegexSpec();
|
||||
RegexSpec(std::regex pattern, std::string raw_pattern);
|
||||
|
||||
[[nodiscard]] auto contains(std::string_view str) const -> bool;
|
||||
|
||||
/**
|
||||
* Return true if the spec will match true on any input.
|
||||
*/
|
||||
[[nodiscard]] auto is_explicitly_free() const -> bool;
|
||||
|
||||
/**
|
||||
* Return true if the spec will match exaclty one input.
|
||||
*/
|
||||
[[nodiscard]] auto is_exact() const -> bool;
|
||||
|
||||
[[nodiscard]] auto str() const -> const std::string&;
|
||||
|
||||
private:
|
||||
|
||||
std::regex m_pattern;
|
||||
std::string m_raw_pattern;
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<mamba::specs::RegexSpec>
|
||||
{
|
||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin());
|
||||
|
||||
auto format(const ::mamba::specs::RegexSpec& spec, format_context& ctx) -> decltype(ctx.out());
|
||||
};
|
||||
|
||||
#endif
|
|
@ -76,7 +76,9 @@ namespace mamba
|
|||
}
|
||||
if (!pkg.build_string.empty())
|
||||
{
|
||||
out.set_build_string(specs::MatchSpec::BuildStringSpec(pkg.build_string));
|
||||
out.set_build_string(
|
||||
specs::MatchSpec::BuildStringSpec(specs::GlobSpec(pkg.build_string))
|
||||
);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
|
|
@ -1171,7 +1171,9 @@ namespace mamba::solver::libsolv
|
|||
));
|
||||
}
|
||||
);
|
||||
ms.set_build_string(specs::MatchSpec::BuildStringSpec(std::string(s.build_string())));
|
||||
ms.set_build_string(
|
||||
specs::MatchSpec::BuildStringSpec(specs::GlobSpec(std::string(s.build_string())))
|
||||
);
|
||||
ms.set_build_number(
|
||||
specs::BuildNumberSpec(specs::BuildNumberPredicate::make_equal_to(s.build_number()))
|
||||
);
|
||||
|
|
|
@ -303,7 +303,7 @@ namespace mamba::solver
|
|||
using TT = std::remove_cv_t<std::remove_reference_t<T>>;
|
||||
using Build = decltype(std::invoke(&TT::build_string, std::forward<T>(e)));
|
||||
Build bld = std::invoke(&TT::build_string, std::forward<T>(e));
|
||||
if constexpr (std::is_same_v<std::decay_t<decltype(bld)>, specs::GlobSpec>)
|
||||
if constexpr (std::is_same_v<std::decay_t<decltype(bld)>, specs::ChimeraStringSpec>)
|
||||
{
|
||||
return std::forward<Build>(bld).str();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
// Copyright (c) 2024, QuantStack and Mamba Contributors
|
||||
//
|
||||
// Distributed under the terms of the BSD 3-Clause License.
|
||||
//
|
||||
// The full license is in the file LICENSE, distributed with this software.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <type_traits>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "mamba/specs/chimera_string_spec.hpp"
|
||||
#include "mamba/specs/regex_spec.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
|
||||
namespace mamba::specs
|
||||
{
|
||||
namespace
|
||||
{
|
||||
[[nodiscard]] auto is_likely_regex(std::string_view pattern) -> bool
|
||||
{
|
||||
return util::starts_with(pattern, RegexSpec::pattern_start)
|
||||
|| util::ends_with(pattern, RegexSpec::pattern_end);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto make_regex(std::string pattern) -> expected_parse_t<ChimeraStringSpec>
|
||||
{
|
||||
return RegexSpec::parse(std::move(pattern))
|
||||
.transform(
|
||||
[](RegexSpec&& spec) -> ChimeraStringSpec
|
||||
{
|
||||
// Chose a lighter implementation when possible
|
||||
if (spec.is_explicitly_free())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
if (spec.is_exact())
|
||||
{
|
||||
return ChimeraStringSpec{ GlobSpec(std::move(spec).str()) };
|
||||
}
|
||||
return ChimeraStringSpec{ std::move(spec) };
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto is_likely_glob(std::string_view pattern) -> bool
|
||||
{
|
||||
constexpr auto is_likely_glob_char = [](char c) -> bool {
|
||||
return util::is_alphanum(c) || (c == '-') || (c == '_')
|
||||
|| (c == GlobSpec::glob_pattern);
|
||||
};
|
||||
|
||||
return pattern.empty() || (pattern == GlobSpec::free_pattern)
|
||||
|| std::all_of(pattern.cbegin(), pattern.cend(), is_likely_glob_char);
|
||||
}
|
||||
}
|
||||
|
||||
auto ChimeraStringSpec::parse(std::string pattern) -> expected_parse_t<ChimeraStringSpec>
|
||||
{
|
||||
if (is_likely_regex(pattern))
|
||||
{
|
||||
return make_regex(std::move(pattern));
|
||||
}
|
||||
if (is_likely_glob(pattern))
|
||||
{
|
||||
return { ChimeraStringSpec(GlobSpec(std::move(pattern))) };
|
||||
}
|
||||
return make_regex(pattern).or_else(
|
||||
[&](const auto& /* error */) -> expected_parse_t<ChimeraStringSpec>
|
||||
{ return { ChimeraStringSpec(GlobSpec(std::move(pattern))) }; }
|
||||
);
|
||||
}
|
||||
|
||||
ChimeraStringSpec::ChimeraStringSpec()
|
||||
: ChimeraStringSpec(GlobSpec())
|
||||
{
|
||||
}
|
||||
|
||||
ChimeraStringSpec::ChimeraStringSpec(Chimera spec)
|
||||
: m_spec(std::move(spec))
|
||||
{
|
||||
}
|
||||
|
||||
auto ChimeraStringSpec::contains(std::string_view str) const -> bool
|
||||
{
|
||||
return std::visit([&](const auto& s) { return s.contains(str); }, m_spec);
|
||||
}
|
||||
|
||||
auto ChimeraStringSpec::is_explicitly_free() const -> bool
|
||||
{
|
||||
return std::visit(
|
||||
[](const auto& s) -> bool
|
||||
{
|
||||
using S = std::decay_t<decltype(s)>;
|
||||
if constexpr (std::is_same_v<S, GlobSpec>)
|
||||
{
|
||||
return s.is_free();
|
||||
}
|
||||
else if constexpr (std::is_same_v<S, RegexSpec>)
|
||||
{
|
||||
return s.is_explicitly_free();
|
||||
}
|
||||
// All variant cases are not handled.
|
||||
assert(false);
|
||||
},
|
||||
m_spec
|
||||
);
|
||||
}
|
||||
|
||||
auto ChimeraStringSpec::is_exact() const -> bool
|
||||
{
|
||||
return std::visit([](const auto& s) { return s.is_exact(); }, m_spec);
|
||||
}
|
||||
|
||||
auto ChimeraStringSpec::is_glob() const -> bool
|
||||
{
|
||||
return std::holds_alternative<GlobSpec>(m_spec);
|
||||
}
|
||||
|
||||
auto ChimeraStringSpec::str() const -> const std::string&
|
||||
{
|
||||
return std::visit([](const auto& s) -> decltype(auto) { return s.str(); }, m_spec);
|
||||
}
|
||||
}
|
||||
|
||||
auto
|
||||
fmt::formatter<mamba::specs::ChimeraStringSpec>::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::ChimeraStringSpec>::format(
|
||||
const ::mamba::specs::ChimeraStringSpec& spec,
|
||||
format_context& ctx
|
||||
) -> decltype(ctx.out())
|
||||
{
|
||||
return fmt::format_to(ctx.out(), "{}", spec.str());
|
||||
}
|
|
@ -37,7 +37,15 @@ namespace mamba::specs
|
|||
|
||||
// Build string
|
||||
auto [head, tail] = util::rsplit_once(strip_archive_extension(pkg), '-');
|
||||
out.m_build_string = BuildStringSpec(std::string(tail));
|
||||
auto maybe_build_string = BuildStringSpec::parse(std::string(tail));
|
||||
if (maybe_build_string.has_value())
|
||||
{
|
||||
out.m_build_string = std::move(maybe_build_string).value();
|
||||
}
|
||||
else
|
||||
{
|
||||
return make_unexpected_parse(std::move(maybe_build_string).error());
|
||||
}
|
||||
|
||||
if (!head.has_value())
|
||||
{
|
||||
|
@ -216,8 +224,9 @@ namespace mamba::specs
|
|||
}
|
||||
if ((attr == "build") || (attr == "build_string"))
|
||||
{
|
||||
spec.set_build_string(MatchSpec::BuildStringSpec(std::string(val)));
|
||||
return {};
|
||||
return MatchSpec::BuildStringSpec::parse(std::string(val))
|
||||
.transform([&](MatchSpec::BuildStringSpec&& bs)
|
||||
{ spec.set_build_string(std::move(bs)); });
|
||||
}
|
||||
if (attr == "version")
|
||||
{
|
||||
|
@ -575,14 +584,19 @@ namespace mamba::specs
|
|||
auto maybe_ver = VersionSpec::parse(ver_str);
|
||||
if (!maybe_ver)
|
||||
{
|
||||
return parse_error(maybe_ver.error().what());
|
||||
return make_unexpected_parse(std::move(maybe_ver).error());
|
||||
}
|
||||
out.m_version = std::move(maybe_ver).value();
|
||||
}
|
||||
|
||||
if (!bld_str.empty())
|
||||
{
|
||||
out.m_build_string = MatchSpec::BuildStringSpec(std::string(bld_str));
|
||||
auto maybe_build_string = BuildStringSpec::parse(std::string(bld_str));
|
||||
if (!maybe_build_string)
|
||||
{
|
||||
return make_unexpected_parse(std::move(maybe_build_string).error());
|
||||
}
|
||||
out.m_build_string = std::move(maybe_build_string).value();
|
||||
}
|
||||
|
||||
return out;
|
||||
|
@ -895,7 +909,7 @@ namespace mamba::specs
|
|||
auto MatchSpec::conda_build_form() const -> std::string
|
||||
{
|
||||
const bool has_version = !m_version.is_explicitly_free();
|
||||
const bool has_build_str = !m_build_string.is_free();
|
||||
const bool has_build_str = !m_build_string.is_explicitly_free();
|
||||
if (has_version)
|
||||
{
|
||||
if (has_build_str)
|
||||
|
@ -945,6 +959,7 @@ namespace mamba::specs
|
|||
// Glob in names and build_string are fine
|
||||
return (version().expression_size() <= 3) // includes op so e.g. ``>3,<4``
|
||||
&& build_number().is_explicitly_free() //
|
||||
&& build_string().is_glob() //
|
||||
&& !channel().has_value() //
|
||||
&& filename().empty() //
|
||||
&& !platforms().has_value() //
|
||||
|
@ -959,9 +974,9 @@ namespace mamba::specs
|
|||
|
||||
[[nodiscard]] auto MatchSpec::is_only_package_name() const -> bool
|
||||
{
|
||||
return name().is_exact() //
|
||||
&& version().is_explicitly_free() //
|
||||
&& build_string().is_free() //
|
||||
return name().is_exact() //
|
||||
&& version().is_explicitly_free() //
|
||||
&& build_string().is_explicitly_free() //
|
||||
&& is_simple();
|
||||
}
|
||||
|
||||
|
@ -1068,7 +1083,7 @@ fmt::formatter<::mamba::specs::MatchSpec>::format(
|
|||
|
||||
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()
|
||||
spec.build_string().is_exact() || spec.build_string().is_explicitly_free()
|
||||
);
|
||||
|
||||
// Any relation is complex, we'll write them all inside the attribute section.
|
||||
|
@ -1076,7 +1091,7 @@ fmt::formatter<::mamba::specs::MatchSpec>::format(
|
|||
// of the url.
|
||||
if (!is_complex_version && !is_complex_build_string)
|
||||
{
|
||||
if (!spec.build_string().is_free())
|
||||
if (!spec.build_string().is_explicitly_free())
|
||||
{
|
||||
out = fmt::format_to(out, "{}={}", spec.version(), spec.build_string());
|
||||
}
|
||||
|
@ -1111,7 +1126,7 @@ fmt::formatter<::mamba::specs::MatchSpec>::format(
|
|||
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())
|
||||
if (const auto& bs = spec.build_string(); !bs.is_explicitly_free())
|
||||
{
|
||||
ensure_bracket_open_or_comma();
|
||||
out = fmt::format_to(out, "build={0}{1}{0}", MatchSpec::prefered_quote, bs);
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
// Copyright (c) 2024, QuantStack and Mamba Contributors
|
||||
//
|
||||
// Distributed under the terms of the BSD 3-Clause License.
|
||||
//
|
||||
// The full license is in the file LICENSE, distributed with this software.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "mamba/specs/regex_spec.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
|
||||
namespace mamba::specs
|
||||
{
|
||||
namespace
|
||||
{
|
||||
template <typename Range, typename T>
|
||||
[[nodiscard]] auto rng_contains(const Range& rng, const T& elem) -> bool
|
||||
{
|
||||
return std::find(rng.begin(), rng.end(), elem) != rng.end();
|
||||
}
|
||||
}
|
||||
|
||||
auto RegexSpec::parse(std::string pattern) -> expected_parse_t<RegexSpec>
|
||||
{
|
||||
// No other mean of getting parse result with ``std::regex``, but parse error need
|
||||
// to be handled by ``tl::expected`` to be managed down the road.
|
||||
try
|
||||
{
|
||||
auto regex = std::regex(pattern);
|
||||
return { { std::move(regex), std::move(pattern) } };
|
||||
}
|
||||
catch (const std::regex_error& e)
|
||||
{
|
||||
return make_unexpected_parse(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
RegexSpec::RegexSpec()
|
||||
: RegexSpec(std::regex(free_pattern.data(), free_pattern.size()), std::string(free_pattern))
|
||||
{
|
||||
}
|
||||
|
||||
RegexSpec::RegexSpec(std::regex pattern, std::string raw_pattern)
|
||||
: m_pattern(std::move(pattern))
|
||||
, m_raw_pattern(std::move(raw_pattern))
|
||||
{
|
||||
// We force regex to start with `^` and end with `$` to simplify the multiple
|
||||
// possible representations, and because this is the safest way we can make sure it is
|
||||
// not a glob when serializing it.
|
||||
if (!util::starts_with(m_raw_pattern, pattern_start))
|
||||
{
|
||||
m_raw_pattern.insert(m_raw_pattern.begin(), pattern_start);
|
||||
}
|
||||
if (!util::ends_with(m_raw_pattern, pattern_end))
|
||||
{
|
||||
m_raw_pattern.push_back(pattern_end);
|
||||
}
|
||||
}
|
||||
|
||||
auto RegexSpec::contains(std::string_view str) const -> bool
|
||||
{
|
||||
return std::regex_match(str.cbegin(), str.cend(), m_pattern);
|
||||
}
|
||||
|
||||
auto RegexSpec::is_explicitly_free() const -> bool
|
||||
{
|
||||
assert(util::starts_with(m_raw_pattern, pattern_start));
|
||||
assert(util::ends_with(m_raw_pattern, pattern_end));
|
||||
return std::string_view(m_raw_pattern).substr(1, m_raw_pattern.size() - 2) == free_pattern;
|
||||
}
|
||||
|
||||
auto RegexSpec::is_exact() const -> bool
|
||||
{
|
||||
constexpr auto no_special_meaning = [](char c)
|
||||
{ return util::is_alphanum(c) || (c == '-') || (c == '_'); };
|
||||
assert(util::starts_with(m_raw_pattern, pattern_start));
|
||||
assert(util::ends_with(m_raw_pattern, pattern_end));
|
||||
return std::all_of(m_raw_pattern.cbegin() + 1, m_raw_pattern.cend() - 1, no_special_meaning);
|
||||
}
|
||||
|
||||
auto RegexSpec::str() const -> const std::string&
|
||||
{
|
||||
return m_raw_pattern;
|
||||
}
|
||||
}
|
||||
|
||||
auto
|
||||
fmt::formatter<mamba::specs::RegexSpec>::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::RegexSpec>::format(const ::mamba::specs::RegexSpec& spec, format_context& ctx)
|
||||
-> decltype(ctx.out())
|
||||
{
|
||||
return fmt::format_to(ctx.out(), "{}", spec.str());
|
||||
}
|
|
@ -38,11 +38,13 @@ set(
|
|||
src/specs/test_authentication_info.cpp
|
||||
src/specs/test_build_number_spec.cpp
|
||||
src/specs/test_channel.cpp
|
||||
src/specs/test_chimera_string_spec.cpp
|
||||
src/specs/test_conda_url.cpp
|
||||
src/specs/test_glob_spec.cpp
|
||||
src/specs/test_match_spec.cpp
|
||||
src/specs/test_package_info.cpp
|
||||
src/specs/test_platform.cpp
|
||||
src/specs/test_regex_spec.cpp
|
||||
src/specs/test_repo_data.cpp
|
||||
src/specs/test_unresolved_channel.cpp
|
||||
src/specs/test_version.cpp
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
// Copyright (c) 2024, QuantStack and Mamba Contributors
|
||||
//
|
||||
// Distributed under the terms of the BSD 3-Clause License.
|
||||
//
|
||||
// The full license is in the file LICENSE, distributed with this software.
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include "mamba/specs/chimera_string_spec.hpp"
|
||||
|
||||
using namespace mamba::specs;
|
||||
|
||||
TEST_SUITE("specs::chimera_string_spec")
|
||||
{
|
||||
TEST_CASE("Free")
|
||||
{
|
||||
auto spec = ChimeraStringSpec();
|
||||
|
||||
CHECK(spec.contains(""));
|
||||
CHECK(spec.contains("hello"));
|
||||
|
||||
CHECK_EQ(spec.str(), "*");
|
||||
CHECK(spec.is_explicitly_free());
|
||||
CHECK_FALSE(spec.is_exact());
|
||||
CHECK(spec.is_glob());
|
||||
}
|
||||
|
||||
TEST_CASE("mkl")
|
||||
{
|
||||
auto spec = ChimeraStringSpec::parse("mkl").value();
|
||||
|
||||
CHECK(spec.contains("mkl"));
|
||||
CHECK_FALSE(spec.contains(""));
|
||||
CHECK_FALSE(spec.contains("nomkl"));
|
||||
CHECK_FALSE(spec.contains("hello"));
|
||||
|
||||
CHECK_EQ(spec.str(), "mkl");
|
||||
CHECK_FALSE(spec.is_explicitly_free());
|
||||
CHECK(spec.is_exact());
|
||||
CHECK(spec.is_glob());
|
||||
}
|
||||
|
||||
TEST_CASE("py.*")
|
||||
{
|
||||
auto spec = ChimeraStringSpec::parse("py.*").value();
|
||||
|
||||
CHECK(spec.contains("python"));
|
||||
CHECK(spec.contains("py"));
|
||||
CHECK(spec.contains("pypy"));
|
||||
CHECK_FALSE(spec.contains(""));
|
||||
CHECK_FALSE(spec.contains("cpython"));
|
||||
|
||||
CHECK_EQ(spec.str(), "^py.*$");
|
||||
CHECK_FALSE(spec.is_explicitly_free());
|
||||
CHECK_FALSE(spec.is_exact());
|
||||
CHECK_FALSE(spec.is_glob());
|
||||
}
|
||||
|
||||
TEST_CASE("^.*(accelerate|mkl)$")
|
||||
{
|
||||
auto spec = ChimeraStringSpec::parse("^.*(accelerate|mkl)$").value();
|
||||
|
||||
CHECK(spec.contains("accelerate"));
|
||||
CHECK(spec.contains("mkl"));
|
||||
CHECK_FALSE(spec.contains(""));
|
||||
CHECK_FALSE(spec.contains("openblas"));
|
||||
|
||||
CHECK_EQ(spec.str(), "^.*(accelerate|mkl)$");
|
||||
CHECK_FALSE(spec.is_explicitly_free());
|
||||
CHECK_FALSE(spec.is_exact());
|
||||
CHECK_FALSE(spec.is_glob());
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ TEST_SUITE("specs::match_spec")
|
|||
auto ms = MatchSpec::parse("").value();
|
||||
CHECK(ms.name().is_free());
|
||||
CHECK(ms.version().is_explicitly_free());
|
||||
CHECK(ms.build_string().is_free());
|
||||
CHECK(ms.build_string().is_explicitly_free());
|
||||
CHECK(ms.build_number().is_explicitly_free());
|
||||
CHECK_EQ(ms.str(), "*");
|
||||
}
|
||||
|
@ -325,7 +325,7 @@ TEST_SUITE("specs::match_spec")
|
|||
auto ms = MatchSpec::parse("libblas=0.15*").value();
|
||||
CHECK_EQ(ms.name().str(), "libblas");
|
||||
CHECK_EQ(ms.version().str(), "=0.15*");
|
||||
CHECK(ms.build_string().is_free());
|
||||
CHECK(ms.build_string().is_explicitly_free());
|
||||
CHECK_EQ(ms.str(), "libblas=0.15*");
|
||||
CHECK_EQ(ms.conda_build_form(), "libblas 0.15*.*");
|
||||
}
|
||||
|
@ -336,7 +336,7 @@ TEST_SUITE("specs::match_spec")
|
|||
auto ms = MatchSpec::parse("xtensor =0.15*").value();
|
||||
CHECK_EQ(ms.name().str(), "xtensor");
|
||||
CHECK_EQ(ms.version().str(), "=0.15*");
|
||||
CHECK(ms.build_string().is_free());
|
||||
CHECK(ms.build_string().is_explicitly_free());
|
||||
CHECK_EQ(ms.str(), "xtensor=0.15*");
|
||||
CHECK_EQ(ms.conda_build_form(), "xtensor 0.15*.*");
|
||||
}
|
||||
|
@ -346,7 +346,7 @@ TEST_SUITE("specs::match_spec")
|
|||
auto ms = MatchSpec::parse("numpy=1.20").value();
|
||||
CHECK_EQ(ms.name().str(), "numpy");
|
||||
CHECK_EQ(ms.version().str(), "=1.20");
|
||||
CHECK(ms.build_string().is_free());
|
||||
CHECK(ms.build_string().is_explicitly_free());
|
||||
CHECK_EQ(ms.str(), "numpy=1.20");
|
||||
}
|
||||
|
||||
|
@ -356,7 +356,7 @@ TEST_SUITE("specs::match_spec")
|
|||
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(ms.build_string().is_explicitly_free());
|
||||
CHECK_EQ(ms.str(), "conda-forge::tzdata");
|
||||
}
|
||||
|
||||
|
@ -366,7 +366,7 @@ TEST_SUITE("specs::match_spec")
|
|||
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(ms.build_string().is_explicitly_free());
|
||||
CHECK_EQ(ms.str(), "conda-forge[noarch]::tzdata");
|
||||
}
|
||||
|
||||
|
@ -376,7 +376,7 @@ TEST_SUITE("specs::match_spec")
|
|||
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(ms.build_string().is_explicitly_free());
|
||||
CHECK_EQ(ms.str(), "conda-forge[noarch]::tzdata");
|
||||
}
|
||||
|
||||
|
@ -386,7 +386,7 @@ TEST_SUITE("specs::match_spec")
|
|||
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(ms.build_string().is_explicitly_free());
|
||||
CHECK_EQ(ms.str(), "pkgs/main::tzdata");
|
||||
}
|
||||
|
||||
|
@ -396,7 +396,7 @@ TEST_SUITE("specs::match_spec")
|
|||
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(ms.build_string().is_explicitly_free());
|
||||
CHECK_EQ(ms.str(), "pkgs/main[noarch]::tzdata");
|
||||
}
|
||||
|
||||
|
@ -407,7 +407,7 @@ TEST_SUITE("specs::match_spec")
|
|||
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(ms.build_string().is_explicitly_free());
|
||||
CHECK_EQ(ms.str(), "conda-forge[noarch]::tzdata");
|
||||
}
|
||||
|
||||
|
@ -418,7 +418,7 @@ TEST_SUITE("specs::match_spec")
|
|||
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(ms.build_string().is_explicitly_free());
|
||||
CHECK_EQ(ms.str(), "conda-forge[mamba-37]::tzdata");
|
||||
}
|
||||
|
||||
|
@ -435,6 +435,14 @@ TEST_SUITE("specs::match_spec")
|
|||
CHECK_EQ(ms.build_string().str(), "py36h4a561cd_0");
|
||||
CHECK_EQ(ms.str(), "conda-canary[linux-64]::conda==4.3.21.0post699+1dab973=py36h4a561cd_0");
|
||||
}
|
||||
|
||||
SUBCASE("libblas[build=^.*(accelerate|mkl)$]")
|
||||
{
|
||||
auto ms = MatchSpec::parse("libblas[build=^.*(accelerate|mkl)$]").value();
|
||||
CHECK_EQ(ms.name().str(), "libblas");
|
||||
CHECK_EQ(ms.build_string().str(), "^.*(accelerate|mkl)$");
|
||||
CHECK_FALSE(ms.build_string().is_glob());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Conda discrepencies")
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright (c) 2024, QuantStack and Mamba Contributors
|
||||
//
|
||||
// Distributed under the terms of the BSD 3-Clause License.
|
||||
//
|
||||
// The full license is in the file LICENSE, distributed with this software.
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include "mamba/specs/regex_spec.hpp"
|
||||
|
||||
using namespace mamba::specs;
|
||||
|
||||
TEST_SUITE("specs::regex_spec")
|
||||
{
|
||||
TEST_CASE("Free")
|
||||
{
|
||||
auto spec = RegexSpec();
|
||||
|
||||
CHECK(spec.contains(""));
|
||||
CHECK(spec.contains("hello"));
|
||||
|
||||
CHECK_EQ(spec.str(), "^.*$");
|
||||
CHECK(spec.is_explicitly_free());
|
||||
CHECK_FALSE(spec.is_exact());
|
||||
}
|
||||
|
||||
TEST_CASE("mkl")
|
||||
{
|
||||
auto spec = RegexSpec::parse("mkl").value();
|
||||
|
||||
CHECK(spec.contains("mkl"));
|
||||
CHECK_FALSE(spec.contains(""));
|
||||
CHECK_FALSE(spec.contains("nomkl"));
|
||||
CHECK_FALSE(spec.contains("hello"));
|
||||
|
||||
CHECK_EQ(spec.str(), "^mkl$");
|
||||
CHECK_FALSE(spec.is_explicitly_free());
|
||||
CHECK(spec.is_exact());
|
||||
}
|
||||
|
||||
TEST_CASE("py.*")
|
||||
{
|
||||
auto spec = RegexSpec::parse("py.*").value();
|
||||
|
||||
CHECK(spec.contains("python"));
|
||||
CHECK(spec.contains("py"));
|
||||
CHECK(spec.contains("pypy"));
|
||||
CHECK_FALSE(spec.contains(""));
|
||||
CHECK_FALSE(spec.contains("cpython"));
|
||||
|
||||
CHECK_EQ(spec.str(), "^py.*$");
|
||||
CHECK_FALSE(spec.is_explicitly_free());
|
||||
CHECK_FALSE(spec.is_exact());
|
||||
}
|
||||
|
||||
TEST_CASE("^.*(accelerate|mkl)$")
|
||||
{
|
||||
auto spec = RegexSpec::parse("^.*(accelerate|mkl)$").value();
|
||||
|
||||
CHECK(spec.contains("accelerate"));
|
||||
CHECK(spec.contains("mkl"));
|
||||
CHECK_FALSE(spec.contains(""));
|
||||
CHECK_FALSE(spec.contains("openblas"));
|
||||
|
||||
CHECK_EQ(spec.str(), "^.*(accelerate|mkl)$");
|
||||
CHECK_FALSE(spec.is_explicitly_free());
|
||||
CHECK_FALSE(spec.is_exact());
|
||||
}
|
||||
}
|
|
@ -715,9 +715,33 @@ namespace mambapy
|
|||
.def("__copy__", ©<GlobSpec>)
|
||||
.def("__deepcopy__", &deepcopy<GlobSpec>, py::arg("memo"));
|
||||
|
||||
py::class_<RegexSpec>(m, "RegexSpec")
|
||||
.def_readonly_static("free_pattern", &RegexSpec::free_pattern)
|
||||
.def_readonly_static("pattern_start", &RegexSpec::pattern_start)
|
||||
.def_readonly_static("pattern_end", &RegexSpec::pattern_end)
|
||||
.def_static("parse", &RegexSpec::parse)
|
||||
.def(py::init<>())
|
||||
.def("contains", &RegexSpec::contains)
|
||||
.def("is_explicitly_free", &RegexSpec::is_explicitly_free)
|
||||
.def("is_exact", &RegexSpec::is_exact)
|
||||
.def("__str__", &RegexSpec::str)
|
||||
.def("__copy__", ©<RegexSpec>)
|
||||
.def("__deepcopy__", &deepcopy<RegexSpec>, py::arg("memo"));
|
||||
|
||||
py::class_<ChimeraStringSpec>(m, "ChimeraStringSpec")
|
||||
.def_static("parse", &ChimeraStringSpec::parse)
|
||||
.def(py::init<>())
|
||||
.def("contains", &ChimeraStringSpec::contains)
|
||||
.def("is_explicitly_free", &ChimeraStringSpec::is_explicitly_free)
|
||||
.def("is_exact", &ChimeraStringSpec::is_exact)
|
||||
.def("is_glob", &ChimeraStringSpec::is_glob)
|
||||
.def("__str__", &ChimeraStringSpec::str)
|
||||
.def("__copy__", ©<ChimeraStringSpec>)
|
||||
.def("__deepcopy__", &deepcopy<ChimeraStringSpec>, py::arg("memo"));
|
||||
|
||||
py::class_<MatchSpec>(m, "MatchSpec")
|
||||
.def_property_readonly_static("NameSpec", &py::type::of<GlobSpec>)
|
||||
.def_property_readonly_static("BuildStringSpec", &py::type::of<GlobSpec>)
|
||||
.def_property_readonly_static("NameSpec", &py::type::of<MatchSpec::NameSpec>)
|
||||
.def_property_readonly_static("BuildStringSpec", &py::type::of<MatchSpec::BuildStringSpec>)
|
||||
.def_readonly_static("url_md5_sep", &MatchSpec::url_md5_sep)
|
||||
.def_readonly_static("prefered_list_open", &MatchSpec::prefered_list_open)
|
||||
.def_readonly_static("prefered_list_close", &MatchSpec::prefered_list_close)
|
||||
|
|
|
@ -812,7 +812,7 @@ def test_PackageInfo_V2Migrator():
|
|||
|
||||
def test_GlobSpec():
|
||||
GlobSpec = libmambapy.specs.GlobSpec
|
||||
spec = libmambapy.specs.GlobSpec("py*")
|
||||
spec = GlobSpec("py*")
|
||||
|
||||
assert GlobSpec().is_free()
|
||||
assert not spec.is_free()
|
||||
|
@ -830,6 +830,49 @@ def test_GlobSpec():
|
|||
assert other is not spec
|
||||
|
||||
|
||||
def test_RegexSpec():
|
||||
RegexSpec = libmambapy.specs.RegexSpec
|
||||
spec = RegexSpec.parse("^py.*$")
|
||||
|
||||
assert RegexSpec().is_explicitly_free()
|
||||
assert not spec.is_explicitly_free()
|
||||
|
||||
assert RegexSpec.parse("python").is_exact()
|
||||
assert not spec.is_exact()
|
||||
|
||||
assert spec.contains("python")
|
||||
|
||||
assert str(spec) == "^py.*$"
|
||||
|
||||
# Copy
|
||||
other = copy.deepcopy(spec)
|
||||
assert str(other) == str(spec)
|
||||
assert other is not spec
|
||||
|
||||
|
||||
def test_ChimeraStringSpec():
|
||||
ChimeraStringSpec = libmambapy.specs.ChimeraStringSpec
|
||||
spec = ChimeraStringSpec.parse("^py.*$")
|
||||
|
||||
assert ChimeraStringSpec().is_explicitly_free()
|
||||
assert not spec.is_explicitly_free()
|
||||
|
||||
assert ChimeraStringSpec().is_glob()
|
||||
assert not spec.is_glob()
|
||||
|
||||
assert ChimeraStringSpec.parse("python").is_exact()
|
||||
assert not spec.is_exact()
|
||||
|
||||
assert spec.contains("python")
|
||||
|
||||
assert str(spec) == "^py.*$"
|
||||
|
||||
# Copy
|
||||
other = copy.deepcopy(spec)
|
||||
assert str(other) == str(spec)
|
||||
assert other is not spec
|
||||
|
||||
|
||||
def test_MatchSpec():
|
||||
MatchSpec = libmambapy.specs.MatchSpec
|
||||
|
||||
|
|
Loading…
Reference in New Issue