aboutsummaryrefslogtreecommitdiff
path: root/tests/result/match.test.py
blob: 7f273efae9f5b6f52f83392629528413b4c3caf7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
"""Tests for the :class:`Match` wrapper — attribute-access for named captures."""

import pytest

from edify import Pattern, RegexBuilder
from edify.result import Match, NamedCaptures


def _username_pattern():
    return RegexBuilder().string("@").named_capture("username").one_or_more().letter().end()


def _uppercase_username(match: Match) -> str:
    username = match.captures.username
    assert username is not None
    return username.upper()


def test_search_returns_a_wrapped_match():
    result = _username_pattern().search("hi @alice")
    assert isinstance(result, Match)


def test_search_returns_none_when_no_match():
    result = RegexBuilder().string("zzz").search("hi @alice")
    assert result is None


def test_match_attribute_access_returns_named_group_substring():
    result = _username_pattern().search("hi @alice")
    assert result is not None
    assert result.username == "alice"


def test_captures_namespace_returns_named_group_substring():
    result = _username_pattern().search("hi @bob")
    assert result is not None
    assert result.captures.username == "bob"


def test_captures_namespace_dir_lists_every_declared_group():
    result = _username_pattern().search("hi @carol")
    assert result is not None
    assert dir(result.captures) == ["username"]


def test_captures_namespace_attribute_error_names_valid_groups():
    result = _username_pattern().search("hi @dave")
    assert result is not None
    with pytest.raises(AttributeError, match="does not exist"):
        _ = result.captures.nonexistent


def test_match_delegates_re_match_methods_via_getattr():
    result = _username_pattern().search("hi @erin")
    assert result is not None
    assert result.group() == "@erin"
    assert result.group("username") == "erin"
    assert result.groupdict() == {"username": "erin"}
    assert result.span("username") == (4, 8)


def test_match_wrapped_property_returns_the_underlying_re_match():
    result = _username_pattern().search("hi @frank")
    assert result is not None
    wrapped = result.wrapped
    assert wrapped.group("username") == "frank"


def test_match_repr_contains_span_and_text():
    result = _username_pattern().search("hi @greta")
    assert result is not None
    assert "greta" in repr(result)


def test_finditer_yields_wrapped_matches():
    pattern = _username_pattern().to_regex()
    hits = list(pattern.finditer("hi @heidi and @ivan"))
    assert [item.captures.username for item in hits] == ["heidi", "ivan"]


def test_sub_callable_receives_wrapped_match():
    pattern = _username_pattern().to_regex()
    result = pattern.sub(_uppercase_username, "hi @jane")
    assert result == "hi JANE"


def test_subn_callable_receives_wrapped_match():
    pattern = _username_pattern().to_regex()
    result, count = pattern.subn(_uppercase_username, "hi @jane and @jim")
    assert result == "hi JANE and JIM"
    assert count == 2


def test_sub_still_accepts_a_plain_string_replacement():
    pattern = _username_pattern().to_regex()
    assert pattern.sub("[X]", "hi @jane") == "hi [X]"


def test_named_captures_instance_is_a_namespace():
    pattern = _username_pattern().to_regex()
    result = pattern.search("hi @karen")
    assert result is not None
    assert isinstance(result.captures, NamedCaptures)


def test_pattern_match_via_pattern_class_returns_wrapped_match():
    pattern = Pattern().named_capture("word").one_or_more().letter().end()
    result = pattern.match("hello")
    assert result is not None
    assert result.captures.word == "hello"


def test_fullmatch_returns_wrapped_match_when_full_input_matches():
    pattern = _username_pattern().to_regex()
    result = pattern.fullmatch("@leo")
    assert result is not None
    assert result.captures.username == "leo"


def test_fullmatch_returns_none_when_input_is_only_partial():
    pattern = _username_pattern().to_regex()
    assert pattern.fullmatch("hi @leo") is None


def test_match_forwarded_group_accepts_named_and_positional_selectors():
    result = _username_pattern().search("hi @meg")
    assert result is not None
    assert result.group() == "@meg"
    assert result.group(0) == "@meg"
    assert result.group(1) == "meg"


def test_match_forwarded_groups_returns_the_positional_group_tuple():
    result = _username_pattern().search("hi @nora")
    assert result is not None
    assert result.groups() == ("nora",)


def test_match_forwarded_groups_uses_default_for_unmatched_groups():
    optional_capture = (
        RegexBuilder()
        .start_of_input()
        .capture()
        .string("hi")
        .end()
        .optional()
        .capture()
        .string("bye")
        .end()
        .end_of_input()
    )
    result = optional_capture.match("hi")
    assert result is not None
    assert result.groups(default="missing") == ("hi", "missing")


def test_match_forwarded_groupdict_defaults_to_none_for_absent_named_groups():
    result = _username_pattern().search("hi @olive")
    assert result is not None
    assert result.groupdict() == {"username": "olive"}


def test_match_forwarded_groupdict_accepts_a_default_string():
    optional_named = (
        RegexBuilder()
        .start_of_input()
        .string("hi")
        .optional()
        .named_capture("nick")
        .one_or_more()
        .letter()
        .end()
        .end_of_input()
    )
    result = optional_named.match("hi")
    assert result is not None
    assert result.groupdict(default="none") == {"nick": "none"}


def test_match_forwarded_start_end_span_and_expand_return_the_expected_values():
    result = _username_pattern().search("hi @pete")
    assert result is not None
    assert result.start() == 3
    assert result.end() == 8
    assert result.span() == (3, 8)
    assert result.expand(r"\g<username>") == "pete"


def test_match_forwarded_re_pos_endpos_string_lastindex_lastgroup_reflect_the_compiled_pattern():
    pattern = _username_pattern().to_regex()
    result = pattern.search("hi @quinn there")
    assert result is not None
    assert result.re is pattern.compiled
    assert result.string == "hi @quinn there"
    assert result.pos == 0
    assert result.endpos > 0
    assert result.lastindex == 1
    assert result.lastgroup == "username"


def test_match_fallback_getattr_raises_attribute_error_on_unknown_name():
    result = _username_pattern().search("hi @rita")
    assert result is not None
    with pytest.raises(AttributeError, match="no attribute"):
        _ = result.nonexistent