diff --git a/nepattern/base.py b/nepattern/base.py index d00df39..5128571 100644 --- a/nepattern/base.py +++ b/nepattern/base.py @@ -95,7 +95,6 @@ class RegexPattern(_RegexPattern[Match[str]]): def __init__(self, pattern: str | TPattern, alias: str | None = None): super().__init__(pattern, Match[str], alias=alias or "regex[:group]") - self.regex_pattern = re.compile(pattern) def match(self, input_: Any) -> Match[str]: if not isinstance(input_, str): @@ -104,7 +103,7 @@ def match(self, input_: Any) -> Match[str]: type=input_.__class__, target=input_, expected="str" ) ) - if mat := self.regex_pattern.match(input_): + if mat := (re.match(self.pattern, input_) or re.search(self.pattern, input_)): return mat raise MatchFailed( lang.require("nepattern", "error.content").format(target=input_, expected=self.pattern) diff --git a/nepattern/core.py b/nepattern/core.py index 95a3bea..dd05121 100644 --- a/nepattern/core.py +++ b/nepattern/core.py @@ -63,12 +63,12 @@ def regex_match(pattern: str | TPattern, alias: str | None = None): @pat.convert def _(self, x: str): - mat = re.match(pattern, x) + mat = re.match(self.pattern, x) or re.search(self.pattern, x) if not mat: raise MatchFailed( - lang.require("nepattern", "error.content").format(target=x, expected=pattern) + lang.require("nepattern", "error.content").format(target=x, expected=self.pattern) ) - return x + return mat[0] return pat @@ -88,10 +88,10 @@ def regex_convert( def _(self, x): if isinstance(x, origin): return x - mat = re.match(pattern, x) + mat = re.match(self.pattern, x) or re.search(self.pattern, x) if not mat: raise MatchFailed( - lang.require("nepattern", "error.content").format(target=x, expected=pattern) + lang.require("nepattern", "error.content").format(target=x, expected=self.pattern) ) return fn(mat) @@ -100,10 +100,10 @@ def _(self, x): @pat.convert def _(self, x: str): - mat = re.match(pattern, x) + mat = re.match(self.pattern, x) or re.search(self.pattern, x) if not mat: raise MatchFailed( - lang.require("nepattern", "error.content").format(target=x, expected=pattern) + lang.require("nepattern", "error.content").format(target=x, expected=self.pattern) ) return fn(mat) @@ -213,4 +213,26 @@ def __eq__(self, other): class _RegexPattern(Pattern[T]): def __init__(self, pattern: str | TPattern, origin: type[T], alias: str | None = None): super().__init__(origin, alias) - self.pattern = pattern + _pat = pattern if isinstance(pattern, str) else pattern.pattern + if _pat.startswith("^") or _pat.endswith("$"): + raise ValueError(lang.require("nepattern", "error.pattern_head_or_tail").format(target=pattern)) + if isinstance(pattern, str): + self.pattern = f"^{pattern}$" + else: + self.pattern = re.compile(f"^{pattern.pattern}$", pattern.flags) + + def prefixed(self): + new = self.copy() + if isinstance(self.pattern, str): + new.pattern = self.pattern[:-1] + else: # pragma: no cover + new.pattern = re.compile(self.pattern.pattern[:-1], self.pattern.flags) + return new + + def suffixed(self): + new = self.copy() + if isinstance(self.pattern, str): + new.pattern = self.pattern[1:] + else: # pragma: no cover + new.pattern = re.compile(self.pattern.pattern[1:], self.pattern.flags) + return new diff --git a/nepattern/i18n/.lang.schema.json b/nepattern/i18n/.lang.schema.json index 0c2715b..9e933e4 100644 --- a/nepattern/i18n/.lang.schema.json +++ b/nepattern/i18n/.lang.schema.json @@ -29,6 +29,11 @@ "title": "type", "description": "value of lang item type 'type'", "type": "string" + }, + "pattern_head_or_tail": { + "title": "pattern_head_or_tail", + "description": "value of lang item type 'pattern_head_or_tail'", + "type": "string" } } } diff --git a/nepattern/i18n/.template.json b/nepattern/i18n/.template.json index fe23cd8..635cd74 100644 --- a/nepattern/i18n/.template.json +++ b/nepattern/i18n/.template.json @@ -9,7 +9,8 @@ "subtype": "error", "types": [ "content", - "type" + "type", + "pattern_head_or_tail" ] } ] diff --git a/nepattern/i18n/en-US.json b/nepattern/i18n/en-US.json index ea2d2fd..b3242a9 100644 --- a/nepattern/i18n/en-US.json +++ b/nepattern/i18n/en-US.json @@ -4,7 +4,8 @@ "parse_reject": "validate {target} failed", "error": { "content": "parameter {target} is incorrect; expected {expected}", - "type": "type {type} of parameter {target} is incorrect; expected {expected}" + "type": "type {type} of parameter {target} is incorrect; expected {expected}", + "pattern_head_or_tail": "The head or tail of regular expression {target} is not allowed to use '^' or '$'" } } } \ No newline at end of file diff --git a/nepattern/i18n/zh-CN.json b/nepattern/i18n/zh-CN.json index b7ef48d..a0507af 100644 --- a/nepattern/i18n/zh-CN.json +++ b/nepattern/i18n/zh-CN.json @@ -3,7 +3,8 @@ "nepattern": { "error": { "content": "参数 {target!r} 不正确, 其应该符合 {expected!r}", - "type": "参数 {target!r} 的类型 {type} 不正确, 其应该是 {expected!r}" + "type": "参数 {target!r} 的类型 {type} 不正确, 其应该是 {expected!r}", + "pattern_head_or_tail": "不允许正则表达式 {target} 头尾部分使用 '^' 或 '$'" }, "parse_reject": "{target} 校验失败" } diff --git a/test.py b/test.py index 7b60648..412a3b5 100644 --- a/test.py +++ b/test.py @@ -1,5 +1,7 @@ from typing import Union +import pytest + from nepattern import * @@ -92,10 +94,8 @@ def test_result(): res2 = NUMBER.execute([]) assert res2.error() assert not res2.success - try: + with pytest.raises(RuntimeError): res2.value() - except RuntimeError as e: - print(e) def test_pattern_of(): @@ -128,11 +128,27 @@ def test_pattern_keep(): def test_pattern_regex(): """测试 Pattern 的正则匹配模式, 仅正则匹配""" + import re + pat3 = Pattern.regex_match("abc[A-Z]+123") assert pat3.execute("abcABC123").value() == "abcABC123" assert pat3.execute("abcAbc123").failed print(pat3) + with pytest.raises(ValueError): + Pattern.regex_match("^abc[A-Z]+123") + + pat3_1 = Pattern.regex_match(re.compile(r"abc[A-Z]+123")) + assert pat3_1.execute("abcABC123").value() == "abcABC123" + + pat3_2 = Pattern.regex_match("abc").prefixed() + assert pat3_2.execute("abc123").value() == "abc" + assert pat3_2.execute("123abc").failed + + pat3_3 = Pattern.regex_match("abc").suffixed() + assert pat3_3.execute("123abc").value() == "abc" + assert pat3_3.execute("abc123").failed + def test_pattern_regex_convert(): """测试 Pattern 的正则转换模式, 正则匹配成功后再进行类型转换""" @@ -245,10 +261,8 @@ def my_func(x: int) -> str: assert parser(complex, extra="ignore") == ANY - try: + with pytest.raises(TypeError): parser(complex, extra="reject") - except TypeError as e: - print(e) pat11_4 = parser(Annotated[int, lambda x: x < 10]) assert pat11_4.execute(11).failed @@ -389,15 +403,11 @@ def test_patterns(): assert not local_patterns().get("b") reset_local_patterns() - try: + with pytest.raises(ValueError): create_local_patterns("$temp") - except ValueError as e: - print(e) - try: + with pytest.raises(ValueError): switch_local_patterns("$temp") - except ValueError as e: - print(e) def test_rawstr(): @@ -414,10 +424,8 @@ def test_direct(): assert pat20_1.execute(123).value() == 123 assert pat20_1.execute("123").failed assert pat20_1.match(123) == 123 - try: + with pytest.raises(MatchFailed): pat20_1.match("123") - except MatchFailed as e: - print(e) pat21 = DirectTypePattern(int) assert pat21.execute(123).value() == 123 assert pat21.execute("123").failed