55 constexpr glob_pattern(std::u32string_view str) : _tokens(parse(str)) {}
67 constexpr glob_pattern(
char32_t const *str) : glob_pattern(
std::u32string_view{str}) {}
92 glob_pattern(std::filesystem::path
const& path) : glob_pattern(path.generic_u32string()) {}
101 for (hilet&
token : _tokens) {
102 r +=
token.u32string();
125 for (hilet&
token : _tokens) {
126 r +=
token.debug_u32string();
155 if (_tokens.empty() or not _tokens.front().is_text()) {
158 auto r = _tokens.front().u32string();
159 if (_tokens.size() >= 2 and _tokens[1].is_any_directory()) {
193 [[nodiscard]] std::filesystem::path
base_path() const noexcept
196 if (
auto i = text.rfind(
'/'); i == std::u32string::npos) {
203 auto path = std::filesystem::path{
std::move(text)};
204 path.make_preferred();
213 [[nodiscard]]
constexpr bool matches(std::u32string_view str)
const noexcept
215 auto first = _tokens.cbegin();
216 auto last = _tokens.cend();
219 if (not matches_strip(first, last, str)) {
223 }
else if (first == last) {
230 return matches(first, last, str);
240 return matches(std::u32string_view{str});
248 [[nodiscard]]
constexpr bool matches(
char32_t const *str)
const noexcept
250 return matches(std::u32string_view{str});
258 [[nodiscard]]
constexpr bool matches(std::string_view str)
const noexcept
270 return matches(std::string_view{str});
278 [[nodiscard]]
constexpr bool matches(
char const *str)
const noexcept
280 return matches(std::string_view{str});
288 [[nodiscard]]
bool matches(std::filesystem::path
const& path)
const noexcept
290 return matches(path.generic_u32string());
294 enum class match_result_type { fail, success, unchecked };
305 constexpr token_type(token_type
const&)
noexcept =
default;
306 constexpr token_type(token_type&&) noexcept = default;
307 constexpr token_type& operator=(token_type const&) noexcept = default;
308 constexpr token_type& operator=(token_type&&) noexcept = default;
310 constexpr token_type(text_type&& rhs) noexcept : _value(
std::move(rhs)) {}
311 constexpr token_type(character_class_type&& rhs) noexcept : _value(
std::move(rhs)) {}
312 constexpr token_type(alternation_type&& rhs) noexcept : _value(
std::move(rhs)) {}
313 constexpr token_type(any_character_type&& rhs) noexcept : _value(
std::move(rhs)) {}
314 constexpr token_type(any_text_type&& rhs) noexcept : _value(
std::move(rhs)) {}
317 [[nodiscard]]
constexpr bool is_text() const noexcept
319 return std::holds_alternative<text_type>(_value);
322 [[nodiscard]]
constexpr bool is_any_directory() const noexcept
324 return std::holds_alternative<any_directory_type>(_value);
328 [[nodiscard]]
constexpr match_result_type strip(std::u32string_view& str)
const noexcept
330 constexpr bool Right = not Left;
332 if (hilet text_ptr = std::get_if<text_type>(&_value)) {
333 if (Left and str.starts_with(*text_ptr)) {
334 str.remove_prefix(text_ptr->size());
335 return match_result_type::success;
337 }
else if (Right and str.ends_with(*text_ptr)) {
338 str.remove_suffix(text_ptr->size());
339 return match_result_type::success;
342 return match_result_type::fail;
345 }
else if (hilet character_class_ptr = std::get_if<character_class_type>(&_value)) {
347 return match_result_type::fail;
349 hilet c = Left ? str.front() : str.back();
350 for (hilet[first_char, last_char] : *character_class_ptr) {
351 if (c >= first_char and c <= last_char) {
352 if constexpr (Left) {
353 str.remove_prefix(1);
355 str.remove_suffix(1);
357 return match_result_type::success;
360 return match_result_type::fail;
362 }
else if (std::holds_alternative<any_character_type>(_value)) {
364 return match_result_type::fail;
366 if constexpr (Left) {
367 str.remove_prefix(1);
369 str.remove_suffix(1);
371 return match_result_type::success;
374 return match_result_type::unchecked;
378 [[nodiscard]]
constexpr match_result_type matches(std::u32string_view& str,
size_t iteration)
const noexcept
380 if (hilet text_ptr = std::get_if<text_type>(&_value)) {
381 if (iteration != 0) {
382 return match_result_type::fail;
384 }
else if (str.starts_with(*text_ptr)) {
385 str.remove_prefix(text_ptr->size());
386 return match_result_type::success;
389 return match_result_type::fail;
392 }
else if (hilet character_class_ptr = std::get_if<character_class_type>(&_value)) {
393 if (iteration != 0 or str.empty()) {
394 return match_result_type::fail;
396 hilet c = str.front();
397 for (hilet[first_char, last_char] : *character_class_ptr) {
398 if (c >= first_char and c <= last_char) {
399 str.remove_prefix(1);
400 return match_result_type::success;
403 return match_result_type::fail;
406 }
else if (hilet alternation_ptr = std::get_if<alternation_type>(&_value)) {
407 if (iteration >= alternation_ptr->size()) {
408 return match_result_type::fail;
409 }
else if (str.starts_with((*alternation_ptr)[iteration])) {
410 str.remove_prefix((*alternation_ptr)[iteration].size());
411 return match_result_type::success;
413 return match_result_type::unchecked;
416 }
else if (std::holds_alternative<any_character_type>(_value)) {
417 if (iteration != 0 or str.empty()) {
418 return match_result_type::fail;
420 str.remove_prefix(1);
421 return match_result_type::success;
424 }
else if (std::holds_alternative<any_text_type>(_value)) {
425 if (iteration > str.size() or iteration > str.find(
'/')) {
426 return match_result_type::fail;
428 str.remove_prefix(iteration);
429 return match_result_type::success;
432 }
else if (std::holds_alternative<any_directory_type>(_value)) {
433 if (str.empty() or str.front() !=
'/') {
434 return match_result_type::fail;
436 for (
auto i = 0_uz; i != std::u32string_view::npos; i = str.find(
'/', i + 1)) {
437 if (iteration-- == 0) {
438 str.remove_prefix(i + 1);
439 return match_result_type::success;
442 return match_result_type::fail;
450 [[nodiscard]]
constexpr std::u32string
u32string() const noexcept
452 auto r = std::u32string{};
454 if (
auto text_ptr = std::get_if<text_type>(&_value)) {
457 }
else if (
auto character_class_ptr = std::get_if<character_class_type>(&_value)) {
459 for (hilet[first_char, last_char] : *character_class_ptr) {
460 if (first_char == last_char) {
470 }
else if (
auto alternation_ptr = std::get_if<alternation_type>(&_value)) {
472 for (hilet& text : *alternation_ptr) {
480 }
else if (std::holds_alternative<any_character_type>(_value)) {
483 }
else if (std::holds_alternative<any_text_type>(_value)) {
486 }
else if (std::holds_alternative<any_directory_type>(_value)) {
496 [[nodiscard]]
constexpr std::u32string
debug_u32string() const noexcept
498 auto r = std::u32string{};
500 if (
auto text_ptr = std::get_if<text_type>(&_value)) {
505 }
else if (
auto character_class_ptr = std::get_if<character_class_type>(&_value)) {
507 for (hilet[first_char, last_char] : *character_class_ptr) {
508 if (first_char == last_char) {
518 }
else if (
auto alternation_ptr = std::get_if<alternation_type>(&_value)) {
520 for (hilet& text : *alternation_ptr) {
528 }
else if (std::holds_alternative<any_character_type>(_value)) {
531 }
else if (std::holds_alternative<any_text_type>(_value)) {
534 }
else if (std::holds_alternative<any_directory_type>(_value)) {
545 using variant_type = std::
546 variant<text_type, character_class_type, alternation_type, any_character_type, any_text_type, any_directory_type>;
551 using tokens_type = std::vector<token_type>;
552 using const_iterator = tokens_type::const_iterator;
556 [[nodiscard]]
constexpr static token_type make_text(token_type::text_type&& rhs)
noexcept
561 [[nodiscard]]
constexpr static token_type make_alternation(token_type::alternation_type&& rhs)
noexcept
566 [[nodiscard]]
constexpr static token_type make_character_class(token_type::character_class_type&& rhs)
noexcept
571 [[nodiscard]]
constexpr static token_type make_any_character() noexcept
576 [[nodiscard]]
constexpr static token_type make_any_text() noexcept
581 [[nodiscard]]
constexpr static token_type make_any_directory() noexcept
586 [[nodiscard]]
constexpr static tokens_type parse(
auto first,
auto last)
588#define HI_GLOB_APPEND_TEXT() \
590 if (not text.empty()) { \
591 r.emplace_back(make_text(std::move(text))); \
596 enum class state_type { idle, star, slash, slash_star, slash_star_star, bracket, bracket_range, brace };
597 using enum state_type;
599 static_assert(std::is_same_v<std::decay_t<
decltype(*first)>,
char32_t>);
601 auto r = tokens_type{};
604 auto text = token_type::text_type{};
605 auto alternation = token_type::alternation_type{};
606 auto character_class = token_type::character_class_type{};
618 HI_GLOB_APPEND_TEXT();
619 r.push_back(make_any_character());
625 HI_GLOB_APPEND_TEXT();
629 HI_GLOB_APPEND_TEXT();
639 throw parse_error(
"Double ** is only allowed between slashes, like /**/.");
641 HI_GLOB_APPEND_TEXT();
642 r.push_back(make_any_text());
660 state = slash_star_star;
663 HI_GLOB_APPEND_TEXT();
664 r.push_back(make_any_text());
670 case slash_star_star:
672 HI_GLOB_APPEND_TEXT();
673 r.push_back(make_any_directory());
676 throw parse_error(
"Double ** is only allowed between slashes, like /**/.");
682 if (character_class.empty()) {
683 character_class.emplace_back(c, c);
685 state = bracket_range;
687 }
else if (c == U
']') {
688 r.push_back(make_character_class(
std::move(character_class)));
689 character_class.clear();
692 character_class.emplace_back(c, c);
698 throw parse_error(
"Double '--' is not allowed inside a character class, i.e. between '[' and ']'.");
699 }
else if (c == U
']') {
700 character_class.emplace_back(U
'-', U
'-');
701 r.push_back(make_character_class(
std::move(character_class)));
702 character_class.clear();
705 character_class.back().second = c;
712 if (not text.empty()) {
716 r.push_back(make_alternation(
std::move(alternation)));
719 }
else if (c == U
',') {
736 HI_GLOB_APPEND_TEXT();
740 HI_GLOB_APPEND_TEXT();
741 r.push_back(make_any_text());
746 HI_GLOB_APPEND_TEXT();
751 HI_GLOB_APPEND_TEXT();
752 r.push_back(make_any_text());
755 case slash_star_star:
756 HI_GLOB_APPEND_TEXT();
757 r.push_back(make_any_directory());
761 throw parse_error(
"Unclosed bracket '[' found in glob pattern.");
764 throw parse_error(
"Unclosed bracket '[' found in glob pattern.");
767 throw parse_error(
"Unclosed brace '{' found in glob pattern.");
772#undef HI_GLOB_APPEND_TEXT
775 [[nodiscard]]
constexpr static tokens_type parse(
auto&& range)
777 return parse(std::ranges::begin(range), std::ranges::end(range));
781 [[nodiscard]]
constexpr static bool
782 matches_strip(const_iterator& first, const_iterator & last, std::u32string_view& str)
noexcept
784 while (first != last) {
785 hilet it = Left ? first : last - 1;
786 switch (it->strip<Left>(str)) {
787 case match_result_type::fail:
789 case match_result_type::unchecked:
791 case match_result_type::success:
792 if constexpr (Left) {
805 [[nodiscard]]
constexpr static bool
806 matches_strip(const_iterator& first, const_iterator& last, std::u32string_view& str)
noexcept
808 return matches_strip<true>(first, last, str) and matches_strip<false>(first, last, str);
811 [[nodiscard]]
constexpr bool matches(const_iterator it, const_iterator last, std::u32string_view original)
const noexcept
813 hi_assert(it != last);
815 struct stack_element {
816 std::u32string_view str;
820 auto stack = std::vector<stack_element>{};
823 stack.emplace_back(original, 0);
825 auto [str, iteration] = stack.back();
827 switch (it->matches(str, iteration)) {
828 case match_result_type::success:
829 if (it + 1 == last) {
837 ++stack.back().iteration;
842 stack.emplace_back(str, 0);
847 case match_result_type::unchecked:
849 ++stack.back().iteration;
852 case match_result_type::fail:
854 if (stack.size() == 1) {
861 ++stack.back().iteration;