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
|
"""Tests for :meth:`RegexBuilder.from_regex` — the reverse parser."""
import re
import pytest
from edify import RegexBuilder
from edify.builder.reverse import UnsupportedReverseParseError
@pytest.mark.parametrize(
("source", "expected"),
[
("hello", "hello"),
("a", "a"),
(r"\d+", r"\d+"),
(r"\d*", r"\d*"),
(r"\d?", r"\d?"),
(r"\d{3}", r"\d{3}"),
(r"\d{2,5}", r"\d{2,5}"),
(r"\d{2,}", r"\d{2,}"),
(r"\w", r"\w"),
(r"\W", r"\W"),
(r"\s", r"\s"),
(r"\S", r"\S"),
(r"\D", r"\D"),
("[a-z]", "[a-z]"),
("[abc]", "[abc]"),
("^", "^"),
("$", "$"),
(r"\b", r"\b"),
(r"\B", r"\B"),
(".", "."),
("(?:foo)", "foo"),
("(abc)", "(abc)"),
("(?P<name>abc)", "(?P<name>abc)"),
("foo|bar", "(?:foo|bar)"),
("(?=x)y", "(?=x)y"),
("(?!x)y", "(?!x)y"),
("(?<=x)y", "(?<=x)y"),
("(?<!x)y", "(?<!x)y"),
],
)
def test_from_regex_translates_common_constructs_faithfully(source: str, expected: str):
builder = RegexBuilder.from_regex(source)
assert builder.to_regex_string() == expected
@pytest.mark.parametrize(
"source",
[
"hello",
r"\d+",
"^abc$",
r"[a-z]+",
"(?P<x>abc)",
"foo|bar",
r"(?=x)\w+",
],
)
def test_round_trip_compiled_regex_matches_the_same_inputs(source: str):
original = re.compile(source)
reversed_builder = RegexBuilder.from_regex(source)
reversed_compiled = reversed_builder.to_regex()
for candidate in ["", "a", "abc", "abc123", "xyz", "foo bar", "hi"]:
assert bool(original.search(candidate)) == bool(reversed_compiled.search(candidate))
def test_from_regex_raises_for_unsupported_construct():
with pytest.raises(UnsupportedReverseParseError):
RegexBuilder.from_regex(r"(a)\1")
def test_from_regex_raises_for_alternation_with_non_literal_branch():
with pytest.raises(UnsupportedReverseParseError):
RegexBuilder.from_regex(r"a|\d+")
def test_from_regex_raises_for_unsupported_class_member_shape():
with pytest.raises(UnsupportedReverseParseError):
RegexBuilder.from_regex(r"[\da-z_]")
def test_from_regex_lazy_quantifier_translates_via_lazy_variants():
builder = RegexBuilder.from_regex(r"a+?")
assert builder.to_regex_string() == "a+?"
def test_from_regex_lazy_between_translates_via_lazy_variant():
builder = RegexBuilder.from_regex(r"a{2,5}?")
assert builder.to_regex_string() == "a{2,5}?"
def test_from_regex_lazy_zero_or_more_translates_via_lazy_variant():
builder = RegexBuilder.from_regex(r"a*?")
assert builder.to_regex_string() == "a*?"
def test_from_regex_named_group_reproduces_the_name():
builder = RegexBuilder.from_regex("(?P<username>[a-z]+)")
emitted = builder.to_regex_string()
assert emitted == "(?P<username>[a-z]+)"
def test_unsupported_error_message_names_the_offending_construct():
with pytest.raises(UnsupportedReverseParseError) as excinfo:
RegexBuilder.from_regex(r"(a)\1")
assert "hand-write" in str(excinfo.value)
def test_from_regex_produces_a_regexbuilder_not_a_pattern():
builder = RegexBuilder.from_regex("abc")
assert isinstance(builder, RegexBuilder)
|