diff --git a/personal_python_ast_optimizer/parser/config.py b/personal_python_ast_optimizer/parser/config.py index 6288550..a6c8185 100644 --- a/personal_python_ast_optimizer/parser/config.py +++ b/personal_python_ast_optimizer/parser/config.py @@ -109,7 +109,6 @@ def get_missing_tokens_iter(self) -> Iterator[tuple[str, str]]: class TokenTypesConfig(_Config): __slots__ = ( - "simplify_named_tuples", "skip_asserts", "skip_dangling_expressions", "skip_type_hints", @@ -123,13 +122,11 @@ def __init__( skip_type_hints: TypeHintsToSkip = TypeHintsToSkip.ALL_BUT_CLASS_VARS, skip_asserts: bool = False, skip_overload_functions: bool = False, - simplify_named_tuples: bool = False, ) -> None: self.skip_dangling_expressions: bool = skip_dangling_expressions self.skip_type_hints: TypeHintsToSkip = skip_type_hints self.skip_asserts: bool = skip_asserts self.skip_overload_functions: bool = skip_overload_functions - self.simplify_named_tuples: bool = simplify_named_tuples class OptimizationsConfig(_Config): @@ -142,6 +139,7 @@ class OptimizationsConfig(_Config): "collection_concat_to_unpack", "fold_constants", "assume_this_machine", + "simplify_named_tuples", ) def __init__( # noqa: PLR0913 @@ -158,6 +156,7 @@ def __init__( # noqa: PLR0913 collection_concat_to_unpack: bool = False, fold_constants: bool = False, assume_this_machine: bool = False, + simplify_named_tuples: bool = False, ) -> None: self.vars_to_fold: dict[ str, str | bytes | bool | int | float | complex | None | EllipsisType @@ -176,6 +175,7 @@ def __init__( # noqa: PLR0913 self.collection_concat_to_unpack: bool = collection_concat_to_unpack self.assume_this_machine: bool = assume_this_machine self.fold_constants: bool = fold_constants + self.simplify_named_tuples: bool = simplify_named_tuples @staticmethod def _format_enums_to_fold_as_dict( diff --git a/personal_python_ast_optimizer/parser/skipper.py b/personal_python_ast_optimizer/parser/skipper.py index 4f7538a..8b32c21 100644 --- a/personal_python_ast_optimizer/parser/skipper.py +++ b/personal_python_ast_optimizer/parser/skipper.py @@ -199,7 +199,7 @@ def visit_ClassDef(self, node: ast.ClassDef) -> ast.AST | None: skip_decorators(node, self.tokens_config.decorators_to_skip) if ( - self.token_types_config.simplify_named_tuples + self.optimizations_config.simplify_named_tuples and self._is_simple_named_tuple(node) ): self._simplified_named_tuple = True diff --git a/personal_python_ast_optimizer/regex/apply.py b/personal_python_ast_optimizer/regex/apply.py deleted file mode 100644 index 3d8a105..0000000 --- a/personal_python_ast_optimizer/regex/apply.py +++ /dev/null @@ -1,41 +0,0 @@ -import re -import warnings -from collections.abc import Iterable - -from personal_python_ast_optimizer.regex.classes import RegexReplacement - - -def apply_regex( - source: str, - regex_replacement: RegexReplacement | Iterable[RegexReplacement], - warning_id: str = "", -) -> str: - """Runs a series of regex on given source. - Passing warning_id enabled warnings when patterns are not found""" - if isinstance(regex_replacement, RegexReplacement): - regex_replacement = (regex_replacement,) - - for regex, replacement, flags, count in regex_replacement: - source, count_replaced = re.subn( - regex, replacement, source, flags=flags, count=count - ) - if count_replaced == 0 and warning_id != "": - warnings.warn(f"{warning_id}: Unused regex {regex}") - - return source - - -def apply_regex_to_file( - path: str, - regex_replacement: RegexReplacement | Iterable[RegexReplacement], - warning_id: str = "", - encoding: str = "utf-8", -): - """Wraps apply_regex with opening and writing to a file""" - with open(path, encoding=encoding) as fp: - source: str = fp.read() - - source = apply_regex(source, regex_replacement, warning_id) - - with open(path, "w", encoding=encoding) as fp: - fp.write(source) diff --git a/personal_python_ast_optimizer/regex/classes.py b/personal_python_ast_optimizer/regex/classes.py deleted file mode 100644 index f41458b..0000000 --- a/personal_python_ast_optimizer/regex/classes.py +++ /dev/null @@ -1,19 +0,0 @@ -from collections.abc import Iterator - - -class RegexReplacement: - """Represents arguments to a regex replacement call like re.sub""" - - __slots__ = ("pattern", "replacement", "flags", "count") - - def __init__( - self, pattern: str, replacement: str = "", flags: int = 0, count: int = 0 - ) -> None: - self.pattern: str = pattern - self.replacement: str = replacement - self.flags: int = flags - self.count: int = count - - def __iter__(self) -> Iterator: - for attr in self.__slots__: - yield getattr(self, attr) diff --git a/personal_python_ast_optimizer/regex/replace.py b/personal_python_ast_optimizer/regex/replace.py new file mode 100644 index 0000000..322d49a --- /dev/null +++ b/personal_python_ast_optimizer/regex/replace.py @@ -0,0 +1,69 @@ +import re +from collections.abc import Iterable, Iterator + + +class RegexReplacement: + """Represents arguments to a regex replacement call like re.sub""" + + __slots__ = ("pattern", "replacement", "flags", "count") + + def __init__( + self, pattern: str, replacement: str = "", flags: int = 0, count: int = 1 + ) -> None: + self.pattern: str = pattern + self.replacement: str = replacement + self.flags: int = flags + self.count: int = count + + def __iter__(self) -> Iterator: + yield self.pattern + yield self.replacement + yield self.flags + yield self.count + + +class RegexNoMatchException(Exception): + pass + + +def re_replace( + source: str, + regex_replacement: RegexReplacement | Iterable[RegexReplacement], + raise_if_not_applied: bool = False, +) -> str: + """Runs a series of regex on given source. + Passing warning_id enabled warnings when patterns are not found""" + if isinstance(regex_replacement, RegexReplacement): + regex_replacement = (regex_replacement,) + + unused_regex: list[str] = [] + + for regex, replacement, flags, count in regex_replacement: + source, count_replaced = re.subn( + regex, replacement, source, flags=flags, count=count + ) + if count_replaced == 0 and raise_if_not_applied: + unused_regex.append(regex) + + if unused_regex: + raise RegexNoMatchException( + f"Found {len(unused_regex)} unused regex: {unused_regex}" + ) + + return source + + +def re_replace_file( + path: str, + regex_replacement: RegexReplacement | Iterable[RegexReplacement], + encoding: str = "utf-8", + raise_if_not_applied: bool = False, +): + """Wraps apply_regex with opening and writing to a file""" + with open(path, encoding=encoding) as fp: + source: str = fp.read() + + source = re_replace(source, regex_replacement, raise_if_not_applied) + + with open(path, "w", encoding=encoding) as fp: + fp.write(source) diff --git a/tests/parser/test_collections.py b/tests/parser/test_collections.py index de3683f..8e01986 100644 --- a/tests/parser/test_collections.py +++ b/tests/parser/test_collections.py @@ -1,9 +1,6 @@ import pytest -from personal_python_ast_optimizer.parser.config import ( - OptimizationsConfig, - TokenTypesConfig, -) +from personal_python_ast_optimizer.parser.config import OptimizationsConfig from tests.utils import BeforeAndAfter, run_minifier_and_assert_correct @@ -73,7 +70,7 @@ def test_simplify_named_tuple(before: str, after: str): run_minifier_and_assert_correct( before_and_after, - token_types_config=TokenTypesConfig(simplify_named_tuples=True), + optimizations_config=OptimizationsConfig(simplify_named_tuples=True), ) @@ -93,7 +90,7 @@ class A(NamedTuple): with pytest.raises(ValueError): run_minifier_and_assert_correct( before_and_after, - token_types_config=TokenTypesConfig(simplify_named_tuples=True), + optimizations_config=OptimizationsConfig(simplify_named_tuples=True), ) diff --git a/tests/regex/test_apply_regex.py b/tests/regex/test_apply_regex.py new file mode 100644 index 0000000..87a84ab --- /dev/null +++ b/tests/regex/test_apply_regex.py @@ -0,0 +1,37 @@ +import pytest + +from personal_python_ast_optimizer.regex.replace import ( + RegexNoMatchException, + RegexReplacement, + re_replace, +) + +_re_cases = [ + ( + RegexReplacement("series", "wonder"), + "Some series of things. A real series.", + "Some wonder of things. A real series.", + ), + ( + RegexReplacement("series", "wonder", count=0), + "Some series of things. A real series.", + "Some wonder of things. A real wonder.", + ), + ( + RegexReplacement("series", "wonder"), + "Fox", + "Fox", + ), +] + + +@pytest.mark.parametrize(("replacement", "source", "expected_output"), _re_cases) +def test_re_replace(replacement: RegexReplacement, source: str, expected_output: str): + assert re_replace(source, replacement) == expected_output + + +def test_re_replace_raises(): + with pytest.raises(RegexNoMatchException): + re_replace( + "Fox", RegexReplacement("series", "wonder"), raise_if_not_applied=True + ) diff --git a/version.txt b/version.txt index a3fcc71..ae9a76b 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -7.1.0 +8.0.0