python-minifier 2.11.2__tar.gz → 3.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_minifier-2.11.2/src/python_minifier.egg-info → python_minifier-3.0.0}/PKG-INFO +15 -3
- {python_minifier-2.11.2 → python_minifier-3.0.0}/README.md +1 -1
- {python_minifier-2.11.2 → python_minifier-3.0.0}/setup.py +1 -1
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/__init__.py +14 -9
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/__init__.pyi +8 -3
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/__main__.py +77 -15
- python_minifier-3.0.0/src/python_minifier/ast_annotation/__init__.py +82 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/ast_compare.py +12 -14
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/ast_compat.py +38 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/ast_printer.py +8 -5
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/expression_printer.py +14 -13
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/f_string.py +44 -45
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/ministring.py +3 -3
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/module_printer.py +19 -16
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/rename/__init__.py +1 -1
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/rename/bind_names.py +2 -1
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/rename/binding.py +39 -40
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/rename/mapper.py +66 -50
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/rename/rename_literals.py +17 -15
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/rename/renamer.py +26 -28
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/rename/resolve_names.py +8 -7
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/rename/util.py +7 -5
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/token_printer.py +12 -1
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/transforms/combine_imports.py +0 -1
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/transforms/constant_folding.py +16 -10
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/transforms/remove_annotations.py +18 -16
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/transforms/remove_annotations_options.py +3 -1
- python_minifier-3.0.0/src/python_minifier/transforms/remove_annotations_options.pyi +18 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/transforms/remove_asserts.py +1 -2
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/transforms/remove_debug.py +4 -3
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/transforms/remove_exception_brackets.py +12 -7
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/transforms/remove_explicit_return_none.py +5 -4
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/transforms/remove_literal_statements.py +5 -6
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/transforms/remove_object_base.py +2 -1
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/transforms/remove_pass.py +1 -2
- python_minifier-3.0.0/src/python_minifier/transforms/remove_posargs.py +12 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/transforms/suite_transformer.py +6 -5
- python_minifier-3.0.0/src/python_minifier/util.py +42 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0/src/python_minifier.egg-info}/PKG-INFO +15 -3
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier.egg-info/SOURCES.txt +7 -2
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_assignment_expressions.py +5 -2
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_await_fstring.py +3 -0
- python_minifier-3.0.0/test/test_best_effort_cli.py +179 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_bind_names.py +61 -7
- python_minifier-3.0.0/test/test_bind_names_namedexpr.py +259 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_bind_names_python2.py +1 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_bind_names_python312.py +10 -1
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_bind_names_python313.py +10 -1
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_bind_names_python33.py +1 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_combine_imports.py +17 -8
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_comprehension_rename.py +6 -1
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_decorator_expressions.py +5 -2
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_dict_expansion.py +3 -0
- python_minifier-3.0.0/test/test_folding.py +151 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_fstring.py +19 -16
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_generic_types.py +5 -2
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_hoist_literals.py +41 -11
- python_minifier-3.0.0/test/test_import.py +32 -0
- python_minifier-3.0.0/test/test_is_constant_node.py +53 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_iterable_unpacking.py +3 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_match.py +15 -10
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_match_rename.py +55 -46
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_name_generator.py +1 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_nonlocal.py +9 -6
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_posargs.py +5 -1
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_remove_annotations.py +96 -62
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_remove_assert.py +18 -6
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_remove_debug.py +29 -23
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_remove_exception_brackets.py +13 -2
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_remove_literal_statements.py +8 -2
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_remove_pass.py +18 -6
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_rename_builtins.py +9 -7
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_rename_locals.py +69 -55
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_slice.py +1 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_type_param_defaults.py +7 -1
- python_minifier-3.0.0/test/test_unicode_cli.py +170 -0
- python_minifier-3.0.0/test/test_utf8_encoding.py +114 -0
- python_minifier-2.11.2/src/python_minifier/transforms/remove_annotations_options.pyi +0 -17
- python_minifier-2.11.2/src/python_minifier/transforms/remove_posargs.py +0 -13
- python_minifier-2.11.2/src/python_minifier/util.py +0 -48
- python_minifier-2.11.2/test/test_folding.py +0 -136
- python_minifier-2.11.2/test/test_import.py +0 -30
- python_minifier-2.11.2/test/test_is_ast_node.py +0 -40
- {python_minifier-2.11.2 → python_minifier-3.0.0}/LICENSE +0 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/setup.cfg +0 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/py.typed +0 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/rename/name_generator.py +0 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier/transforms/__init__.py +0 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier.egg-info/dependency_links.txt +0 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier.egg-info/entry_points.txt +0 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier.egg-info/top_level.txt +0 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/src/python_minifier.egg-info/zip-safe +0 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_preserve_shebang.py +0 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_remove_explicit_return_none.py +0 -0
- {python_minifier-2.11.2 → python_minifier-3.0.0}/test/test_remove_object.py +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python_minifier
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: Transform Python source code into it's most compact representation
|
|
5
5
|
Home-page: https://github.com/dflook/python-minifier
|
|
6
6
|
Author: Daniel Flook
|
|
@@ -34,6 +34,18 @@ Classifier: Topic :: Software Development
|
|
|
34
34
|
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.14
|
|
35
35
|
Description-Content-Type: text/markdown
|
|
36
36
|
License-File: LICENSE
|
|
37
|
+
Dynamic: author
|
|
38
|
+
Dynamic: author-email
|
|
39
|
+
Dynamic: classifier
|
|
40
|
+
Dynamic: description
|
|
41
|
+
Dynamic: description-content-type
|
|
42
|
+
Dynamic: home-page
|
|
43
|
+
Dynamic: keywords
|
|
44
|
+
Dynamic: license
|
|
45
|
+
Dynamic: license-file
|
|
46
|
+
Dynamic: project-url
|
|
47
|
+
Dynamic: requires-python
|
|
48
|
+
Dynamic: summary
|
|
37
49
|
|
|
38
50
|
# Python Minifier
|
|
39
51
|
|
|
@@ -130,7 +142,7 @@ def handler(event,context):
|
|
|
130
142
|
|
|
131
143
|
## Why?
|
|
132
144
|
|
|
133
|
-
AWS Cloudformation templates may have AWS lambda function source code embedded in them, but only if the function is less
|
|
145
|
+
AWS Cloudformation templates may have AWS lambda function source code embedded in them, but only if the function is less
|
|
134
146
|
than 4KiB. I wrote this package so I could write python normally and still embed the module in a template.
|
|
135
147
|
|
|
136
148
|
## Installation
|
|
@@ -93,7 +93,7 @@ def handler(event,context):
|
|
|
93
93
|
|
|
94
94
|
## Why?
|
|
95
95
|
|
|
96
|
-
AWS Cloudformation templates may have AWS lambda function source code embedded in them, but only if the function is less
|
|
96
|
+
AWS Cloudformation templates may have AWS lambda function source code embedded in them, but only if the function is less
|
|
97
97
|
than 4KiB. I wrote this package so I could write python normally and still embed the module in a template.
|
|
98
98
|
|
|
99
99
|
## Installation
|
|
@@ -4,29 +4,30 @@ a 'minified' representation of the same source code.
|
|
|
4
4
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
import python_minifier.ast_compat as ast
|
|
8
7
|
import re
|
|
9
8
|
|
|
9
|
+
import python_minifier.ast_compat as ast
|
|
10
|
+
from python_minifier.ast_annotation import add_parent
|
|
11
|
+
|
|
10
12
|
from python_minifier.ast_compare import CompareError, compare_ast
|
|
11
13
|
from python_minifier.module_printer import ModulePrinter
|
|
12
14
|
from python_minifier.rename import (
|
|
13
|
-
|
|
14
|
-
bind_names,
|
|
15
|
-
resolve_names,
|
|
16
|
-
rename,
|
|
15
|
+
add_namespace,
|
|
17
16
|
allow_rename_globals,
|
|
18
17
|
allow_rename_locals,
|
|
19
|
-
|
|
18
|
+
bind_names,
|
|
19
|
+
rename,
|
|
20
|
+
rename_literals,
|
|
21
|
+
resolve_names
|
|
20
22
|
)
|
|
21
|
-
|
|
22
23
|
from python_minifier.transforms.combine_imports import CombineImports
|
|
23
24
|
from python_minifier.transforms.constant_folding import FoldConstants
|
|
24
25
|
from python_minifier.transforms.remove_annotations import RemoveAnnotations
|
|
25
26
|
from python_minifier.transforms.remove_annotations_options import RemoveAnnotationsOptions
|
|
26
27
|
from python_minifier.transforms.remove_asserts import RemoveAsserts
|
|
27
28
|
from python_minifier.transforms.remove_debug import RemoveDebug
|
|
28
|
-
from python_minifier.transforms.remove_explicit_return_none import RemoveExplicitReturnNone
|
|
29
29
|
from python_minifier.transforms.remove_exception_brackets import remove_no_arg_exception_call
|
|
30
|
+
from python_minifier.transforms.remove_explicit_return_none import RemoveExplicitReturnNone
|
|
30
31
|
from python_minifier.transforms.remove_literal_statements import RemoveLiteralStatements
|
|
31
32
|
from python_minifier.transforms.remove_object_base import RemoveObject
|
|
32
33
|
from python_minifier.transforms.remove_pass import RemovePass
|
|
@@ -85,7 +86,8 @@ def minify(
|
|
|
85
86
|
:param str source: The python module source code
|
|
86
87
|
:param str filename: The original source filename if known
|
|
87
88
|
|
|
88
|
-
:param remove_annotations: Configures the removal of type annotations. True removes all annotations, False removes none.
|
|
89
|
+
:param remove_annotations: Configures the removal of type annotations. True removes all annotations, False removes none.
|
|
90
|
+
RemoveAnnotationsOptions can be used to configure the removal of specific annotations.
|
|
89
91
|
:type remove_annotations: bool or RemoveAnnotationsOptions
|
|
90
92
|
:param bool remove_pass: If Pass statements should be removed where possible
|
|
91
93
|
:param bool remove_literal_statements: If statements consisting of a single literal should be removed, including docstrings
|
|
@@ -115,6 +117,7 @@ def minify(
|
|
|
115
117
|
# This will raise if the source file can't be parsed
|
|
116
118
|
module = ast.parse(source, filename)
|
|
117
119
|
|
|
120
|
+
add_parent(module)
|
|
118
121
|
add_namespace(module)
|
|
119
122
|
|
|
120
123
|
if remove_literal_statements:
|
|
@@ -198,6 +201,7 @@ def minify(
|
|
|
198
201
|
|
|
199
202
|
return minified
|
|
200
203
|
|
|
204
|
+
|
|
201
205
|
def _find_shebang(source):
|
|
202
206
|
"""
|
|
203
207
|
Find a shebang line in source
|
|
@@ -214,6 +218,7 @@ def _find_shebang(source):
|
|
|
214
218
|
|
|
215
219
|
return None
|
|
216
220
|
|
|
221
|
+
|
|
217
222
|
def unparse(module):
|
|
218
223
|
"""
|
|
219
224
|
Turn a module AST into python code
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import ast
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
from typing import Any, List, Optional, Text, Union
|
|
3
4
|
|
|
4
5
|
from .transforms.remove_annotations_options import RemoveAnnotationsOptions as RemoveAnnotationsOptions
|
|
5
6
|
|
|
7
|
+
|
|
6
8
|
class UnstableMinification(RuntimeError):
|
|
7
9
|
def __init__(self, exception: Any, source: Any, minified: Any): ...
|
|
8
10
|
|
|
11
|
+
|
|
9
12
|
def minify(
|
|
10
|
-
source:
|
|
13
|
+
source: Union[str, bytes],
|
|
11
14
|
filename: Optional[str] = ...,
|
|
12
15
|
remove_annotations: Union[bool, RemoveAnnotationsOptions] = ...,
|
|
13
16
|
remove_pass: bool = ...,
|
|
@@ -28,10 +31,12 @@ def minify(
|
|
|
28
31
|
constant_folding: bool = ...
|
|
29
32
|
) -> Text: ...
|
|
30
33
|
|
|
34
|
+
|
|
31
35
|
def unparse(module: ast.Module) -> Text: ...
|
|
32
36
|
|
|
37
|
+
|
|
33
38
|
def awslambda(
|
|
34
|
-
source:
|
|
39
|
+
source: Union[str, bytes],
|
|
35
40
|
filename: Optional[Text] = ...,
|
|
36
41
|
entrypoint: Optional[Text] = ...
|
|
37
42
|
) -> Text: ...
|
|
@@ -7,14 +7,29 @@ import sys
|
|
|
7
7
|
from python_minifier import minify
|
|
8
8
|
from python_minifier.transforms.remove_annotations_options import RemoveAnnotationsOptions
|
|
9
9
|
|
|
10
|
+
|
|
11
|
+
class MinificationNotBeneficialError(Exception):
|
|
12
|
+
"""Raised when minification results in larger output than the original."""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
def stdout_write_bytes(data):
|
|
16
|
+
"""Write bytes to stdout with proper Python 2.7/3.x compatibility."""
|
|
17
|
+
if sys.version_info >= (3, 0):
|
|
18
|
+
sys.stdout.buffer.write(data)
|
|
19
|
+
else:
|
|
20
|
+
sys.stdout.write(data)
|
|
21
|
+
|
|
22
|
+
|
|
10
23
|
if sys.version_info >= (3, 8):
|
|
11
24
|
from importlib import metadata
|
|
25
|
+
|
|
12
26
|
try:
|
|
13
27
|
version = metadata.version('python-minifier')
|
|
14
28
|
except metadata.PackageNotFoundError:
|
|
15
29
|
version = '0.0.0'
|
|
16
30
|
else:
|
|
17
|
-
from pkg_resources import
|
|
31
|
+
from pkg_resources import DistributionNotFound, get_distribution
|
|
32
|
+
|
|
18
33
|
try:
|
|
19
34
|
version = get_distribution('python_minifier').version
|
|
20
35
|
except DistributionNotFound:
|
|
@@ -48,12 +63,23 @@ examples:
|
|
|
48
63
|
if len(args.path) == 1 and args.path[0] == '-':
|
|
49
64
|
# minify stdin
|
|
50
65
|
source = sys.stdin.buffer.read() if sys.version_info >= (3, 0) else sys.stdin.read()
|
|
51
|
-
|
|
66
|
+
try:
|
|
67
|
+
minified = do_minify(source, 'stdin', args)
|
|
68
|
+
except MinificationNotBeneficialError:
|
|
69
|
+
# Use original source when minification isn't beneficial
|
|
70
|
+
if args.output:
|
|
71
|
+
with open(args.output, 'wb') as f:
|
|
72
|
+
f.write(source)
|
|
73
|
+
else:
|
|
74
|
+
# Write original source to stdout
|
|
75
|
+
stdout_write_bytes(source)
|
|
76
|
+
return
|
|
77
|
+
|
|
52
78
|
if args.output:
|
|
53
|
-
with open(args.output, '
|
|
79
|
+
with open(args.output, 'wb') as f:
|
|
54
80
|
f.write(minified)
|
|
55
81
|
else:
|
|
56
|
-
|
|
82
|
+
stdout_write_bytes(minified)
|
|
57
83
|
|
|
58
84
|
else:
|
|
59
85
|
# minify source paths
|
|
@@ -64,16 +90,30 @@ examples:
|
|
|
64
90
|
with open(path, 'rb') as f:
|
|
65
91
|
source = f.read()
|
|
66
92
|
|
|
67
|
-
|
|
93
|
+
try:
|
|
94
|
+
minified = do_minify(source, path, args)
|
|
95
|
+
except MinificationNotBeneficialError:
|
|
96
|
+
# Use original source when minification isn't beneficial
|
|
97
|
+
if args.in_place:
|
|
98
|
+
# File is already the original, no need to write
|
|
99
|
+
pass
|
|
100
|
+
elif args.output:
|
|
101
|
+
# Write original source to output
|
|
102
|
+
with open(args.output, 'wb') as f:
|
|
103
|
+
f.write(source)
|
|
104
|
+
else:
|
|
105
|
+
# Write original source to stdout
|
|
106
|
+
stdout_write_bytes(source)
|
|
107
|
+
continue
|
|
68
108
|
|
|
69
109
|
if args.in_place:
|
|
70
|
-
with open(path, '
|
|
110
|
+
with open(path, 'wb') as f:
|
|
71
111
|
f.write(minified)
|
|
72
112
|
elif args.output:
|
|
73
|
-
with open(args.output, '
|
|
113
|
+
with open(args.output, 'wb') as f:
|
|
74
114
|
f.write(minified)
|
|
75
115
|
else:
|
|
76
|
-
|
|
116
|
+
stdout_write_bytes(minified)
|
|
77
117
|
|
|
78
118
|
|
|
79
119
|
def parse_args():
|
|
@@ -170,25 +210,25 @@ def parse_args():
|
|
|
170
210
|
minification_options.add_argument(
|
|
171
211
|
'--no-preserve-shebang',
|
|
172
212
|
action='store_false',
|
|
173
|
-
help='
|
|
213
|
+
help='Disable preserving any shebang line from the source',
|
|
174
214
|
dest='preserve_shebang',
|
|
175
215
|
)
|
|
176
216
|
minification_options.add_argument(
|
|
177
217
|
'--remove-asserts',
|
|
178
218
|
action='store_true',
|
|
179
|
-
help='
|
|
219
|
+
help='Enable removing assert statements',
|
|
180
220
|
dest='remove_asserts',
|
|
181
221
|
)
|
|
182
222
|
minification_options.add_argument(
|
|
183
223
|
'--remove-debug',
|
|
184
224
|
action='store_true',
|
|
185
|
-
help='
|
|
225
|
+
help='Enable removing conditional statements that test __debug__ is True',
|
|
186
226
|
dest='remove_debug',
|
|
187
227
|
)
|
|
188
228
|
minification_options.add_argument(
|
|
189
229
|
'--no-remove-explicit-return-none',
|
|
190
230
|
action='store_false',
|
|
191
|
-
help='
|
|
231
|
+
help='Disable replacing explicit return None with a bare return',
|
|
192
232
|
dest='remove_explicit_return_none',
|
|
193
233
|
)
|
|
194
234
|
minification_options.add_argument(
|
|
@@ -268,15 +308,24 @@ def source_modules(args):
|
|
|
268
308
|
|
|
269
309
|
for path_arg in args.path:
|
|
270
310
|
if os.path.isdir(path_arg):
|
|
271
|
-
for root,
|
|
311
|
+
for root, _dirs, files in os.walk(path_arg, onerror=error, followlinks=True):
|
|
272
312
|
for file in files:
|
|
273
|
-
if file.endswith('.py'
|
|
313
|
+
if file.endswith(('.py', '.pyw')):
|
|
274
314
|
yield os.path.join(root, file)
|
|
275
315
|
else:
|
|
276
316
|
yield path_arg
|
|
277
317
|
|
|
278
318
|
|
|
279
319
|
def do_minify(source, filename, minification_args):
|
|
320
|
+
"""Minify Python source code with size-based fallback.
|
|
321
|
+
|
|
322
|
+
:param bytes source: Source code as bytes (from file 'rb' or stdin.buffer)
|
|
323
|
+
:param str filename: Filename for error reporting
|
|
324
|
+
:param argparse.Namespace minification_args: CLI arguments for minification options
|
|
325
|
+
:returns: Minified source code as UTF-8 bytes
|
|
326
|
+
:rtype: bytes
|
|
327
|
+
:raises MinificationNotBeneficialError: When minified output is larger than original
|
|
328
|
+
"""
|
|
280
329
|
|
|
281
330
|
preserve_globals = []
|
|
282
331
|
if minification_args.preserve_globals:
|
|
@@ -305,7 +354,7 @@ def do_minify(source, filename, minification_args):
|
|
|
305
354
|
remove_class_attribute_annotations=minification_args.remove_class_attribute_annotations,
|
|
306
355
|
)
|
|
307
356
|
|
|
308
|
-
|
|
357
|
+
minified_result = minify(
|
|
309
358
|
source,
|
|
310
359
|
filename=filename,
|
|
311
360
|
combine_imports=minification_args.combine_imports,
|
|
@@ -327,6 +376,19 @@ def do_minify(source, filename, minification_args):
|
|
|
327
376
|
constant_folding=minification_args.constant_folding
|
|
328
377
|
)
|
|
329
378
|
|
|
379
|
+
# Encode minified result to bytes for comparison and output
|
|
380
|
+
minified_bytes = minified_result.encode('utf-8')
|
|
381
|
+
|
|
382
|
+
# Check if environment variable forces minified output
|
|
383
|
+
if os.environ.get('PYMINIFY_FORCE_BEST_EFFORT'):
|
|
384
|
+
return minified_bytes
|
|
385
|
+
|
|
386
|
+
# Compare byte lengths for accurate size comparison
|
|
387
|
+
if len(minified_bytes) > len(source):
|
|
388
|
+
raise MinificationNotBeneficialError("Minified output is longer than original")
|
|
389
|
+
|
|
390
|
+
return minified_bytes
|
|
391
|
+
|
|
330
392
|
|
|
331
393
|
if __name__ == '__main__':
|
|
332
394
|
main()
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides utilities for annotating Abstract Syntax Tree (AST) nodes with parent references.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
|
|
7
|
+
class _NoParent(ast.AST):
|
|
8
|
+
"""A placeholder class used to indicate that a node has no parent."""
|
|
9
|
+
|
|
10
|
+
def __repr__(self):
|
|
11
|
+
# type: () -> str
|
|
12
|
+
return 'NoParent()'
|
|
13
|
+
|
|
14
|
+
def add_parent(node, parent=_NoParent()):
|
|
15
|
+
# type: (ast.AST, ast.AST) -> None
|
|
16
|
+
"""
|
|
17
|
+
Recursively adds a parent reference to each node in the AST.
|
|
18
|
+
|
|
19
|
+
>>> tree = ast.parse('a = 1')
|
|
20
|
+
>>> add_parent(tree)
|
|
21
|
+
>>> get_parent(tree.body[0]) == tree
|
|
22
|
+
True
|
|
23
|
+
|
|
24
|
+
:param node: The current AST node.
|
|
25
|
+
:param parent: The parent :class:`ast.AST` node.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
node._parent = parent # type: ignore[attr-defined]
|
|
29
|
+
for child in ast.iter_child_nodes(node):
|
|
30
|
+
add_parent(child, node)
|
|
31
|
+
|
|
32
|
+
def get_parent(node):
|
|
33
|
+
# type: (ast.AST) -> ast.AST
|
|
34
|
+
"""
|
|
35
|
+
Retrieves the parent of the given AST node.
|
|
36
|
+
|
|
37
|
+
>>> tree = ast.parse('a = 1')
|
|
38
|
+
>>> add_parent(tree)
|
|
39
|
+
>>> get_parent(tree.body[0]) == tree
|
|
40
|
+
True
|
|
41
|
+
|
|
42
|
+
:param node: The AST node whose parent is to be retrieved.
|
|
43
|
+
:return: The parent AST node.
|
|
44
|
+
:raises ValueError: If the node has no parent.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
if not hasattr(node, '_parent') or isinstance(node._parent, _NoParent): # type: ignore[attr-defined]
|
|
48
|
+
raise ValueError('Node has no parent')
|
|
49
|
+
|
|
50
|
+
return node._parent # type: ignore[attr-defined]
|
|
51
|
+
|
|
52
|
+
def set_parent(node, parent):
|
|
53
|
+
# type: (ast.AST, ast.AST) -> None
|
|
54
|
+
"""
|
|
55
|
+
Replace the parent of the given AST node.
|
|
56
|
+
|
|
57
|
+
Create a simple AST:
|
|
58
|
+
>>> tree = ast.parse('a = func()')
|
|
59
|
+
>>> add_parent(tree)
|
|
60
|
+
>>> isinstance(tree.body[0], ast.Assign) and isinstance(tree.body[0].value, ast.Call)
|
|
61
|
+
True
|
|
62
|
+
>>> assign = tree.body[0]
|
|
63
|
+
>>> call = tree.body[0].value
|
|
64
|
+
>>> get_parent(call) == assign
|
|
65
|
+
True
|
|
66
|
+
|
|
67
|
+
Replace the parent of the call node:
|
|
68
|
+
>>> tree.body[0] = call
|
|
69
|
+
>>> set_parent(call, tree)
|
|
70
|
+
>>> get_parent(call) == tree
|
|
71
|
+
True
|
|
72
|
+
>>> from python_minifier.ast_printer import print_ast
|
|
73
|
+
>>> print(print_ast(tree))
|
|
74
|
+
Module(body=[
|
|
75
|
+
Call(Name('func'))
|
|
76
|
+
])
|
|
77
|
+
|
|
78
|
+
:param node: The AST node whose parent is to be set.
|
|
79
|
+
:param parent: The parent AST node.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
node._parent = parent # type: ignore[attr-defined]
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import python_minifier.ast_compat as ast
|
|
2
2
|
|
|
3
|
-
from python_minifier.util import is_ast_node
|
|
4
|
-
|
|
5
3
|
|
|
6
4
|
class CompareError(RuntimeError):
|
|
7
5
|
"""
|
|
@@ -18,7 +16,7 @@ class CompareError(RuntimeError):
|
|
|
18
16
|
|
|
19
17
|
def namespace(self, node):
|
|
20
18
|
if hasattr(node, 'namespace'):
|
|
21
|
-
if
|
|
19
|
+
if isinstance(node.namespace, (ast.FunctionDef, ast.ClassDef, ast.AsyncFunctionDef)):
|
|
22
20
|
return self.namespace(node.namespace) + '.' + node.namespace.name
|
|
23
21
|
elif isinstance(node.namespace, ast.Module):
|
|
24
22
|
return ''
|
|
@@ -79,26 +77,26 @@ def compare_ast(l_ast, r_ast):
|
|
|
79
77
|
% (type(l_ast), field, len(l_list), type(r_ast), field, len(r_list)),
|
|
80
78
|
)
|
|
81
79
|
|
|
82
|
-
for i,
|
|
83
|
-
if isinstance(
|
|
84
|
-
compare_ast(
|
|
85
|
-
elif
|
|
80
|
+
for i, left, right in zip(counter(), l_list, r_list):
|
|
81
|
+
if isinstance(left, ast.AST) or isinstance(right, ast.AST):
|
|
82
|
+
compare_ast(left, right)
|
|
83
|
+
elif left != right:
|
|
86
84
|
raise CompareError(
|
|
87
85
|
l_ast,
|
|
88
86
|
r_ast,
|
|
89
87
|
'Fields do not match! %s.%s[%i]=%r, %s.%s[%i]=%r'
|
|
90
|
-
% (type(l_ast), field, i,
|
|
88
|
+
% (type(l_ast), field, i, left, type(r_ast), field, i, right),
|
|
91
89
|
)
|
|
92
90
|
|
|
93
91
|
else:
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
left_field = getattr(l_ast, field, None)
|
|
93
|
+
right_field = getattr(r_ast, field, None)
|
|
96
94
|
|
|
97
|
-
if isinstance(
|
|
98
|
-
compare_ast(
|
|
99
|
-
elif
|
|
95
|
+
if isinstance(left_field, ast.AST) or isinstance(right_field, ast.AST):
|
|
96
|
+
compare_ast(left_field, right_field)
|
|
97
|
+
elif left_field != right_field:
|
|
100
98
|
raise CompareError(
|
|
101
99
|
l_ast,
|
|
102
100
|
r_ast,
|
|
103
|
-
'Fields do not match! %s.%s=%r, %s.%s=%r' % (type(l_ast), field,
|
|
101
|
+
'Fields do not match! %s.%s=%r, %s.%s=%r' % (type(l_ast), field, left_field, type(r_ast), field, right_field),
|
|
104
102
|
)
|
|
@@ -8,6 +8,7 @@ deprecation warnings.
|
|
|
8
8
|
|
|
9
9
|
from ast import *
|
|
10
10
|
|
|
11
|
+
|
|
11
12
|
# Ideally we don't import anything else
|
|
12
13
|
|
|
13
14
|
if 'TypeAlias' in globals():
|
|
@@ -16,6 +17,7 @@ if 'TypeAlias' in globals():
|
|
|
16
17
|
Constant.n = property(lambda self: self.value, lambda self, value: setattr(self, 'value', value)) # type: ignore[assignment]
|
|
17
18
|
Constant.s = property(lambda self: self.value, lambda self, value: setattr(self, 'value', value)) # type: ignore[assignment]
|
|
18
19
|
|
|
20
|
+
|
|
19
21
|
# These classes are redefined from the ones in ast that complain about deprecation
|
|
20
22
|
# They will continue to work once they are removed from ast
|
|
21
23
|
|
|
@@ -23,18 +25,54 @@ if 'TypeAlias' in globals():
|
|
|
23
25
|
def __new__(cls, s, *args, **kwargs):
|
|
24
26
|
return Constant(value=s, *args, **kwargs)
|
|
25
27
|
|
|
28
|
+
|
|
26
29
|
class Bytes(Constant): # type: ignore[no-redef]
|
|
27
30
|
def __new__(cls, s, *args, **kwargs):
|
|
28
31
|
return Constant(value=s, *args, **kwargs)
|
|
29
32
|
|
|
33
|
+
|
|
30
34
|
class Num(Constant): # type: ignore[no-redef]
|
|
31
35
|
def __new__(cls, n, *args, **kwargs):
|
|
32
36
|
return Constant(value=n, *args, **kwargs)
|
|
33
37
|
|
|
38
|
+
|
|
34
39
|
class NameConstant(Constant): # type: ignore[no-redef]
|
|
35
40
|
def __new__(cls, *args, **kwargs):
|
|
36
41
|
return Constant(*args, **kwargs)
|
|
37
42
|
|
|
43
|
+
|
|
38
44
|
class Ellipsis(Constant): # type: ignore[no-redef]
|
|
39
45
|
def __new__(cls, *args, **kwargs):
|
|
40
46
|
return Constant(value=literal_eval('...'), *args, **kwargs)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# Create a dummy class for missing AST nodes
|
|
50
|
+
for _node_type in [
|
|
51
|
+
'AnnAssign',
|
|
52
|
+
'AsyncFor',
|
|
53
|
+
'AsyncFunctionDef',
|
|
54
|
+
'AsyncFunctionDef',
|
|
55
|
+
'AsyncWith',
|
|
56
|
+
'Bytes',
|
|
57
|
+
'Constant',
|
|
58
|
+
'DictComp',
|
|
59
|
+
'Exec',
|
|
60
|
+
'ListComp',
|
|
61
|
+
'MatchAs',
|
|
62
|
+
'MatchMapping',
|
|
63
|
+
'MatchStar',
|
|
64
|
+
'NameConstant',
|
|
65
|
+
'NamedExpr',
|
|
66
|
+
'Nonlocal',
|
|
67
|
+
'ParamSpec',
|
|
68
|
+
'SetComp',
|
|
69
|
+
'Starred',
|
|
70
|
+
'TryStar',
|
|
71
|
+
'TypeVar',
|
|
72
|
+
'TypeVarTuple',
|
|
73
|
+
'YieldFrom',
|
|
74
|
+
'arg',
|
|
75
|
+
'withitem',
|
|
76
|
+
]:
|
|
77
|
+
if _node_type not in globals():
|
|
78
|
+
globals()[_node_type] = type(_node_type, (AST,), {})
|
|
@@ -12,7 +12,8 @@ fields or field names may be omitted for clarity. It should still be precise and
|
|
|
12
12
|
|
|
13
13
|
import python_minifier.ast_compat as ast
|
|
14
14
|
|
|
15
|
-
from python_minifier.util import
|
|
15
|
+
from python_minifier.util import is_constant_node
|
|
16
|
+
|
|
16
17
|
|
|
17
18
|
INDENT = ' '
|
|
18
19
|
|
|
@@ -63,24 +64,26 @@ default_fields = {
|
|
|
63
64
|
'AugAssign': 'op',
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
|
|
66
68
|
def is_literal(node, field):
|
|
67
69
|
if hasattr(ast, 'Constant') and isinstance(node, ast.Constant) and field == 'value':
|
|
68
70
|
return True
|
|
69
71
|
|
|
70
|
-
if
|
|
72
|
+
if is_constant_node(node, ast.Num) and field == 'n':
|
|
71
73
|
return True
|
|
72
74
|
|
|
73
|
-
if
|
|
75
|
+
if is_constant_node(node, ast.Str) and field == 's':
|
|
74
76
|
return True
|
|
75
77
|
|
|
76
|
-
if
|
|
78
|
+
if is_constant_node(node, ast.Bytes) and field == 's':
|
|
77
79
|
return True
|
|
78
80
|
|
|
79
|
-
if
|
|
81
|
+
if is_constant_node(node, ast.NameConstant) and field == 'value':
|
|
80
82
|
return True
|
|
81
83
|
|
|
82
84
|
return False
|
|
83
85
|
|
|
86
|
+
|
|
84
87
|
def print_ast(node):
|
|
85
88
|
if not isinstance(node, ast.AST):
|
|
86
89
|
return repr(node)
|