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/authentication_info.cpp
|
||||||
${LIBMAMBA_SOURCE_DIR}/specs/build_number_spec.cpp
|
${LIBMAMBA_SOURCE_DIR}/specs/build_number_spec.cpp
|
||||||
${LIBMAMBA_SOURCE_DIR}/specs/channel.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/conda_url.cpp
|
||||||
${LIBMAMBA_SOURCE_DIR}/specs/glob_spec.cpp
|
${LIBMAMBA_SOURCE_DIR}/specs/glob_spec.cpp
|
||||||
${LIBMAMBA_SOURCE_DIR}/specs/match_spec.cpp
|
${LIBMAMBA_SOURCE_DIR}/specs/match_spec.cpp
|
||||||
${LIBMAMBA_SOURCE_DIR}/specs/package_info.cpp
|
${LIBMAMBA_SOURCE_DIR}/specs/package_info.cpp
|
||||||
${LIBMAMBA_SOURCE_DIR}/specs/platform.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/repo_data.cpp
|
||||||
${LIBMAMBA_SOURCE_DIR}/specs/unresolved_channel.cpp
|
${LIBMAMBA_SOURCE_DIR}/specs/unresolved_channel.cpp
|
||||||
${LIBMAMBA_SOURCE_DIR}/specs/version.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 <fmt/core.h>
|
||||||
|
|
||||||
#include "mamba/specs/build_number_spec.hpp"
|
#include "mamba/specs/build_number_spec.hpp"
|
||||||
|
#include "mamba/specs/chimera_string_spec.hpp"
|
||||||
#include "mamba/specs/error.hpp"
|
#include "mamba/specs/error.hpp"
|
||||||
#include "mamba/specs/glob_spec.hpp"
|
#include "mamba/specs/glob_spec.hpp"
|
||||||
#include "mamba/specs/unresolved_channel.hpp"
|
#include "mamba/specs/unresolved_channel.hpp"
|
||||||
|
@ -31,7 +32,7 @@ namespace mamba::specs
|
||||||
public:
|
public:
|
||||||
|
|
||||||
using NameSpec = GlobSpec;
|
using NameSpec = GlobSpec;
|
||||||
using BuildStringSpec = GlobSpec;
|
using BuildStringSpec = ChimeraStringSpec;
|
||||||
using platform_set = typename UnresolvedChannel::platform_set;
|
using platform_set = typename UnresolvedChannel::platform_set;
|
||||||
using platform_set_const_ref = std::reference_wrapper<const 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 = 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())
|
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;
|
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(
|
ms.set_build_number(
|
||||||
specs::BuildNumberSpec(specs::BuildNumberPredicate::make_equal_to(s.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 TT = std::remove_cv_t<std::remove_reference_t<T>>;
|
||||||
using Build = decltype(std::invoke(&TT::build_string, std::forward<T>(e)));
|
using Build = decltype(std::invoke(&TT::build_string, std::forward<T>(e)));
|
||||||
Build bld = 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();
|
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
|
// Build string
|
||||||
auto [head, tail] = util::rsplit_once(strip_archive_extension(pkg), '-');
|
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())
|
if (!head.has_value())
|
||||||
{
|
{
|
||||||
|
@ -216,8 +224,9 @@ namespace mamba::specs
|
||||||
}
|
}
|
||||||
if ((attr == "build") || (attr == "build_string"))
|
if ((attr == "build") || (attr == "build_string"))
|
||||||
{
|
{
|
||||||
spec.set_build_string(MatchSpec::BuildStringSpec(std::string(val)));
|
return MatchSpec::BuildStringSpec::parse(std::string(val))
|
||||||
return {};
|
.transform([&](MatchSpec::BuildStringSpec&& bs)
|
||||||
|
{ spec.set_build_string(std::move(bs)); });
|
||||||
}
|
}
|
||||||
if (attr == "version")
|
if (attr == "version")
|
||||||
{
|
{
|
||||||
|
@ -575,14 +584,19 @@ namespace mamba::specs
|
||||||
auto maybe_ver = VersionSpec::parse(ver_str);
|
auto maybe_ver = VersionSpec::parse(ver_str);
|
||||||
if (!maybe_ver)
|
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();
|
out.m_version = std::move(maybe_ver).value();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bld_str.empty())
|
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;
|
return out;
|
||||||
|
@ -895,7 +909,7 @@ namespace mamba::specs
|
||||||
auto MatchSpec::conda_build_form() const -> std::string
|
auto MatchSpec::conda_build_form() const -> std::string
|
||||||
{
|
{
|
||||||
const bool has_version = !m_version.is_explicitly_free();
|
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_version)
|
||||||
{
|
{
|
||||||
if (has_build_str)
|
if (has_build_str)
|
||||||
|
@ -945,6 +959,7 @@ namespace mamba::specs
|
||||||
// Glob in names and build_string are fine
|
// Glob in names and build_string are fine
|
||||||
return (version().expression_size() <= 3) // includes op so e.g. ``>3,<4``
|
return (version().expression_size() <= 3) // includes op so e.g. ``>3,<4``
|
||||||
&& build_number().is_explicitly_free() //
|
&& build_number().is_explicitly_free() //
|
||||||
|
&& build_string().is_glob() //
|
||||||
&& !channel().has_value() //
|
&& !channel().has_value() //
|
||||||
&& filename().empty() //
|
&& filename().empty() //
|
||||||
&& !platforms().has_value() //
|
&& !platforms().has_value() //
|
||||||
|
@ -959,9 +974,9 @@ namespace mamba::specs
|
||||||
|
|
||||||
[[nodiscard]] auto MatchSpec::is_only_package_name() const -> bool
|
[[nodiscard]] auto MatchSpec::is_only_package_name() const -> bool
|
||||||
{
|
{
|
||||||
return name().is_exact() //
|
return name().is_exact() //
|
||||||
&& version().is_explicitly_free() //
|
&& version().is_explicitly_free() //
|
||||||
&& build_string().is_free() //
|
&& build_string().is_explicitly_free() //
|
||||||
&& is_simple();
|
&& 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_version = spec.version().expression_size() > 1;
|
||||||
const bool is_complex_build_string = !(
|
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.
|
// 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.
|
// of the url.
|
||||||
if (!is_complex_version && !is_complex_build_string)
|
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());
|
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();
|
ensure_bracket_open_or_comma();
|
||||||
out = fmt::format_to(out, "version={0}{1}{0}", MatchSpec::prefered_quote, ver);
|
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();
|
ensure_bracket_open_or_comma();
|
||||||
out = fmt::format_to(out, "build={0}{1}{0}", MatchSpec::prefered_quote, bs);
|
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_authentication_info.cpp
|
||||||
src/specs/test_build_number_spec.cpp
|
src/specs/test_build_number_spec.cpp
|
||||||
src/specs/test_channel.cpp
|
src/specs/test_channel.cpp
|
||||||
|
src/specs/test_chimera_string_spec.cpp
|
||||||
src/specs/test_conda_url.cpp
|
src/specs/test_conda_url.cpp
|
||||||
src/specs/test_glob_spec.cpp
|
src/specs/test_glob_spec.cpp
|
||||||
src/specs/test_match_spec.cpp
|
src/specs/test_match_spec.cpp
|
||||||
src/specs/test_package_info.cpp
|
src/specs/test_package_info.cpp
|
||||||
src/specs/test_platform.cpp
|
src/specs/test_platform.cpp
|
||||||
|
src/specs/test_regex_spec.cpp
|
||||||
src/specs/test_repo_data.cpp
|
src/specs/test_repo_data.cpp
|
||||||
src/specs/test_unresolved_channel.cpp
|
src/specs/test_unresolved_channel.cpp
|
||||||
src/specs/test_version.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();
|
auto ms = MatchSpec::parse("").value();
|
||||||
CHECK(ms.name().is_free());
|
CHECK(ms.name().is_free());
|
||||||
CHECK(ms.version().is_explicitly_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(ms.build_number().is_explicitly_free());
|
||||||
CHECK_EQ(ms.str(), "*");
|
CHECK_EQ(ms.str(), "*");
|
||||||
}
|
}
|
||||||
|
@ -325,7 +325,7 @@ TEST_SUITE("specs::match_spec")
|
||||||
auto ms = MatchSpec::parse("libblas=0.15*").value();
|
auto ms = MatchSpec::parse("libblas=0.15*").value();
|
||||||
CHECK_EQ(ms.name().str(), "libblas");
|
CHECK_EQ(ms.name().str(), "libblas");
|
||||||
CHECK_EQ(ms.version().str(), "=0.15*");
|
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.str(), "libblas=0.15*");
|
||||||
CHECK_EQ(ms.conda_build_form(), "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();
|
auto ms = MatchSpec::parse("xtensor =0.15*").value();
|
||||||
CHECK_EQ(ms.name().str(), "xtensor");
|
CHECK_EQ(ms.name().str(), "xtensor");
|
||||||
CHECK_EQ(ms.version().str(), "=0.15*");
|
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.str(), "xtensor=0.15*");
|
||||||
CHECK_EQ(ms.conda_build_form(), "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();
|
auto ms = MatchSpec::parse("numpy=1.20").value();
|
||||||
CHECK_EQ(ms.name().str(), "numpy");
|
CHECK_EQ(ms.name().str(), "numpy");
|
||||||
CHECK_EQ(ms.version().str(), "=1.20");
|
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");
|
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.channel().value().str(), "conda-forge");
|
||||||
CHECK_EQ(ms.name().str(), "tzdata");
|
CHECK_EQ(ms.name().str(), "tzdata");
|
||||||
CHECK(ms.version().is_explicitly_free());
|
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");
|
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.channel().value().str(), "conda-forge[noarch]");
|
||||||
CHECK_EQ(ms.name().str(), "tzdata");
|
CHECK_EQ(ms.name().str(), "tzdata");
|
||||||
CHECK(ms.version().is_explicitly_free());
|
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");
|
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.channel().value().str(), "conda-forge[noarch]");
|
||||||
CHECK_EQ(ms.name().str(), "tzdata");
|
CHECK_EQ(ms.name().str(), "tzdata");
|
||||||
CHECK(ms.version().is_explicitly_free());
|
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");
|
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.channel().value().str(), "pkgs/main");
|
||||||
CHECK_EQ(ms.name().str(), "tzdata");
|
CHECK_EQ(ms.name().str(), "tzdata");
|
||||||
CHECK(ms.version().is_explicitly_free());
|
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");
|
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.channel().value().str(), "pkgs/main[noarch]");
|
||||||
CHECK_EQ(ms.name().str(), "tzdata");
|
CHECK_EQ(ms.name().str(), "tzdata");
|
||||||
CHECK(ms.version().is_explicitly_free());
|
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");
|
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.platforms().value().get(), MatchSpec::platform_set{ "noarch" });
|
||||||
CHECK_EQ(ms.name().str(), "tzdata");
|
CHECK_EQ(ms.name().str(), "tzdata");
|
||||||
CHECK(ms.version().is_explicitly_free());
|
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");
|
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.platforms().value().get(), MatchSpec::platform_set{ "mamba-37" });
|
||||||
CHECK_EQ(ms.name().str(), "tzdata");
|
CHECK_EQ(ms.name().str(), "tzdata");
|
||||||
CHECK(ms.version().is_explicitly_free());
|
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");
|
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.build_string().str(), "py36h4a561cd_0");
|
||||||
CHECK_EQ(ms.str(), "conda-canary[linux-64]::conda==4.3.21.0post699+1dab973=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")
|
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("__copy__", ©<GlobSpec>)
|
||||||
.def("__deepcopy__", &deepcopy<GlobSpec>, py::arg("memo"));
|
.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")
|
py::class_<MatchSpec>(m, "MatchSpec")
|
||||||
.def_property_readonly_static("NameSpec", &py::type::of<GlobSpec>)
|
.def_property_readonly_static("NameSpec", &py::type::of<MatchSpec::NameSpec>)
|
||||||
.def_property_readonly_static("BuildStringSpec", &py::type::of<GlobSpec>)
|
.def_property_readonly_static("BuildStringSpec", &py::type::of<MatchSpec::BuildStringSpec>)
|
||||||
.def_readonly_static("url_md5_sep", &MatchSpec::url_md5_sep)
|
.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_open", &MatchSpec::prefered_list_open)
|
||||||
.def_readonly_static("prefered_list_close", &MatchSpec::prefered_list_close)
|
.def_readonly_static("prefered_list_close", &MatchSpec::prefered_list_close)
|
||||||
|
|
|
@ -812,7 +812,7 @@ def test_PackageInfo_V2Migrator():
|
||||||
|
|
||||||
def test_GlobSpec():
|
def test_GlobSpec():
|
||||||
GlobSpec = libmambapy.specs.GlobSpec
|
GlobSpec = libmambapy.specs.GlobSpec
|
||||||
spec = libmambapy.specs.GlobSpec("py*")
|
spec = GlobSpec("py*")
|
||||||
|
|
||||||
assert GlobSpec().is_free()
|
assert GlobSpec().is_free()
|
||||||
assert not spec.is_free()
|
assert not spec.is_free()
|
||||||
|
@ -830,6 +830,49 @@ def test_GlobSpec():
|
||||||
assert other is not spec
|
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():
|
def test_MatchSpec():
|
||||||
MatchSpec = libmambapy.specs.MatchSpec
|
MatchSpec = libmambapy.specs.MatchSpec
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue