import pytest from bindery import rules as rules_module from bindery.rules import RuleError, apply_rules, breaks_to_paragraphs, compile_rules, normalize_rules def test_rules_apply_in_order_and_skip_disabled(): rules = normalize_rules( [ {"pattern": "cat", "replacement": "dog"}, {"pattern": "dog", "replacement": "wolf", "enabled": False}, {"pattern": r"(\w+) house", "replacement": r"\1 home", "flags": "I"}, ] ) assert apply_rules("The cat is in the HOUSE", compile_rules(rules)) == "The dog is in the home" assert apply_rules("a cat House", compile_rules(rules)) == "a dog home" def test_global_rules_run_before_story_rules(): global_rules = normalize_rules([{"pattern": "a", "replacement": "b"}]) story_rules = normalize_rules([{"pattern": "b", "replacement": "c"}]) assert apply_rules("a", compile_rules(global_rules, story_rules)) == "c" def test_flags_are_cleaned(): assert normalize_rules([{"pattern": "x", "flags": "smi"}])[0]["flags"] == "ims" @pytest.mark.parametrize( "rule, message", [ ({"pattern": ""}, "empty"), ({"pattern": "(open"}, "missing"), ({"pattern": "a", "replacement": r"\3"}, "group"), ({"pattern": "a", "flags": "q"}, "flag"), ], ) def test_bad_rules_name_the_rule(rule, message): with pytest.raises(RuleError) as info: normalize_rules([{"pattern": "fine"}, rule]) assert info.value.index == 1 assert message in str(info.value) @pytest.mark.parametrize( "rule", [ {"pattern": r"(a)(?Pb)", "replacement": r"\2 \g \1"}, {"pattern": r"(?i)(a)", "replacement": r"\1"}, {"pattern": "(a) # a comment", "replacement": r"\1", "flags": "x"}, {"pattern": "a", "replacement": r"x\\y\n"}, ], ) def test_good_replacements_pass(rule): assert normalize_rules([rule])[0]["replacement"] == rule["replacement"] @pytest.mark.parametrize("replacement", [r"\g", "\\", r"\q"]) def test_bad_replacements_fail_before_a_match(replacement): with pytest.raises(RuleError): normalize_rules([{"pattern": "(a)", "replacement": replacement}]) def test_a_slow_rule_stops_at_the_timeout(monkeypatch): monkeypatch.setattr(rules_module, "TIMEOUT", 0.1) steps = compile_rules(normalize_rules([{"pattern": "(a|aa)+$"}])) with pytest.raises(ValueError, match=r"takes longer than 0.1 seconds"): apply_rules("a" * 60 + "b", steps) @pytest.mark.parametrize( ("html", "expected"), [ ("
One
Two

Three
", "

One

Two

Three

"), ('

A
B

', '

A

B

'), ("
Loose

Block

After
", "

Loose

Block

After

"), ("

a
b

", "

a
b

"), ('
Text
 

', '

Text

'), ("

No breaks

", "

No breaks

"), ], ) def test_breaks_become_paragraphs(html, expected): assert breaks_to_paragraphs(html) == expected def test_paragraphs_come_before_the_rules(): rules = normalize_rules([{"pattern": "

", "replacement": '

'}]) steps = compile_rules(rules, paragraphs=True) assert apply_rules("

a
b
", steps) == '

a

b

' assert apply_rules("
a
b
", compile_rules(rules)) == "
a
b
"