重构为函数式通用 Markdown 修改库
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from mdpolish import Modifier, ModifierContractError, Pipeline, RunStatus, regex_replace
|
||||
|
||||
|
||||
def test_factory_returns_modifier_and_expands_capture_groups() -> None:
|
||||
modifier = regex_replace(
|
||||
modifier_id="example.swap-date",
|
||||
version="1.0.0",
|
||||
pattern=r"(\d{4})-(\d{2})-(\d{2})",
|
||||
replacement=r"\3/\2/\1",
|
||||
applicability="处理 ISO 形状的虚构日期;不验证真实日历日期。",
|
||||
)
|
||||
|
||||
result = Pipeline([modifier]).transform("A 2026-08-26, B 2027-01-02")
|
||||
|
||||
assert isinstance(modifier, Modifier)
|
||||
assert result.status is RunStatus.SUCCESS
|
||||
assert result.output_markdown == "A 26/08/2026, B 02/01/2027"
|
||||
assert len(result.changes) == 2
|
||||
assert result.changes[0].before == "2026-08-26"
|
||||
assert result.changes[0].after == "26/08/2026"
|
||||
|
||||
|
||||
def test_flags_and_factory_parameters_are_recorded() -> None:
|
||||
modifier = regex_replace(
|
||||
modifier_id="example.case",
|
||||
version="2.0.0",
|
||||
pattern="token",
|
||||
replacement="value",
|
||||
flags=re.IGNORECASE | re.MULTILINE,
|
||||
)
|
||||
|
||||
result = Pipeline([modifier]).transform("TOKEN")
|
||||
|
||||
assert result.status is RunStatus.SUCCESS
|
||||
assert result.modifiers[0].modifier_id == modifier.modifier_id
|
||||
assert result.modifiers[0].parameters == modifier.parameters
|
||||
assert dict(modifier.parameters) == {
|
||||
"flags": int(re.IGNORECASE | re.MULTILINE),
|
||||
"pattern": "token",
|
||||
"replacement": "value",
|
||||
}
|
||||
|
||||
|
||||
def test_zero_match_and_no_op_replacement_are_stable() -> None:
|
||||
missing = Pipeline(
|
||||
[regex_replace(modifier_id="example.missing", version="1.0.0", pattern="missing", replacement="new")]
|
||||
).transform("text")
|
||||
no_op = Pipeline(
|
||||
[regex_replace(modifier_id="example.no-op", version="1.0.0", pattern="text", replacement="text")]
|
||||
).transform("text")
|
||||
|
||||
assert missing.output_markdown == "text"
|
||||
assert missing.changes == ()
|
||||
assert no_op.status is RunStatus.SUCCESS
|
||||
assert no_op.changes == ()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("pattern", [r"", r"^", r"a*", r"(?=a)"])
|
||||
def test_zero_length_matches_are_rejected(pattern: str) -> None:
|
||||
if pattern == r"(?=a)":
|
||||
modifier = regex_replace(
|
||||
modifier_id="example.zero",
|
||||
version="1.0.0",
|
||||
pattern=pattern,
|
||||
replacement="x",
|
||||
)
|
||||
result = Pipeline([modifier]).transform("a")
|
||||
assert result.status is RunStatus.FAILED
|
||||
assert result.partial_markdown == "a"
|
||||
assert result.changes == ()
|
||||
return
|
||||
|
||||
with pytest.raises(ModifierContractError, match="zero-length"):
|
||||
regex_replace(
|
||||
modifier_id="example.zero",
|
||||
version="1.0.0",
|
||||
pattern=pattern,
|
||||
replacement="x",
|
||||
)
|
||||
|
||||
|
||||
def test_invalid_pattern_flags_and_input_types_are_rejected() -> None:
|
||||
with pytest.raises(ModifierContractError, match="compile"):
|
||||
regex_replace(modifier_id="example.invalid", version="1.0.0", pattern="(", replacement="x")
|
||||
with pytest.raises(ModifierContractError, match="flags"):
|
||||
regex_replace(
|
||||
modifier_id="example.flags",
|
||||
version="1.0.0",
|
||||
pattern="x",
|
||||
replacement="y",
|
||||
flags=True,
|
||||
)
|
||||
|
||||
|
||||
def test_successful_output_is_idempotent() -> None:
|
||||
pipeline = Pipeline(
|
||||
[regex_replace(modifier_id="example.space", version="1.0.0", pattern=r" {2,}", replacement=" ")]
|
||||
)
|
||||
first = pipeline.transform("a b")
|
||||
assert first.output_markdown == "a b"
|
||||
assert first.output_markdown is not None
|
||||
assert pipeline.transform(first.output_markdown).changes == ()
|
||||
Reference in New Issue
Block a user