Skip to content

Commit

Permalink
Handle regex in build string (#3239)
Browse files Browse the repository at this point in the history
  • Loading branch information
AntoinePrv authored Mar 20, 2024
1 parent cad6793 commit 7942d3f
Show file tree
Hide file tree
Showing 16 changed files with 657 additions and 30 deletions.
2 changes: 2 additions & 0 deletions libmamba/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
69 changes: 69 additions & 0 deletions libmamba/include/mamba/specs/chimera_string_spec.hpp
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion libmamba/include/mamba/specs/match_spec.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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>;
Expand Down
65 changes: 65 additions & 0 deletions libmamba/include/mamba/specs/regex_spec.hpp
Original file line number Diff line number Diff line change
@@ -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
4 changes: 3 additions & 1 deletion libmamba/src/core/transaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
4 changes: 3 additions & 1 deletion libmamba/src/solver/libsolv/helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
);
Expand Down
2 changes: 1 addition & 1 deletion libmamba/src/solver/problems_graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
146 changes: 146 additions & 0 deletions libmamba/src/specs/chimera_string_spec.cpp
Original file line number Diff line number Diff line change
@@ -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());
}
Loading

0 comments on commit 7942d3f

Please sign in to comment.