diff options
| author | natsuoto <[email protected]> | 2026-07-01 15:36:40 +0530 |
|---|---|---|
| committer | natsuoto <[email protected]> | 2026-07-01 15:36:40 +0530 |
| commit | a7974d090211955c394e0fecbc547f73b7786a97 (patch) | |
| tree | 890106bf873d10604d5d141444519a039441bd14 /tests/pattern | |
| parent | d46c40de921dd2ba62fbfcd0e2e4591eee8de2a9 (diff) | |
| download | edify-a7974d090211955c394e0fecbc547f73b7786a97.tar.xz edify-a7974d090211955c394e0fecbc547f73b7786a97.zip | |
feat: humre-style functional API — operators, constants, factories, matcher
Diffstat (limited to 'tests/pattern')
| -rw-r--r-- | tests/pattern/anchors.test.py | 27 | ||||
| -rw-r--r-- | tests/pattern/boundaries.test.py | 27 | ||||
| -rw-r--r-- | tests/pattern/classes.test.py | 58 | ||||
| -rw-r--r-- | tests/pattern/composition.test.py | 9 | ||||
| -rw-r--r-- | tests/pattern/factories/assertions.test.py | 30 | ||||
| -rw-r--r-- | tests/pattern/factories/groups.test.py | 71 | ||||
| -rw-r--r-- | tests/pattern/factories/quantifiers.test.py | 94 | ||||
| -rw-r--r-- | tests/pattern/factories/values.test.py | 67 | ||||
| -rw-r--r-- | tests/pattern/operators.test.py | 54 |
9 files changed, 433 insertions, 4 deletions
diff --git a/tests/pattern/anchors.test.py b/tests/pattern/anchors.test.py new file mode 100644 index 0000000..a42aca0 --- /dev/null +++ b/tests/pattern/anchors.test.py @@ -0,0 +1,27 @@ +"""Tests for the START and END module-level anchor constants.""" + +from edify import END, START, Pattern + + +def test_start_is_a_pattern(): + assert isinstance(START, Pattern) + + +def test_end_is_a_pattern(): + assert isinstance(END, Pattern) + + +def test_start_compiles_to_caret(): + assert START.to_regex_string() == "^" + + +def test_end_compiles_to_dollar_sign(): + assert END.to_regex_string() == "$" + + +def test_start_matches_start_of_input_element_from_the_fluent_chain(): + assert START._state == Pattern().start_of_input()._state + + +def test_end_matches_end_of_input_element_from_the_fluent_chain(): + assert END._state == Pattern().end_of_input()._state diff --git a/tests/pattern/boundaries.test.py b/tests/pattern/boundaries.test.py new file mode 100644 index 0000000..b391733 --- /dev/null +++ b/tests/pattern/boundaries.test.py @@ -0,0 +1,27 @@ +"""Tests for the WORD_BOUNDARY and NON_WORD_BOUNDARY module-level constants.""" + +from edify import NON_WORD_BOUNDARY, WORD_BOUNDARY, Pattern + + +def test_word_boundary_is_a_pattern(): + assert isinstance(WORD_BOUNDARY, Pattern) + + +def test_non_word_boundary_is_a_pattern(): + assert isinstance(NON_WORD_BOUNDARY, Pattern) + + +def test_word_boundary_compiles_to_backslash_b(): + assert WORD_BOUNDARY.to_regex_string() == "\\b" + + +def test_non_word_boundary_compiles_to_backslash_capital_b(): + assert NON_WORD_BOUNDARY.to_regex_string() == "\\B" + + +def test_word_boundary_matches_fluent_chain_output(): + assert WORD_BOUNDARY._state == Pattern().word_boundary()._state + + +def test_non_word_boundary_matches_fluent_chain_output(): + assert NON_WORD_BOUNDARY._state == Pattern().non_word_boundary()._state diff --git a/tests/pattern/classes.test.py b/tests/pattern/classes.test.py new file mode 100644 index 0000000..de6bfc0 --- /dev/null +++ b/tests/pattern/classes.test.py @@ -0,0 +1,58 @@ +"""Tests for the module-level character-class :class:`Pattern` constants.""" + +import pytest + +from edify import ( + ANY_CHAR, + CARRIAGE_RETURN, + DIGIT, + NEW_LINE, + NON_DIGIT, + NON_WHITESPACE, + NON_WORD, + NULL_BYTE, + TAB, + WHITESPACE, + WORD, + Pattern, +) + + + ("constant", "expected"), + [ + (ANY_CHAR, "."), + (WHITESPACE, "\\s"), + (NON_WHITESPACE, "\\S"), + (DIGIT, "\\d"), + (NON_DIGIT, "\\D"), + (WORD, "\\w"), + (NON_WORD, "\\W"), + (NEW_LINE, "\\n"), + (CARRIAGE_RETURN, "\\r"), + (TAB, "\\t"), + (NULL_BYTE, "\\0"), + ], +) +def test_character_class_constant_compiles_to_expected_regex(constant, expected): + assert constant.to_regex_string() == expected + + + "constant", + [ + ANY_CHAR, + WHITESPACE, + NON_WHITESPACE, + DIGIT, + NON_DIGIT, + WORD, + NON_WORD, + NEW_LINE, + CARRIAGE_RETURN, + TAB, + NULL_BYTE, + ], +) +def test_character_class_constant_is_a_pattern(constant): + assert isinstance(constant, Pattern) diff --git a/tests/pattern/composition.test.py b/tests/pattern/composition.test.py index 9cf0003..f58df57 100644 --- a/tests/pattern/composition.test.py +++ b/tests/pattern/composition.test.py @@ -12,14 +12,15 @@ def test_pattern_builds_the_same_element_tree_as_a_builder(): assert pattern._state == builder._state -def test_pattern_has_no_to_regex_terminal(): +def test_pattern_exposes_to_regex_string_terminal(): pattern = Pattern().digit() - assert not hasattr(pattern, "to_regex") + assert pattern.to_regex_string() == "\\d" -def test_pattern_has_no_to_regex_string_terminal(): +def test_pattern_exposes_to_regex_terminal(): pattern = Pattern().digit() - assert not hasattr(pattern, "to_regex_string") + compiled = pattern.to_regex() + assert compiled.pattern == "\\d" def test_pattern_supports_nested_use_composition(): diff --git a/tests/pattern/factories/assertions.test.py b/tests/pattern/factories/assertions.test.py new file mode 100644 index 0000000..ae521de --- /dev/null +++ b/tests/pattern/factories/assertions.test.py @@ -0,0 +1,30 @@ +"""Tests for the functional lookaround assertion factories.""" + +from edify import ( + DIGIT, + Pattern, + assert_ahead, + assert_behind, + assert_not_ahead, + assert_not_behind, +) + + +def test_assert_ahead_emits_positive_lookahead(): + assert assert_ahead(DIGIT).to_regex_string() == "(?=\\d)" + + +def test_assert_not_ahead_emits_negative_lookahead(): + assert assert_not_ahead(DIGIT).to_regex_string() == "(?!\\d)" + + +def test_assert_behind_emits_positive_lookbehind(): + assert assert_behind(DIGIT).to_regex_string() == "(?<=\\d)" + + +def test_assert_not_behind_emits_negative_lookbehind(): + assert assert_not_behind(DIGIT).to_regex_string() == "(?<!\\d)" + + +def test_assert_ahead_returns_a_pattern_instance(): + assert isinstance(assert_ahead(DIGIT), Pattern) diff --git a/tests/pattern/factories/groups.test.py b/tests/pattern/factories/groups.test.py new file mode 100644 index 0000000..a90ce28 --- /dev/null +++ b/tests/pattern/factories/groups.test.py @@ -0,0 +1,71 @@ +"""Tests for the functional grouping, capture, back-reference, and alternation factories.""" + +import pytest + +from edify import ( + DIGIT, + WORD, + Pattern, + any_of, + back_reference, + capture, + group, + named_back_reference, + named_capture, + string, +) +from edify.errors.input import MustBeAtLeastTwoOperandsError, MustBePositiveIntegerError + + +def test_group_wraps_operand_in_a_non_capturing_group(): + assert group(DIGIT + WORD).to_regex_string() == "(?:\\d\\w)" + + +def test_capture_wraps_operand_in_a_numbered_group(): + assert capture(DIGIT).to_regex_string() == "(\\d)" + + +def test_named_capture_wraps_operand_with_the_supplied_name(): + assert named_capture("year", DIGIT).to_regex_string() == "(?P<year>\\d)" + + +def test_back_reference_emits_a_numbered_backref(): + assert back_reference(1).to_regex_string() == "\\1" + + +def test_named_back_reference_emits_a_named_backref(): + assert named_back_reference("year").to_regex_string() == "(?P=year)" + + +def test_any_of_produces_alternation_between_multi_character_operands(): + assert any_of(string("http"), string("https")).to_regex_string() == "(?:http|https)" + + +def test_any_of_accepts_more_than_two_operands(): + assert any_of(string("cat"), string("dog"), string("fish")).to_regex_string() == ( + "(?:cat|dog|fish)" + ) + + +def test_group_returns_a_pattern_instance(): + assert isinstance(group(DIGIT), Pattern) + + +def test_any_of_rejects_a_single_operand(): + with pytest.raises(MustBeAtLeastTwoOperandsError): + any_of(DIGIT) + + +def test_any_of_rejects_zero_operands(): + with pytest.raises(MustBeAtLeastTwoOperandsError): + any_of() + + +def test_back_reference_rejects_zero_index(): + with pytest.raises(MustBePositiveIntegerError): + back_reference(0) + + +def test_back_reference_rejects_negative_index(): + with pytest.raises(MustBePositiveIntegerError): + back_reference(-1) diff --git a/tests/pattern/factories/quantifiers.test.py b/tests/pattern/factories/quantifiers.test.py new file mode 100644 index 0000000..8a6f34f --- /dev/null +++ b/tests/pattern/factories/quantifiers.test.py @@ -0,0 +1,94 @@ +"""Tests for the functional quantifier factories.""" + +import pytest + +from edify import ( + DIGIT, + WORD, + Pattern, + at_least, + between, + between_lazy, + char, + exactly, + one_or_more, + one_or_more_lazy, + optional, + string, + zero_or_more, + zero_or_more_lazy, +) +from edify.errors.input import ( + MustBeIntegerGreaterThanZeroError, + MustBeLessThanError, + MustBePositiveIntegerError, +) + + +def test_optional_wraps_a_single_element_operand_directly(): + assert optional(char("x")).to_regex_string() == "x?" + + +def test_zero_or_more_wraps_a_module_constant(): + assert zero_or_more(WORD).to_regex_string() == "\\w*" + + +def test_zero_or_more_lazy_emits_lazy_star(): + assert zero_or_more_lazy(WORD).to_regex_string() == "\\w*?" + + +def test_one_or_more_wraps_a_module_constant(): + assert one_or_more(DIGIT).to_regex_string() == "\\d+" + + +def test_one_or_more_lazy_emits_lazy_plus(): + assert one_or_more_lazy(DIGIT).to_regex_string() == "\\d+?" + + +def test_exactly_wraps_a_module_constant_without_extra_grouping(): + assert exactly(3, DIGIT).to_regex_string() == "\\d{3}" + + +def test_exactly_groups_a_multi_element_operand(): + combined = DIGIT + WORD + assert exactly(3, combined).to_regex_string() == "(?:\\d\\w){3}" + + +def test_exactly_groups_a_multi_character_string_operand(): + assert exactly(3, string("ab")).to_regex_string() == "(?:ab){3}" + + +def test_at_least_produces_open_ended_quantifier(): + assert at_least(2, DIGIT).to_regex_string() == "\\d{2,}" + + +def test_between_produces_greedy_bounded_quantifier(): + assert between(2, 4, DIGIT).to_regex_string() == "\\d{2,4}" + + +def test_between_lazy_produces_lazy_bounded_quantifier(): + assert between_lazy(2, 4, DIGIT).to_regex_string() == "\\d{2,4}?" + + +def test_optional_returns_a_pattern_instance(): + assert isinstance(optional(DIGIT), Pattern) + + +def test_exactly_rejects_zero_count(): + with pytest.raises(MustBePositiveIntegerError): + exactly(0, DIGIT) + + +def test_at_least_rejects_negative_count(): + with pytest.raises(MustBePositiveIntegerError): + at_least(-1, DIGIT) + + +def test_between_rejects_negative_lower_bound(): + with pytest.raises(MustBeIntegerGreaterThanZeroError): + between(-1, 5, DIGIT) + + +def test_between_rejects_upper_bound_less_than_or_equal_to_lower(): + with pytest.raises(MustBeLessThanError): + between(3, 3, DIGIT) diff --git a/tests/pattern/factories/values.test.py b/tests/pattern/factories/values.test.py new file mode 100644 index 0000000..69b04b7 --- /dev/null +++ b/tests/pattern/factories/values.test.py @@ -0,0 +1,67 @@ +"""Tests for the functional value factories.""" + +import pytest + +from edify import Pattern, char, chars, nonchars, nonrange, nonstring, range_of, string +from edify.errors.input import ( + MustBeAStringError, + MustBeOneCharacterError, + MustBeSingleCharacterError, + MustHaveASmallerValueError, +) + + +def test_string_produces_the_literal_value(): + assert string("hello").to_regex_string() == "hello" + + +def test_string_returns_a_pattern_instance(): + assert isinstance(string("x"), Pattern) + + +def test_string_escapes_regex_metacharacters(): + assert string("a.b").to_regex_string() == "a\\.b" + + +def test_char_produces_a_single_character_literal(): + assert char("a").to_regex_string() == "a" + + +def test_char_rejects_multi_character_input(): + with pytest.raises(MustBeSingleCharacterError): + char("ab") + + +def test_range_of_produces_an_ascii_range(): + assert range_of("a", "z").to_regex_string() == "[a-z]" + + +def test_range_of_rejects_descending_bounds(): + with pytest.raises(MustHaveASmallerValueError): + range_of("z", "a") + + +def test_chars_produces_an_inline_character_class(): + assert chars("abc").to_regex_string() == "[abc]" + + +def test_nonchars_produces_a_negated_character_class(): + assert nonchars("abc").to_regex_string() == "[^abc]" + + +def test_nonstring_produces_a_per_character_negation(): + assert nonstring("ab").to_regex_string() == "(?:[^a][^b])" + + +def test_nonrange_produces_a_negated_character_range(): + assert nonrange("a", "z").to_regex_string() == "[^a-z]" + + +def test_string_rejects_empty_input(): + with pytest.raises(MustBeOneCharacterError): + string("") + + +def test_string_rejects_non_string_input(): + with pytest.raises(MustBeAStringError): + string(42) diff --git a/tests/pattern/operators.test.py b/tests/pattern/operators.test.py new file mode 100644 index 0000000..74b672f --- /dev/null +++ b/tests/pattern/operators.test.py @@ -0,0 +1,54 @@ +"""Tests for the ``+`` and ``|`` operators on :class:`Pattern`.""" + +from edify import DIGIT, END, START, WORD, Pattern + + +def test_plus_concatenates_two_patterns(): + combined = Pattern().string("hello") + Pattern().string("world") + assert combined.to_regex_string() == "helloworld" + + +def test_plus_preserves_anchors_from_both_operands(): + combined = START + Pattern().exactly(4).digit() + END + assert combined.to_regex_string() == "^\\d{4}$" + + +def test_plus_returns_a_new_pattern_and_leaves_operands_untouched(): + left = Pattern().digit() + right = Pattern().word() + combined = left + right + assert combined is not left + assert combined is not right + assert left.to_regex_string() == "\\d" + assert right.to_regex_string() == "\\w" + + +def test_plus_using_module_constants_produces_expected_string(): + combined = DIGIT + WORD + assert combined.to_regex_string() == "\\d\\w" + + +def test_or_produces_alternation_between_two_patterns(): + combined = Pattern().string("http") | Pattern().string("https") + assert combined.to_regex_string() == "(?:http|https)" + + +def test_or_returns_a_new_pattern_and_leaves_operands_untouched(): + left = Pattern().string("cat") + right = Pattern().string("dog") + combined = left | right + assert combined is not left + assert combined is not right + assert left.to_regex_string() == "cat" + assert right.to_regex_string() == "dog" + + +def test_or_chaining_produces_three_way_alternation(): + combined = Pattern().string("cat") | Pattern().string("dog") | Pattern().string("fish") + assert combined.to_regex_string() == "(?:(?:cat|dog)|fish)" + + +def test_plus_composes_with_or_to_form_a_realistic_pattern(): + scheme = Pattern().string("http") | Pattern().string("https") + combined = scheme + Pattern().string("://") + assert combined.to_regex_string() == "(?:http|https)://" |
