python-minifier 2.8.1__tar.gz → 2.10.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.8.1 → python_minifier-2.10.0}/PKG-INFO +5 -4
- {python_minifier-2.8.1 → python_minifier-2.10.0}/README.md +2 -2
- {python_minifier-2.8.1 → python_minifier-2.10.0}/setup.py +4 -4
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/__init__.py +50 -6
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/__init__.pyi +7 -3
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/__main__.py +93 -17
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/ast_compare.py +1 -1
- python_minifier-2.10.0/src/python_minifier/ast_compat.py +40 -0
- python_minifier-2.10.0/src/python_minifier/ast_printer.py +130 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/expression_printer.py +214 -270
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/f_string.py +89 -33
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/module_printer.py +276 -284
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/rename/bind_names.py +24 -6
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/rename/binding.py +48 -3
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/rename/mapper.py +16 -1
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/rename/rename_literals.py +17 -2
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/rename/renamer.py +1 -1
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/rename/resolve_names.py +28 -10
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/rename/util.py +10 -1
- python_minifier-2.10.0/src/python_minifier/token_printer.py +299 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/transforms/combine_imports.py +8 -5
- python_minifier-2.10.0/src/python_minifier/transforms/constant_folding.py +114 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/transforms/remove_annotations.py +25 -5
- python_minifier-2.10.0/src/python_minifier/transforms/remove_annotations_options.py +35 -0
- python_minifier-2.10.0/src/python_minifier/transforms/remove_annotations_options.pyi +17 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/transforms/remove_asserts.py +1 -1
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/transforms/remove_debug.py +1 -1
- python_minifier-2.10.0/src/python_minifier/transforms/remove_exception_brackets.py +124 -0
- python_minifier-2.10.0/src/python_minifier/transforms/remove_explicit_return_none.py +38 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/transforms/remove_literal_statements.py +1 -1
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/transforms/remove_object_base.py +4 -1
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/transforms/remove_pass.py +1 -1
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/transforms/remove_posargs.py +1 -1
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/transforms/suite_transformer.py +4 -1
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/util.py +7 -7
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier.egg-info/PKG-INFO +6 -5
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier.egg-info/SOURCES.txt +18 -1
- python_minifier-2.10.0/test/test_bind_names.py +1354 -0
- python_minifier-2.10.0/test/test_bind_names_python2.py +605 -0
- python_minifier-2.10.0/test/test_bind_names_python33.py +620 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/test/test_combine_imports.py +80 -0
- python_minifier-2.10.0/test/test_folding.py +136 -0
- python_minifier-2.10.0/test/test_fstring.py +103 -0
- python_minifier-2.10.0/test/test_generic_types.py +278 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/test/test_hoist_literals.py +26 -0
- python_minifier-2.10.0/test/test_import.py +30 -0
- python_minifier-2.10.0/test/test_is_ast_node.py +40 -0
- python_minifier-2.10.0/test/test_remove_annotations.py +325 -0
- python_minifier-2.10.0/test/test_remove_exception_brackets.py +165 -0
- python_minifier-2.10.0/test/test_remove_explicit_return_none.py +218 -0
- python_minifier-2.8.1/test/test_empty_fstring.py +0 -19
- python_minifier-2.8.1/test/test_remove_annotations.py +0 -218
- {python_minifier-2.8.1 → python_minifier-2.10.0}/LICENSE +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/setup.cfg +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/ministring.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/py.typed +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/rename/__init__.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/rename/name_generator.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier/transforms/__init__.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier.egg-info/dependency_links.txt +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier.egg-info/entry_points.txt +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier.egg-info/top_level.txt +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/src/python_minifier.egg-info/zip-safe +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/test/test_assignment_expressions.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/test/test_await_fstring.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/test/test_comprehension_rename.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/test/test_decorator_expressions.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/test/test_dict_expansion.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/test/test_iterable_unpacking.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/test/test_match.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/test/test_match_rename.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/test/test_name_generator.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/test/test_nonlocal.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/test/test_posargs.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/test/test_preserve_shebang.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/test/test_remove_assert.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/test/test_remove_debug.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/test/test_remove_literal_statements.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/test/test_remove_object.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/test/test_remove_pass.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/test/test_rename_builtins.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/test/test_rename_locals.py +0 -0
- {python_minifier-2.8.1 → python_minifier-2.10.0}/test/test_slice.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python_minifier
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.10.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
|
|
@@ -22,13 +22,14 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.9
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.10
|
|
24
24
|
Classifier: Programming Language :: Python :: 3.11
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
25
26
|
Classifier: Programming Language :: Python :: 2
|
|
26
27
|
Classifier: Programming Language :: Python :: 2.7
|
|
27
28
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
28
29
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
29
30
|
Classifier: Intended Audience :: Developers
|
|
30
31
|
Classifier: Topic :: Software Development
|
|
31
|
-
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.
|
|
32
|
+
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.13
|
|
32
33
|
Description-Content-Type: text/markdown
|
|
33
34
|
License-File: LICENSE
|
|
34
35
|
|
|
@@ -38,7 +39,7 @@ Transforms Python source code into its most compact representation.
|
|
|
38
39
|
|
|
39
40
|
[Try it out!](https://python-minifier.com)
|
|
40
41
|
|
|
41
|
-
python-minifier currently supports Python 2.7 and Python 3.3 to 3.
|
|
42
|
+
python-minifier currently supports Python 2.7 and Python 3.3 to 3.12. Previous releases supported Python 2.6.
|
|
42
43
|
|
|
43
44
|
* [PyPI](https://pypi.org/project/python-minifier/)
|
|
44
45
|
* [Documentation](https://dflook.github.io/python-minifier/)
|
|
@@ -141,7 +142,7 @@ $ pip install python-minifier
|
|
|
141
142
|
Note that python-minifier depends on the python interpreter for parsing source code,
|
|
142
143
|
so install using a version of python appropriate for your source.
|
|
143
144
|
|
|
144
|
-
python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.
|
|
145
|
+
python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.12.
|
|
145
146
|
|
|
146
147
|
## Usage
|
|
147
148
|
|
|
@@ -4,7 +4,7 @@ Transforms Python source code into its most compact representation.
|
|
|
4
4
|
|
|
5
5
|
[Try it out!](https://python-minifier.com)
|
|
6
6
|
|
|
7
|
-
python-minifier currently supports Python 2.7 and Python 3.3 to 3.
|
|
7
|
+
python-minifier currently supports Python 2.7 and Python 3.3 to 3.12. Previous releases supported Python 2.6.
|
|
8
8
|
|
|
9
9
|
* [PyPI](https://pypi.org/project/python-minifier/)
|
|
10
10
|
* [Documentation](https://dflook.github.io/python-minifier/)
|
|
@@ -107,7 +107,7 @@ $ pip install python-minifier
|
|
|
107
107
|
Note that python-minifier depends on the python interpreter for parsing source code,
|
|
108
108
|
so install using a version of python appropriate for your source.
|
|
109
109
|
|
|
110
|
-
python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.
|
|
110
|
+
python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.12.
|
|
111
111
|
|
|
112
112
|
## Usage
|
|
113
113
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import os.path
|
|
2
|
-
import sys
|
|
3
2
|
|
|
4
3
|
from setuptools import setup, find_packages
|
|
5
4
|
|
|
@@ -23,12 +22,12 @@ setup(
|
|
|
23
22
|
|
|
24
23
|
package_dir={'': 'src'},
|
|
25
24
|
packages=find_packages('src'),
|
|
26
|
-
package_data={"python_minifier": ["py.typed", "
|
|
25
|
+
package_data={"python_minifier": ["py.typed", "*.pyi", "rename/*.pyi", "transforms/*.pyi"]},
|
|
27
26
|
long_description=long_desc,
|
|
28
27
|
long_description_content_type='text/markdown',
|
|
29
28
|
|
|
30
|
-
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.
|
|
31
|
-
version='2.
|
|
29
|
+
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.13',
|
|
30
|
+
version='2.10.0',
|
|
32
31
|
|
|
33
32
|
classifiers=[
|
|
34
33
|
'Development Status :: 5 - Production/Stable',
|
|
@@ -44,6 +43,7 @@ setup(
|
|
|
44
43
|
'Programming Language :: Python :: 3.9',
|
|
45
44
|
'Programming Language :: Python :: 3.10',
|
|
46
45
|
'Programming Language :: Python :: 3.11',
|
|
46
|
+
'Programming Language :: Python :: 3.12',
|
|
47
47
|
'Programming Language :: Python :: 2',
|
|
48
48
|
'Programming Language :: Python :: 2.7',
|
|
49
49
|
'Programming Language :: Python :: Implementation :: CPython',
|
|
@@ -4,7 +4,7 @@ a 'minified' representation of the same source code.
|
|
|
4
4
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
import ast
|
|
7
|
+
import python_minifier.ast_compat as ast
|
|
8
8
|
import re
|
|
9
9
|
|
|
10
10
|
from python_minifier.ast_compare import CompareError, compare_ast
|
|
@@ -20,9 +20,13 @@ from python_minifier.rename import (
|
|
|
20
20
|
)
|
|
21
21
|
|
|
22
22
|
from python_minifier.transforms.combine_imports import CombineImports
|
|
23
|
+
from python_minifier.transforms.constant_folding import FoldConstants
|
|
23
24
|
from python_minifier.transforms.remove_annotations import RemoveAnnotations
|
|
25
|
+
from python_minifier.transforms.remove_annotations_options import RemoveAnnotationsOptions
|
|
24
26
|
from python_minifier.transforms.remove_asserts import RemoveAsserts
|
|
25
27
|
from python_minifier.transforms.remove_debug import RemoveDebug
|
|
28
|
+
from python_minifier.transforms.remove_explicit_return_none import RemoveExplicitReturnNone
|
|
29
|
+
from python_minifier.transforms.remove_exception_brackets import remove_no_arg_exception_call
|
|
26
30
|
from python_minifier.transforms.remove_literal_statements import RemoveLiteralStatements
|
|
27
31
|
from python_minifier.transforms.remove_object_base import RemoveObject
|
|
28
32
|
from python_minifier.transforms.remove_pass import RemovePass
|
|
@@ -51,7 +55,7 @@ class UnstableMinification(RuntimeError):
|
|
|
51
55
|
def minify(
|
|
52
56
|
source,
|
|
53
57
|
filename=None,
|
|
54
|
-
remove_annotations=
|
|
58
|
+
remove_annotations=RemoveAnnotationsOptions(),
|
|
55
59
|
remove_pass=True,
|
|
56
60
|
remove_literal_statements=False,
|
|
57
61
|
combine_imports=True,
|
|
@@ -64,7 +68,10 @@ def minify(
|
|
|
64
68
|
convert_posargs_to_args=True,
|
|
65
69
|
preserve_shebang=True,
|
|
66
70
|
remove_asserts=False,
|
|
67
|
-
remove_debug=False
|
|
71
|
+
remove_debug=False,
|
|
72
|
+
remove_explicit_return_none=True,
|
|
73
|
+
remove_builtin_exception_brackets=True,
|
|
74
|
+
constant_folding=True
|
|
68
75
|
):
|
|
69
76
|
"""
|
|
70
77
|
Minify a python module
|
|
@@ -78,7 +85,8 @@ def minify(
|
|
|
78
85
|
:param str source: The python module source code
|
|
79
86
|
:param str filename: The original source filename if known
|
|
80
87
|
|
|
81
|
-
:param
|
|
88
|
+
:param remove_annotations: Configures the removal of type annotations. True removes all annotations, False removes none. RemoveAnnotationsOptions can be used to configure the removal of specific annotations.
|
|
89
|
+
:type remove_annotations: bool or RemoveAnnotationsOptions
|
|
82
90
|
:param bool remove_pass: If Pass statements should be removed where possible
|
|
83
91
|
:param bool remove_literal_statements: If statements consisting of a single literal should be removed, including docstrings
|
|
84
92
|
:param bool combine_imports: Combine adjacent import statements where possible
|
|
@@ -94,6 +102,9 @@ def minify(
|
|
|
94
102
|
:param bool preserve_shebang: Keep any shebang interpreter directive from the source in the minified output
|
|
95
103
|
:param bool remove_asserts: If assert statements should be removed
|
|
96
104
|
:param bool remove_debug: If conditional statements that test '__debug__ is True' should be removed
|
|
105
|
+
:param bool remove_explicit_return_none: If explicit return None statements should be replaced with a bare return
|
|
106
|
+
:param bool remove_builtin_exception_brackets: If brackets should be removed when raising exceptions with no arguments
|
|
107
|
+
:param bool constant_folding: If literal expressions should be evaluated
|
|
97
108
|
|
|
98
109
|
:rtype: str
|
|
99
110
|
|
|
@@ -112,8 +123,20 @@ def minify(
|
|
|
112
123
|
if combine_imports:
|
|
113
124
|
module = CombineImports()(module)
|
|
114
125
|
|
|
115
|
-
if remove_annotations:
|
|
116
|
-
|
|
126
|
+
if isinstance(remove_annotations, bool):
|
|
127
|
+
remove_annotations_options = RemoveAnnotationsOptions(
|
|
128
|
+
remove_variable_annotations=remove_annotations,
|
|
129
|
+
remove_return_annotations=remove_annotations,
|
|
130
|
+
remove_argument_annotations=remove_annotations,
|
|
131
|
+
remove_class_attribute_annotations=remove_annotations,
|
|
132
|
+
)
|
|
133
|
+
elif isinstance(remove_annotations, RemoveAnnotationsOptions):
|
|
134
|
+
remove_annotations_options = remove_annotations
|
|
135
|
+
else:
|
|
136
|
+
raise TypeError('remove_annotations must be a bool or RemoveAnnotationsOptions')
|
|
137
|
+
|
|
138
|
+
if remove_annotations_options:
|
|
139
|
+
module = RemoveAnnotations(remove_annotations_options)(module)
|
|
117
140
|
|
|
118
141
|
if remove_pass:
|
|
119
142
|
module = RemovePass()(module)
|
|
@@ -127,13 +150,34 @@ def minify(
|
|
|
127
150
|
if remove_debug:
|
|
128
151
|
module = RemoveDebug()(module)
|
|
129
152
|
|
|
153
|
+
if remove_explicit_return_none:
|
|
154
|
+
module = RemoveExplicitReturnNone()(module)
|
|
155
|
+
|
|
156
|
+
if constant_folding:
|
|
157
|
+
module = FoldConstants()(module)
|
|
158
|
+
|
|
130
159
|
bind_names(module)
|
|
131
160
|
resolve_names(module)
|
|
132
161
|
|
|
162
|
+
if remove_builtin_exception_brackets and not module.tainted:
|
|
163
|
+
remove_no_arg_exception_call(module)
|
|
164
|
+
|
|
133
165
|
if module.tainted:
|
|
134
166
|
rename_globals = False
|
|
135
167
|
rename_locals = False
|
|
136
168
|
|
|
169
|
+
if preserve_locals is None:
|
|
170
|
+
preserve_locals = []
|
|
171
|
+
elif isinstance(preserve_locals, str):
|
|
172
|
+
preserve_locals = [preserve_locals]
|
|
173
|
+
if preserve_globals is None:
|
|
174
|
+
preserve_globals = []
|
|
175
|
+
elif isinstance(preserve_globals, str):
|
|
176
|
+
preserve_globals = [preserve_globals]
|
|
177
|
+
|
|
178
|
+
preserve_locals.extend(module.preserved)
|
|
179
|
+
preserve_globals.extend(module.preserved)
|
|
180
|
+
|
|
137
181
|
allow_rename_locals(module, rename_locals, preserve_locals)
|
|
138
182
|
allow_rename_globals(module, rename_globals, preserve_globals)
|
|
139
183
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import ast
|
|
2
|
-
from typing import List, Text, AnyStr, Optional, Any
|
|
2
|
+
from typing import List, Text, AnyStr, Optional, Any, Union
|
|
3
3
|
|
|
4
|
+
from .transforms.remove_annotations_options import RemoveAnnotationsOptions as RemoveAnnotationsOptions
|
|
4
5
|
|
|
5
6
|
class UnstableMinification(RuntimeError):
|
|
6
7
|
def __init__(self, exception: Any, source: Any, minified: Any): ...
|
|
@@ -8,7 +9,7 @@ class UnstableMinification(RuntimeError):
|
|
|
8
9
|
def minify(
|
|
9
10
|
source: AnyStr,
|
|
10
11
|
filename: Optional[str] = ...,
|
|
11
|
-
remove_annotations: bool = ...,
|
|
12
|
+
remove_annotations: Union[bool, RemoveAnnotationsOptions] = ...,
|
|
12
13
|
remove_pass: bool = ...,
|
|
13
14
|
remove_literal_statements: bool = ...,
|
|
14
15
|
combine_imports: bool = ...,
|
|
@@ -21,7 +22,10 @@ def minify(
|
|
|
21
22
|
convert_posargs_to_args: bool = ...,
|
|
22
23
|
preserve_shebang: bool = ...,
|
|
23
24
|
remove_asserts: bool = ...,
|
|
24
|
-
remove_debug: bool =
|
|
25
|
+
remove_debug: bool = ...,
|
|
26
|
+
remove_explicit_return_none: bool = ...,
|
|
27
|
+
remove_builtin_exception_brackets: bool = ...,
|
|
28
|
+
constant_folding: bool = ...
|
|
25
29
|
) -> Text: ...
|
|
26
30
|
|
|
27
31
|
def unparse(module: ast.Module) -> Text: ...
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
from __future__ import print_function
|
|
2
2
|
|
|
3
|
-
import sys
|
|
4
|
-
import os
|
|
5
|
-
|
|
6
3
|
import argparse
|
|
7
|
-
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
8
6
|
|
|
9
7
|
from python_minifier import minify
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
from python_minifier.transforms.remove_annotations_options import RemoveAnnotationsOptions
|
|
9
|
+
|
|
10
|
+
if sys.version_info >= (3, 8):
|
|
11
|
+
from importlib import metadata
|
|
12
|
+
try:
|
|
13
|
+
version = metadata.version('python-minifier')
|
|
14
|
+
except metadata.PackageNotFoundError:
|
|
15
|
+
version = '0.0.0'
|
|
16
|
+
else:
|
|
17
|
+
from pkg_resources import get_distribution, DistributionNotFound
|
|
18
|
+
try:
|
|
19
|
+
version = get_distribution('python_minifier').version
|
|
20
|
+
except DistributionNotFound:
|
|
21
|
+
version = '0.0.0'
|
|
15
22
|
|
|
16
23
|
|
|
17
24
|
def main():
|
|
@@ -68,6 +75,7 @@ examples:
|
|
|
68
75
|
else:
|
|
69
76
|
sys.stdout.write(minified)
|
|
70
77
|
|
|
78
|
+
|
|
71
79
|
def parse_args():
|
|
72
80
|
parser = argparse.ArgumentParser(prog='pyminify', description='Minify Python source code', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=main.__doc__)
|
|
73
81
|
|
|
@@ -113,12 +121,6 @@ def parse_args():
|
|
|
113
121
|
help='Enable removing statements that are just a literal (including docstrings)',
|
|
114
122
|
dest='remove_literal_statements',
|
|
115
123
|
)
|
|
116
|
-
minification_options.add_argument(
|
|
117
|
-
'--no-remove-annotations',
|
|
118
|
-
action='store_false',
|
|
119
|
-
help='Disable removing function and variable annotations',
|
|
120
|
-
dest='remove_annotations',
|
|
121
|
-
)
|
|
122
124
|
minification_options.add_argument(
|
|
123
125
|
'--no-hoist-literals',
|
|
124
126
|
action='store_false',
|
|
@@ -183,6 +185,56 @@ def parse_args():
|
|
|
183
185
|
help='Remove conditional statements that test __debug__ is True',
|
|
184
186
|
dest='remove_debug',
|
|
185
187
|
)
|
|
188
|
+
minification_options.add_argument(
|
|
189
|
+
'--no-remove-explicit-return-none',
|
|
190
|
+
action='store_false',
|
|
191
|
+
help='Replace explicit return None with a bare return',
|
|
192
|
+
dest='remove_explicit_return_none',
|
|
193
|
+
)
|
|
194
|
+
minification_options.add_argument(
|
|
195
|
+
'--no-remove-builtin-exception-brackets',
|
|
196
|
+
action='store_false',
|
|
197
|
+
help='Disable removing brackets when raising builtin exceptions with no arguments',
|
|
198
|
+
dest='remove_exception_brackets',
|
|
199
|
+
)
|
|
200
|
+
minification_options.add_argument(
|
|
201
|
+
'--no-constant-folding',
|
|
202
|
+
action='store_false',
|
|
203
|
+
help='Disable evaluating literal expressions',
|
|
204
|
+
dest='constant_folding',
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
annotation_options = parser.add_argument_group('remove annotations options', 'Options that affect how annotations are removed')
|
|
208
|
+
annotation_options.add_argument(
|
|
209
|
+
'--no-remove-annotations',
|
|
210
|
+
action='store_false',
|
|
211
|
+
help='Disable removing all annotations',
|
|
212
|
+
dest='remove_annotations',
|
|
213
|
+
)
|
|
214
|
+
annotation_options.add_argument(
|
|
215
|
+
'--no-remove-variable-annotations',
|
|
216
|
+
action='store_false',
|
|
217
|
+
help='Disable removing variable annotations',
|
|
218
|
+
dest='remove_variable_annotations',
|
|
219
|
+
)
|
|
220
|
+
annotation_options.add_argument(
|
|
221
|
+
'--no-remove-return-annotations',
|
|
222
|
+
action='store_false',
|
|
223
|
+
help='Disable removing function return annotations',
|
|
224
|
+
dest='remove_return_annotations',
|
|
225
|
+
)
|
|
226
|
+
annotation_options.add_argument(
|
|
227
|
+
'--no-remove-argument-annotations',
|
|
228
|
+
action='store_false',
|
|
229
|
+
help='Disable removing function argument annotations',
|
|
230
|
+
dest='remove_argument_annotations',
|
|
231
|
+
)
|
|
232
|
+
annotation_options.add_argument(
|
|
233
|
+
'--remove-class-attribute-annotations',
|
|
234
|
+
action='store_true',
|
|
235
|
+
help='Enable removing class attribute annotations',
|
|
236
|
+
dest='remove_class_attribute_annotations',
|
|
237
|
+
)
|
|
186
238
|
|
|
187
239
|
parser.add_argument('--version', '-v', action='version', version=version)
|
|
188
240
|
|
|
@@ -202,8 +254,13 @@ def parse_args():
|
|
|
202
254
|
sys.stderr.write('error: path ' + args.path[0] + ' is a directory, --in-place required\n')
|
|
203
255
|
sys.exit(1)
|
|
204
256
|
|
|
257
|
+
if args.remove_class_attribute_annotations and not args.remove_annotations:
|
|
258
|
+
sys.stderr.write('error: --remove-class-attribute-annotations would do nothing when used with --no-remove-annotations\n')
|
|
259
|
+
sys.exit(1)
|
|
260
|
+
|
|
205
261
|
return args
|
|
206
262
|
|
|
263
|
+
|
|
207
264
|
def source_modules(args):
|
|
208
265
|
|
|
209
266
|
def error(os_error):
|
|
@@ -218,6 +275,7 @@ def source_modules(args):
|
|
|
218
275
|
else:
|
|
219
276
|
yield path_arg
|
|
220
277
|
|
|
278
|
+
|
|
221
279
|
def do_minify(source, filename, minification_args):
|
|
222
280
|
|
|
223
281
|
preserve_globals = []
|
|
@@ -232,12 +290,27 @@ def do_minify(source, filename, minification_args):
|
|
|
232
290
|
names = [name.strip() for name in arg.split(',') if name]
|
|
233
291
|
preserve_locals.extend(names)
|
|
234
292
|
|
|
293
|
+
if minification_args.remove_annotations is False:
|
|
294
|
+
remove_annotations = RemoveAnnotationsOptions(
|
|
295
|
+
remove_variable_annotations=False,
|
|
296
|
+
remove_return_annotations=False,
|
|
297
|
+
remove_argument_annotations=False,
|
|
298
|
+
remove_class_attribute_annotations=False,
|
|
299
|
+
)
|
|
300
|
+
else:
|
|
301
|
+
remove_annotations = RemoveAnnotationsOptions(
|
|
302
|
+
remove_variable_annotations=minification_args.remove_variable_annotations,
|
|
303
|
+
remove_return_annotations=minification_args.remove_return_annotations,
|
|
304
|
+
remove_argument_annotations=minification_args.remove_argument_annotations,
|
|
305
|
+
remove_class_attribute_annotations=minification_args.remove_class_attribute_annotations,
|
|
306
|
+
)
|
|
307
|
+
|
|
235
308
|
return minify(
|
|
236
309
|
source,
|
|
237
310
|
filename=filename,
|
|
238
311
|
combine_imports=minification_args.combine_imports,
|
|
239
312
|
remove_pass=minification_args.remove_pass,
|
|
240
|
-
remove_annotations=
|
|
313
|
+
remove_annotations=remove_annotations,
|
|
241
314
|
remove_literal_statements=minification_args.remove_literal_statements,
|
|
242
315
|
hoist_literals=minification_args.hoist_literals,
|
|
243
316
|
rename_locals=minification_args.rename_locals,
|
|
@@ -248,7 +321,10 @@ def do_minify(source, filename, minification_args):
|
|
|
248
321
|
convert_posargs_to_args=minification_args.convert_posargs_to_args,
|
|
249
322
|
preserve_shebang=minification_args.preserve_shebang,
|
|
250
323
|
remove_asserts=minification_args.remove_asserts,
|
|
251
|
-
remove_debug=minification_args.remove_debug
|
|
324
|
+
remove_debug=minification_args.remove_debug,
|
|
325
|
+
remove_explicit_return_none=minification_args.remove_explicit_return_none,
|
|
326
|
+
remove_builtin_exception_brackets=minification_args.remove_exception_brackets,
|
|
327
|
+
constant_folding=minification_args.constant_folding
|
|
252
328
|
)
|
|
253
329
|
|
|
254
330
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The is a backwards compatible shim for the ast module.
|
|
3
|
+
|
|
4
|
+
This is the best way to make the ast module work the same in both python 2 and 3.
|
|
5
|
+
This is essentially what the ast module was doing until 3.12, when it started throwing
|
|
6
|
+
deprecation warnings.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from ast import *
|
|
10
|
+
|
|
11
|
+
# Ideally we don't import anything else
|
|
12
|
+
|
|
13
|
+
if 'TypeAlias' in globals():
|
|
14
|
+
|
|
15
|
+
# Add n and s properties to Constant so it can stand in for Num, Str and Bytes
|
|
16
|
+
Constant.n = property(lambda self: self.value, lambda self, value: setattr(self, 'value', value)) # type: ignore[assignment]
|
|
17
|
+
Constant.s = property(lambda self: self.value, lambda self, value: setattr(self, 'value', value)) # type: ignore[assignment]
|
|
18
|
+
|
|
19
|
+
# These classes are redefined from the ones in ast that complain about deprecation
|
|
20
|
+
# They will continue to work once they are removed from ast
|
|
21
|
+
|
|
22
|
+
class Str(Constant): # type: ignore[no-redef]
|
|
23
|
+
def __new__(cls, s, *args, **kwargs):
|
|
24
|
+
return Constant(value=s, *args, **kwargs)
|
|
25
|
+
|
|
26
|
+
class Bytes(Constant): # type: ignore[no-redef]
|
|
27
|
+
def __new__(cls, s, *args, **kwargs):
|
|
28
|
+
return Constant(value=s, *args, **kwargs)
|
|
29
|
+
|
|
30
|
+
class Num(Constant): # type: ignore[no-redef]
|
|
31
|
+
def __new__(cls, n, *args, **kwargs):
|
|
32
|
+
return Constant(value=n, *args, **kwargs)
|
|
33
|
+
|
|
34
|
+
class NameConstant(Constant): # type: ignore[no-redef]
|
|
35
|
+
def __new__(cls, *args, **kwargs):
|
|
36
|
+
return Constant(*args, **kwargs)
|
|
37
|
+
|
|
38
|
+
class Ellipsis(Constant): # type: ignore[no-redef]
|
|
39
|
+
def __new__(cls, *args, **kwargs):
|
|
40
|
+
return Constant(value=literal_eval('...'), *args, **kwargs)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Print a representation of an AST
|
|
3
|
+
|
|
4
|
+
This prints a human readable representation of the nodes in the AST.
|
|
5
|
+
The goal is to make it easy to see what the AST looks like, and to
|
|
6
|
+
make it easy to compare two ASTs.
|
|
7
|
+
|
|
8
|
+
This is not intended to be a complete representation of the AST, some
|
|
9
|
+
fields or field names may be omitted for clarity. It should still be precise and unambiguous.
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import python_minifier.ast_compat as ast
|
|
14
|
+
|
|
15
|
+
from python_minifier.util import is_ast_node
|
|
16
|
+
|
|
17
|
+
INDENT = ' '
|
|
18
|
+
|
|
19
|
+
# The field name that can be omitted for each node
|
|
20
|
+
# Either it's the only field or would otherwise be obvious
|
|
21
|
+
default_fields = {
|
|
22
|
+
'Constant': 'value',
|
|
23
|
+
'Num': 'n',
|
|
24
|
+
'Str': 's',
|
|
25
|
+
'Bytes': 's',
|
|
26
|
+
'NameConstant': 'value',
|
|
27
|
+
'FormattedValue': 'value',
|
|
28
|
+
'JoinedStr': 'values',
|
|
29
|
+
'List': 'elts',
|
|
30
|
+
'Tuple': 'elts',
|
|
31
|
+
'Set': 'elts',
|
|
32
|
+
'Name': 'id',
|
|
33
|
+
'Expr': 'value',
|
|
34
|
+
'UnaryOp': 'op',
|
|
35
|
+
'BinOp': 'op',
|
|
36
|
+
'BoolOp': 'op',
|
|
37
|
+
'Call': 'func',
|
|
38
|
+
'Index': 'value',
|
|
39
|
+
'ExtSlice': 'dims',
|
|
40
|
+
'Assert': 'test',
|
|
41
|
+
'Delete': 'targets',
|
|
42
|
+
'Import': 'names',
|
|
43
|
+
'If': 'test',
|
|
44
|
+
'While': 'test',
|
|
45
|
+
'Try': 'handlers',
|
|
46
|
+
'TryExcept': 'handlers',
|
|
47
|
+
'With': 'items',
|
|
48
|
+
'withitem': 'context_expr',
|
|
49
|
+
'FunctionDef': 'name',
|
|
50
|
+
'arg': 'arg',
|
|
51
|
+
'Return': 'value',
|
|
52
|
+
'Yield': 'value',
|
|
53
|
+
'YieldFrom': 'value',
|
|
54
|
+
'Global': 'names',
|
|
55
|
+
'Nonlocal': 'names',
|
|
56
|
+
'ClassDef': 'name',
|
|
57
|
+
'AsyncFunctionDef': 'name',
|
|
58
|
+
'Await': 'value',
|
|
59
|
+
'AsyncWith': 'items',
|
|
60
|
+
'Raise': 'exc',
|
|
61
|
+
'Subscript': 'value',
|
|
62
|
+
'Attribute': 'value',
|
|
63
|
+
'AugAssign': 'op',
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
def is_literal(node, field):
|
|
67
|
+
if hasattr(ast, 'Constant') and isinstance(node, ast.Constant) and field == 'value':
|
|
68
|
+
return True
|
|
69
|
+
|
|
70
|
+
if is_ast_node(node, ast.Num) and field == 'n':
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
if is_ast_node(node, ast.Str) and field == 's':
|
|
74
|
+
return True
|
|
75
|
+
|
|
76
|
+
if is_ast_node(node, 'Bytes') and field == 's':
|
|
77
|
+
return True
|
|
78
|
+
|
|
79
|
+
if is_ast_node(node, 'NameConstant') and field == 'value':
|
|
80
|
+
return True
|
|
81
|
+
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
def print_ast(node):
|
|
85
|
+
if not isinstance(node, ast.AST):
|
|
86
|
+
return repr(node)
|
|
87
|
+
|
|
88
|
+
s = ''
|
|
89
|
+
|
|
90
|
+
node_name = node.__class__.__name__
|
|
91
|
+
s += node_name
|
|
92
|
+
s += '('
|
|
93
|
+
|
|
94
|
+
first = True
|
|
95
|
+
for field, value in ast.iter_fields(node):
|
|
96
|
+
if not value and not is_literal(node, field):
|
|
97
|
+
# Don't bother printing fields that are empty, except for literals
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
if field == 'ctx':
|
|
101
|
+
# Don't print the ctx, it's always apparent from context
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
if first:
|
|
105
|
+
first = False
|
|
106
|
+
else:
|
|
107
|
+
s += ', '
|
|
108
|
+
|
|
109
|
+
if default_fields.get(node_name) != field:
|
|
110
|
+
s += field + '='
|
|
111
|
+
|
|
112
|
+
if isinstance(value, ast.AST):
|
|
113
|
+
s += print_ast(value)
|
|
114
|
+
elif isinstance(value, list):
|
|
115
|
+
s += '['
|
|
116
|
+
first_list = True
|
|
117
|
+
for item in value:
|
|
118
|
+
if first_list:
|
|
119
|
+
first_list = False
|
|
120
|
+
else:
|
|
121
|
+
s += ','
|
|
122
|
+
|
|
123
|
+
for line in print_ast(item).splitlines():
|
|
124
|
+
s += '\n' + INDENT + line
|
|
125
|
+
s += '\n]'
|
|
126
|
+
else:
|
|
127
|
+
s += repr(value)
|
|
128
|
+
|
|
129
|
+
s += ')'
|
|
130
|
+
return s
|