-
Notifications
You must be signed in to change notification settings - Fork 353
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Handle regex in build string (#3239)
- Loading branch information
1 parent
cad6793
commit 7942d3f
Showing
16 changed files
with
657 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
} |
Oops, something went wrong.