56 constexpr glob_pattern(std::u32string_view str) : _tokens(parse(str)) {}
102 for (
auto const&
token : _tokens) {
103 r +=
token.u32string();
126 for (
auto const&
token : _tokens) {
127 r +=
token.debug_u32string();
156 if (_tokens.
empty() or not _tokens.
front().is_text()) {
159 auto r = _tokens.
front().u32string();
160 if (_tokens.
size() >= 2 and _tokens[1].is_any_directory()) {
194 [[nodiscard]] std::filesystem::path
base_path() const noexcept
197 if (
auto i = text.rfind(
'/'); i == std::u32string::npos) {
204 auto path = std::filesystem::path{
std::move(text)};
205 path.make_preferred();
214 [[nodiscard]]
constexpr bool matches(std::u32string_view str)
const noexcept
216 auto first = _tokens.
cbegin();
217 auto last = _tokens.
cend();
220 if (not matches_strip(first, last, str)) {
224 }
else if (first == last) {
231 return matches(first, last, str);
241 return matches(std::u32string_view{str});
249 [[nodiscard]]
constexpr bool matches(
char32_t const *str)
const noexcept
251 return matches(std::u32string_view{str});
259 [[nodiscard]]
constexpr bool matches(std::string_view str)
const noexcept
271 return matches(std::string_view{str});
279 [[nodiscard]]
constexpr bool matches(
char const *str)
const noexcept
281 return matches(std::string_view{str});
289 [[nodiscard]]
bool matches(std::filesystem::path
const& path)
const noexcept
291 return matches(path.generic_u32string());
295 enum class match_result_type { fail, success, unchecked };
306 constexpr token_type(token_type
const&)
noexcept =
default;
307 constexpr token_type(token_type&&) noexcept = default;
308 constexpr token_type& operator=(token_type const&) noexcept = default;
309 constexpr token_type& operator=(token_type&&) noexcept = default;
311 constexpr token_type(
text_type&& rhs) noexcept : _value(
std::move(rhs)) {}
312 constexpr token_type(character_class_type&& rhs) noexcept : _value(
std::move(rhs)) {}
313 constexpr token_type(alternation_type&& rhs) noexcept : _value(
std::move(rhs)) {}
314 constexpr token_type(any_character_type&& rhs) noexcept : _value(
std::move(rhs)) {}
315 constexpr token_type(any_text_type&& rhs) noexcept : _value(
std::move(rhs)) {}
316 constexpr token_type(any_directory_type&& rhs) noexcept : _value(
std::move(rhs)) {}
318 [[nodiscard]]
constexpr bool is_text() const noexcept
320 return std::holds_alternative<text_type>(_value);
323 [[nodiscard]]
constexpr bool is_any_directory() const noexcept
325 return std::holds_alternative<any_directory_type>(_value);
329 [[nodiscard]]
constexpr match_result_type strip(std::u32string_view& str)
const noexcept
331 constexpr bool Right = not Left;
333 if (
auto const text_ptr = std::get_if<text_type>(&_value)) {
334 if (Left and str.starts_with(*text_ptr)) {
335 str.remove_prefix(text_ptr->size());
336 return match_result_type::success;
338 }
else if (Right and str.ends_with(*text_ptr)) {
339 str.remove_suffix(text_ptr->size());
340 return match_result_type::success;
343 return match_result_type::fail;
346 }
else if (
auto const character_class_ptr = std::get_if<character_class_type>(&_value)) {
348 return match_result_type::fail;
350 auto const c = Left ? str.front() : str.back();
351 for (
auto const[first_char, last_char] : *character_class_ptr) {
352 if (c >= first_char and c <= last_char) {
353 if constexpr (Left) {
354 str.remove_prefix(1);
356 str.remove_suffix(1);
358 return match_result_type::success;
361 return match_result_type::fail;
363 }
else if (std::holds_alternative<any_character_type>(_value)) {
365 return match_result_type::fail;
367 if constexpr (Left) {
368 str.remove_prefix(1);
370 str.remove_suffix(1);
372 return match_result_type::success;
375 return match_result_type::unchecked;
379 [[nodiscard]]
constexpr match_result_type matches(std::u32string_view& str,
size_t iteration)
const noexcept
381 if (
auto const text_ptr = std::get_if<text_type>(&_value)) {
382 if (iteration != 0) {
383 return match_result_type::fail;
385 }
else if (str.starts_with(*text_ptr)) {
386 str.remove_prefix(text_ptr->size());
387 return match_result_type::success;
390 return match_result_type::fail;
393 }
else if (
auto const character_class_ptr = std::get_if<character_class_type>(&_value)) {
394 if (iteration != 0 or str.empty()) {
395 return match_result_type::fail;
397 auto const c = str.front();
398 for (
auto const[first_char, last_char] : *character_class_ptr) {
399 if (c >= first_char and c <= last_char) {
400 str.remove_prefix(1);
401 return match_result_type::success;
404 return match_result_type::fail;
407 }
else if (
auto const alternation_ptr = std::get_if<alternation_type>(&_value)) {
408 if (iteration >= alternation_ptr->size()) {
409 return match_result_type::fail;
410 }
else if (str.starts_with((*alternation_ptr)[iteration])) {
411 str.remove_prefix((*alternation_ptr)[iteration].size());
412 return match_result_type::success;
414 return match_result_type::unchecked;
417 }
else if (std::holds_alternative<any_character_type>(_value)) {
418 if (iteration != 0 or str.empty()) {
419 return match_result_type::fail;
421 str.remove_prefix(1);
422 return match_result_type::success;
425 }
else if (std::holds_alternative<any_text_type>(_value)) {
426 if (iteration > str.size() or iteration > str.find(
'/')) {
427 return match_result_type::fail;
429 str.remove_prefix(iteration);
430 return match_result_type::success;
433 }
else if (std::holds_alternative<any_directory_type>(_value)) {
434 if (str.empty() or str.front() !=
'/') {
435 return match_result_type::fail;
437 for (
auto i = 0_uz; i != std::u32string_view::npos; i = str.find(
'/', i + 1)) {
438 if (iteration-- == 0) {
439 str.remove_prefix(i + 1);
440 return match_result_type::success;
443 return match_result_type::fail;
455 if (
auto text_ptr = std::get_if<text_type>(&_value)) {
458 }
else if (
auto character_class_ptr = std::get_if<character_class_type>(&_value)) {
460 for (
auto const[first_char, last_char] : *character_class_ptr) {
461 if (first_char == last_char) {
471 }
else if (
auto alternation_ptr = std::get_if<alternation_type>(&_value)) {
473 for (
auto const& text : *alternation_ptr) {
481 }
else if (std::holds_alternative<any_character_type>(_value)) {
484 }
else if (std::holds_alternative<any_text_type>(_value)) {
487 }
else if (std::holds_alternative<any_directory_type>(_value)) {
497 [[nodiscard]]
constexpr std::u32string debug_u32string() const noexcept
501 if (
auto text_ptr = std::get_if<text_type>(&_value)) {
506 }
else if (
auto character_class_ptr = std::get_if<character_class_type>(&_value)) {
508 for (
auto const[first_char, last_char] : *character_class_ptr) {
509 if (first_char == last_char) {
519 }
else if (
auto alternation_ptr = std::get_if<alternation_type>(&_value)) {
521 for (
auto const& text : *alternation_ptr) {
529 }
else if (std::holds_alternative<any_character_type>(_value)) {
532 }
else if (std::holds_alternative<any_text_type>(_value)) {
535 }
else if (std::holds_alternative<any_directory_type>(_value)) {
546 using variant_type = std::
547 variant<text_type, character_class_type, alternation_type, any_character_type, any_text_type, any_directory_type>;
553 using const_iterator = tokens_type::const_iterator;
557 [[nodiscard]]
constexpr static token_type make_text(token_type::text_type&& rhs)
noexcept
562 [[nodiscard]]
constexpr static token_type make_alternation(token_type::alternation_type&& rhs)
noexcept
567 [[nodiscard]]
constexpr static token_type make_character_class(token_type::character_class_type&& rhs)
noexcept
572 [[nodiscard]]
constexpr static token_type make_any_character() noexcept
574 return token_type{token_type::any_character_type{}};
577 [[nodiscard]]
constexpr static token_type make_any_text() noexcept
579 return token_type{token_type::any_text_type{}};
582 [[nodiscard]]
constexpr static token_type make_any_directory() noexcept
584 return token_type{token_type::any_directory_type{}};
587 [[nodiscard]]
constexpr static tokens_type parse(
auto first,
auto last)
589#define HI_GLOB_APPEND_TEXT() \
591 if (not text.empty()) { \
592 r.emplace_back(make_text(std::move(text))); \
597 enum class state_type { idle, star, slash, slash_star, slash_star_star, bracket, bracket_range, brace };
598 using enum state_type;
600 static_assert(std::is_same_v<std::decay_t<
decltype(*first)>,
char32_t>);
602 auto r = tokens_type{};
605 auto text = token_type::text_type{};
606 auto alternation = token_type::alternation_type{};
607 auto character_class = token_type::character_class_type{};
619 HI_GLOB_APPEND_TEXT();
620 r.push_back(make_any_character());
626 HI_GLOB_APPEND_TEXT();
630 HI_GLOB_APPEND_TEXT();
640 throw parse_error(
"Double ** is only allowed between slashes, like /**/.");
642 HI_GLOB_APPEND_TEXT();
643 r.push_back(make_any_text());
661 state = slash_star_star;
664 HI_GLOB_APPEND_TEXT();
665 r.push_back(make_any_text());
671 case slash_star_star:
673 HI_GLOB_APPEND_TEXT();
674 r.push_back(make_any_directory());
677 throw parse_error(
"Double ** is only allowed between slashes, like /**/.");
683 if (character_class.empty()) {
684 character_class.emplace_back(c, c);
686 state = bracket_range;
688 }
else if (c == U
']') {
689 r.push_back(make_character_class(
std::move(character_class)));
690 character_class.clear();
693 character_class.emplace_back(c, c);
699 throw parse_error(
"Double '--' is not allowed inside a character class, i.e. between '[' and ']'.");
700 }
else if (c == U
']') {
701 character_class.emplace_back(U
'-', U
'-');
702 r.push_back(make_character_class(
std::move(character_class)));
703 character_class.clear();
706 character_class.back().second = c;
713 if (not text.empty()) {
717 r.push_back(make_alternation(
std::move(alternation)));
720 }
else if (c == U
',') {
737 HI_GLOB_APPEND_TEXT();
741 HI_GLOB_APPEND_TEXT();
742 r.push_back(make_any_text());
747 HI_GLOB_APPEND_TEXT();
752 HI_GLOB_APPEND_TEXT();
753 r.push_back(make_any_text());
756 case slash_star_star:
757 HI_GLOB_APPEND_TEXT();
758 r.push_back(make_any_directory());
762 throw parse_error(
"Unclosed bracket '[' found in glob pattern.");
765 throw parse_error(
"Unclosed bracket '[' found in glob pattern.");
768 throw parse_error(
"Unclosed brace '{' found in glob pattern.");
773#undef HI_GLOB_APPEND_TEXT
776 [[nodiscard]]
constexpr static tokens_type parse(
auto&& range)
778 return parse(std::ranges::begin(range), std::ranges::end(range));
782 [[nodiscard]]
constexpr static bool
783 matches_strip(const_iterator& first, const_iterator& last, std::u32string_view& str)
noexcept
785 while (first != last) {
786 auto const it = Left ? first : last - 1;
787 switch (it->strip<Left>(str)) {
788 case match_result_type::fail:
790 case match_result_type::unchecked:
792 case match_result_type::success:
793 if constexpr (Left) {
806 [[nodiscard]]
constexpr static bool
807 matches_strip(const_iterator& first, const_iterator& last, std::u32string_view& str)
noexcept
809 return matches_strip<true>(first, last, str) and matches_strip<false>(first, last, str);
812 [[nodiscard]]
constexpr bool matches(const_iterator it, const_iterator last, std::u32string_view original)
const noexcept
814 hi_assert(it != last);
816 struct stack_element {
817 std::u32string_view str;
824 stack.emplace_back(original, 0);
826 auto [str, iteration] = stack.back();
828 switch (it->matches(str, iteration)) {
829 case match_result_type::success:
830 if (it + 1 == last) {
838 ++stack.back().iteration;
843 stack.emplace_back(str, 0);
848 case match_result_type::unchecked:
850 ++stack.back().iteration;
853 case match_result_type::fail:
855 if (stack.size() == 1) {
862 ++stack.back().iteration;