diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index d965892..3820dad 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -35,4 +35,4 @@ jobs: black --line-length=120 --check phone_gen - name: Test with pytest run: | - pytest tests + pytest -m "not phonenumbers" tests diff --git a/dev_tools/update.py b/dev_tools/update.py index df9c579..806ce2b 100644 --- a/dev_tools/update.py +++ b/dev_tools/update.py @@ -7,7 +7,7 @@ import requests -from dev_tools.patterns_generator import get_latest, parsing_version, main +from patterns_generator import get_latest, parsing_version, main ROOT_DIR: Final[Path] = Path(__file__).absolute().parent.parent logging.basicConfig(format="[%(asctime)s] %(levelname)s:%(message)s", level=logging.INFO) diff --git a/phone_gen/__init__.py b/phone_gen/__init__.py index 88187b2..7f68ea8 100644 --- a/phone_gen/__init__.py +++ b/phone_gen/__init__.py @@ -26,7 +26,6 @@ * libphonenumber https://github.com/google/libphonenumber """ -from ._generator import RegEx, PatternError from ._phone_number import clean_alt_patters, load_alt_patters, PhoneNumber, PhoneNumberNotFound try: @@ -40,6 +39,4 @@ "load_alt_patters", "PhoneNumber", "PhoneNumberNotFound", - "RegEx", - "PatternError", ] diff --git a/phone_gen/_generator.py b/phone_gen/_generator.py deleted file mode 100644 index 8f41235..0000000 --- a/phone_gen/_generator.py +++ /dev/null @@ -1,94 +0,0 @@ -import re -import string -import sys -from dataclasses import dataclass, field -from itertools import chain -from random import choice, randint -from typing import Any, Dict, Callable, Sequence, Union, Final - -try: - import re._parser as parse -except ImportError: # pragma: no cover - import sre_parse as parse - -kwargs = {"eq": True} -if sys.version_info >= (3, 11): - kwargs.update({"slots": True}) - - -class PatternError(Exception): - """Not found country""" - - -_CATEGORIES: Final[Dict[int, str]] = { - parse.CATEGORY_DIGIT: string.digits, - parse.CATEGORY_NOT_DIGIT: string.ascii_letters + string.punctuation, - parse.CATEGORY_SPACE: string.whitespace, - parse.CATEGORY_NOT_SPACE: string.printable.strip(), - parse.CATEGORY_WORD: string.ascii_letters + string.digits + "_", - parse.CATEGORY_NOT_WORD: "".join(set(string.printable).difference(string.ascii_letters + string.digits + "_")), -} - -_CASE: Final[Dict[int, Callable[[Any, Any], Any]]] = { - parse.LITERAL: lambda p, x: chr(x), - parse.NOT_LITERAL: lambda s, x: choice(string.printable.replace(chr(x), "")), - parse.AT: lambda p, x: "", - parse.IN: lambda p, x: p.in_state(x), - parse.ANY: lambda p, x: choice(string.printable.replace("\n", "")), - parse.RANGE: lambda p, x: [chr(i) for i in range(x[0], x[1] + 1)], - parse.CATEGORY: lambda p, x: _CATEGORIES[x], - parse.BRANCH: lambda p, x: "".join(p.state(i) for i in choice(x[1])), - parse.SUBPATTERN: lambda p, x: p.group(x), - parse.ASSERT: lambda p, x: "".join(p.state(i) for i in x[1]), - parse.ASSERT_NOT: lambda p, x: "", - parse.GROUPREF: lambda p, x: p.cache[x], - parse.MIN_REPEAT: lambda p, x: p.repeat(*x), - parse.MAX_REPEAT: lambda p, x: p.repeat(*x), - parse.NEGATE: lambda p, x: [False], -} - - -@dataclass(**kwargs) -class _Parser: - cache: Dict[str, Any] = field(default_factory=dict, init=False, repr=False) - - def repeat(self, start_range: int, end_range: int, value: str) -> str: - times = randint(start_range, min((end_range, 100))) - return "".join("".join(self.state(i) for i in value) for _ in range(times)) - - def group(self, value: Sequence[Any]) -> str: - result = "".join(self.state(i) for i in value[-1]) - if value[0]: - self.cache[value[0]] = result - return result - - def in_state(self, value: Any) -> Any: - candidates = list(chain(*(self.state(i) for i in value))) - if candidates[0] is False: - candidates = list(set(string.printable).difference(candidates[1:])) - return choice(candidates) - - def state(self, state: Any) -> Any: - opcode, value = state - return _CASE[opcode](self, value) - - def value(self, pattern: re.Pattern) -> str: - parsed = parse.parse(pattern.pattern) - result = "".join(self.state(state) for state in parsed) - self.cache.clear() - return result - - -@dataclass(**kwargs) -class RegEx: - pattern: Union[re.Pattern, str] = field(init=True, repr=True) - _parser: _Parser = field(default_factory=_Parser, init=False, repr=False) - - def __post_init__(self): - try: - self.pattern = re.compile(self.pattern) - except re.error as e: - raise PatternError(f"Invalid pattern: {self.pattern!r}") from e - - def generate(self) -> str: - return self._parser.value(self.pattern) diff --git a/phone_gen/_phone_number.py b/phone_gen/_phone_number.py index 54e3f5e..447a350 100644 --- a/phone_gen/_phone_number.py +++ b/phone_gen/_phone_number.py @@ -1,12 +1,14 @@ import random from dataclasses import dataclass, field from re import sub -from .patterns import PATTERNS from typing import Dict, Optional + +from string_gen import StringGen + from .alt_patterns import ALT_PATTERNS from .country_name import COUNTRY_NAME from .iso3 import ISO3 -from ._generator import RegEx +from .patterns import PATTERNS ALTERNATIVE_FILE: dict = {} @@ -60,11 +62,11 @@ def get_number(self, full: bool = True) -> str: return self.get_national(full) if random.randint(0, 1) else self.get_mobile(full) def get_mobile(self, full: bool = True) -> str: - number = RegEx(self._country.get("mobile", self._country["pattern"])).generate() + number = StringGen(self._country.get("mobile", self._country["pattern"])).render() return f"+{self._country['code']}{number}" if full else number def get_national(self, full: bool = True) -> str: - number = RegEx(self._country["pattern"]).generate() + number = StringGen(self._country["pattern"]).render() # Could not find problem if number.startswith("49") and self._country["code"] == "49": # pragma: no cover return self.get_national(full) diff --git a/phone_gen/patterns.py b/phone_gen/patterns.py index 0c3ef4d..181fc68 100644 --- a/phone_gen/patterns.py +++ b/phone_gen/patterns.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- """ -Auto-generated file 2024-07-11 15:15:55 UTC (v3) -Resource: https://github.com/google/libphonenumber v8.13.40 +Auto-generated file 2024-07-26 17:24:22 UTC (v3) +Resource: https://github.com/google/libphonenumber v8.13.42 """ PATTERNS = { - "info": "libphonenumber v8.13.40", + "info": "libphonenumber v8.13.42", "data": { "AC": {"code": "247", "pattern": "6[2-467]\\d{3}", "mobile": "4\\d{4}"}, "AD": {"code": "376", "pattern": "[78]\\d{5}", "mobile": "690\\d{6}|[356]\\d{5}"}, @@ -390,7 +390,7 @@ "GY": { "code": "592", "pattern": "(?:2(?:1[6-9]|2[0-35-9]|3[1-4]|5[3-9]|6\\d|7[0-79])|3(?:2[25-9]|3\\d)|4(?:4[0-24]|5[56])|50[0-6]|77[1-57])\\d{4}", - "mobile": "(?:510|6\\d\\d|7(?:[0135]\\d|2[0-8]|4[0-24-9]))\\d{4}", + "mobile": "510\\d{4}|(?:6\\d|7[0-5])\\d{5}", }, "HK": { "code": "852", @@ -426,7 +426,7 @@ "IL": { "code": "972", "pattern": "153\\d{8,9}|29[1-9]\\d{5}|(?:2[0-8]|[3489]\\d)\\d{6}", - "mobile": "55410\\d{4}|5(?:(?:[02][02-9]|[149][2-9]|[36]\\d|8[3-7])\\d|5(?:01|2\\d|3[0-3]|4[34]|5[0-25689]|6[6-8]|7[0-267]|8[7-9]|9[1-9]))\\d{5}", + "mobile": "55(?:410|57[0-289])\\d{4}|5(?:(?:[02][02-9]|[149][2-9]|[36]\\d|8[3-7])\\d|5(?:01|2\\d|3[0-3]|4[34]|5[0-25689]|6[6-8]|7[0-267]|8[7-9]|9[1-9]))\\d{5}", }, "IM": { "code": "44", @@ -533,7 +533,7 @@ "LA": { "code": "856", "pattern": "(?:2[13]|[35-7][14]|41|8[1468])\\d{6}", - "mobile": "(?:20(?:[23579]\\d|88)|30(?:2\\d|4))\\d{6}", + "mobile": "208[78]\\d{6}|(?:20[23579]|30[24])\\d{7}", }, "LB": { "code": "961", @@ -672,7 +672,7 @@ }, "MY": { "code": "60", - "pattern": "(?:3(?:2[0-36-9]|3[0-368]|4[0-278]|5[0-24-8]|6[0-467]|7[1246-9]|8\\d|9[0-57])\\d|4(?:2[0-689]|[3-79]\\d|8[1-35689])|5(?:2[0-589]|[3468]\\d|5[0-489]|7[1-9]|9[23])|6(?:2[2-9]|3[1357-9]|[46]\\d|5[0-6]|7[0-35-9]|85|9[015-8])|7(?:[2579]\\d|3[03-68]|4[0-8]|6[5-9]|8[0-35-9])|8(?:[24][2-8]|3[2-5]|5[2-7]|6[2-589]|7[2-578]|[89][2-9])|9(?:0[57]|13|[25-7]\\d|[3489][0-8]))\\d{5}", + "pattern": "4270\\d{4}|(?:3(?:2[0-36-9]|3[0-368]|4[0-278]|5[0-24-8]|6[0-467]|7[1246-9]|8\\d|9[0-57])\\d|4(?:2[0-689]|[3-79]\\d|8[1-35689])|5(?:2[0-589]|[3468]\\d|5[0-489]|7[1-9]|9[23])|6(?:2[2-9]|3[1357-9]|[46]\\d|5[0-6]|7[0-35-9]|85|9[015-8])|7(?:[2579]\\d|3[03-68]|4[0-8]|6[5-9]|8[0-35-9])|8(?:[24][2-8]|3[2-5]|5[2-7]|6[2-589]|7[2-578]|[89][2-9])|9(?:0[57]|13|[25-7]\\d|[3489][0-8]))\\d{5}", "mobile": "1(?:1888[689]|4400|8(?:47|8[27])[0-4])\\d{4}|1(?:0(?:[23568]\\d|4[0-6]|7[016-9]|9[0-8])|1(?:[1-5]\\d\\d|6(?:0[5-9]|[1-9]\\d)|7(?:[0-4]\\d|5[0-7]))|(?:[269]\\d|[37][1-9]|4[235-9])\\d|5(?:31|9\\d\\d)|8(?:1[23]|[236]\\d|4[06]|5(?:46|[7-9])|7[016-9]|8[01]|9[0-8]))\\d{5}", }, "MZ": {"code": "258", "pattern": "2(?:[1346]\\d|5[0-2]|[78][12]|93)\\d{5}", "mobile": "8[2-79]\\d{7}"}, @@ -828,7 +828,7 @@ "SG": { "code": "65", "pattern": "662[0-24-9]\\d{4}|6(?:[0-578]\\d|6[013-57-9]|9[0-35-9])\\d{5}", - "mobile": "8(?:09[0-6]|95[0-2])\\d{4}|(?:8(?:0[1-8]|[1-8]\\d|9[0-4])|9[0-8]\\d)\\d{5}", + "mobile": "8(?:09[0-68]|95[0-2])\\d{4}|(?:8(?:0[1-8]|[1-8]\\d|9[0-4])|9[0-8]\\d)\\d{5}", }, "SH": {"code": "290", "pattern": "2(?:[0-57-9]\\d|6[4-9])\\d\\d", "mobile": "[56]\\d{4}"}, "SI": { @@ -897,7 +897,7 @@ "TJ": { "code": "992", "pattern": "(?:3(?:1[3-5]|2[245]|3[12]|4[24-7]|5[25]|72)|4(?:46|74|87))\\d{6}", - "mobile": "(?:4(?:1[18]|4[02-479])|81[1-9])\\d{6}|(?:0[0-57-9]|1[017]|2[02]|[34]0|5[05]|7[01578]|8[078]|9\\d)\\d{7}", + "mobile": "(?:33[03-9]|4(?:1[18]|4[02-479])|81[1-9])\\d{6}|(?:0[0-57-9]|1[017]|2[02]|[34]0|5[05]|7[01578]|8[078]|9\\d)\\d{7}", }, "TK": {"code": "690", "pattern": "(?:2[2-4]|[34]\\d)\\d{2,5}", "mobile": "7[2-4]\\d{2,5}"}, "TL": {"code": "670", "pattern": "(?:2[1-5]|3[1-9]|4[1-4])\\d{5}", "mobile": "7[2-8]\\d{6}"}, @@ -976,8 +976,8 @@ }, "VI": { "code": "1", - "pattern": "340(?:2(?:0\\d|2[06-8]|4[49]|77)|3(?:32|44)|4(?:2[23]|44|7[34]|89)|5(?:1[34]|55)|6(?:2[56]|4[23]|77|9[023])|7(?:1[2-57-9]|2[57]|7\\d)|884|998)\\d{4}", - "mobile": "340(?:2(?:0\\d|2[06-8]|4[49]|77)|3(?:32|44)|4(?:2[23]|44|7[34]|89)|5(?:1[34]|55)|6(?:2[56]|4[23]|77|9[023])|7(?:1[2-57-9]|2[57]|7\\d)|884|998)\\d{4}", + "pattern": "340(?:2(?:0\\d|10|2[06-8]|4[49]|77)|3(?:32|44)|4(?:2[23]|44|7[34]|89)|5(?:1[34]|55)|6(?:2[56]|4[23]|77|9[023])|7(?:1[2-57-9]|2[57]|7\\d)|884|998)\\d{4}", + "mobile": "340(?:2(?:0\\d|10|2[06-8]|4[49]|77)|3(?:32|44)|4(?:2[23]|44|7[34]|89)|5(?:1[34]|55)|6(?:2[56]|4[23]|77|9[023])|7(?:1[2-57-9]|2[57]|7\\d)|884|998)\\d{4}", }, "VN": { "code": "84", diff --git a/setup.py b/setup.py index 9921447..a17db5d 100644 --- a/setup.py +++ b/setup.py @@ -18,6 +18,7 @@ long_description_content_type="text/markdown", use_scm_version={"write_to": "phone_gen/__version__.py"}, setup_requires=["setuptools_scm"], + install_requires=["string-gen>=0.1.0"], entry_points={ "console_scripts": ["phone-gen=phone_gen.cli:main"], }, diff --git a/tests/pytest.ini b/tests/pytest.ini index d383032..95db79b 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -1,6 +1,7 @@ [pytest] addopts = --cov=phone_gen tests/ +; --cov-fail-under=100 -l ; --cov-report html diff --git a/tests/test_generator.py b/tests/test_generator.py deleted file mode 100644 index b7530fd..0000000 --- a/tests/test_generator.py +++ /dev/null @@ -1,37 +0,0 @@ -import re -from typing import Tuple - -import pytest - -from phone_gen import RegEx, PatternError - -REGEX: Tuple[str, ...] = ( - r"\d{5}", - r"#[a-fA-F0-9]{3,6}", - r"[a-z0-9._%+-]+[a-z]@[a-z][a-z0-9-]+\.[a-z]{2,4}", - r"(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)", - r"^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])" - r"[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$", - r"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", - r"[-a-zA-Z0-9@:%_\+.~#?&\/=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&\/=]*)?", - r"^https?:\/\/[a-z0-9]{1,}\.[a-z]{2,3}$", - r"^([A-Z][a-z]{1,15}\n){4,}[^\s]\w{1,15}\n\W{3}$", -) - - -@pytest.mark.parametrize("regex", REGEX) -def test_string(regex: str): - value = RegEx(regex).generate() - assert re.match(regex, value) - - -@pytest.mark.parametrize("regex", REGEX) -def test_pattern(regex: str): - pattern = re.compile(regex) - value = RegEx(regex).generate() - assert pattern.match(value) - - -def test_error(): - with pytest.raises(PatternError): - RegEx(".**").generate()