python-minifier 2.9.0__tar.gz → 2.11.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.9.0 → python_minifier-2.11.0}/PKG-INFO +13 -7
- {python_minifier-2.9.0 → python_minifier-2.11.0}/README.md +8 -5
- {python_minifier-2.9.0 → python_minifier-2.11.0}/setup.py +6 -4
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/__init__.py +43 -5
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/__init__.pyi +6 -3
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/__main__.py +86 -16
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/ast_compare.py +1 -1
- python_minifier-2.11.0/src/python_minifier/ast_compat.py +40 -0
- python_minifier-2.11.0/src/python_minifier/ast_printer.py +130 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/expression_printer.py +11 -6
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/f_string.py +41 -21
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/ministring.py +7 -3
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/module_printer.py +75 -7
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/rename/bind_names.py +24 -6
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/rename/binding.py +48 -3
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/rename/mapper.py +16 -1
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/rename/rename_literals.py +17 -2
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/rename/renamer.py +1 -1
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/rename/resolve_names.py +28 -10
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/rename/util.py +10 -1
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/token_printer.py +4 -2
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/transforms/combine_imports.py +8 -5
- python_minifier-2.11.0/src/python_minifier/transforms/constant_folding.py +114 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/transforms/remove_annotations.py +25 -5
- python_minifier-2.11.0/src/python_minifier/transforms/remove_annotations_options.py +35 -0
- python_minifier-2.11.0/src/python_minifier/transforms/remove_annotations_options.pyi +17 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/transforms/remove_asserts.py +1 -1
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/transforms/remove_debug.py +1 -1
- python_minifier-2.11.0/src/python_minifier/transforms/remove_exception_brackets.py +124 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/transforms/remove_explicit_return_none.py +1 -1
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/transforms/remove_literal_statements.py +1 -1
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/transforms/remove_object_base.py +4 -1
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/transforms/remove_pass.py +1 -1
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/transforms/remove_posargs.py +1 -1
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/transforms/suite_transformer.py +4 -1
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/util.py +7 -7
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier.egg-info/PKG-INFO +14 -8
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier.egg-info/SOURCES.txt +18 -2
- python_minifier-2.11.0/test/test_bind_names.py +1354 -0
- python_minifier-2.11.0/test/test_bind_names_python2.py +605 -0
- python_minifier-2.11.0/test/test_bind_names_python312.py +158 -0
- python_minifier-2.11.0/test/test_bind_names_python313.py +189 -0
- python_minifier-2.11.0/test/test_bind_names_python33.py +620 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_combine_imports.py +80 -0
- python_minifier-2.11.0/test/test_folding.py +136 -0
- python_minifier-2.11.0/test/test_fstring.py +103 -0
- python_minifier-2.11.0/test/test_generic_types.py +278 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_hoist_literals.py +26 -0
- python_minifier-2.11.0/test/test_is_ast_node.py +40 -0
- python_minifier-2.11.0/test/test_remove_annotations.py +325 -0
- python_minifier-2.11.0/test/test_remove_exception_brackets.py +165 -0
- python_minifier-2.11.0/test/test_type_param_defaults.py +89 -0
- python_minifier-2.9.0/test/test_empty_fstring.py +0 -19
- python_minifier-2.9.0/test/test_remove_annotations.py +0 -218
- {python_minifier-2.9.0 → python_minifier-2.11.0}/LICENSE +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/setup.cfg +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/py.typed +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/rename/__init__.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/rename/name_generator.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier/transforms/__init__.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier.egg-info/dependency_links.txt +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier.egg-info/entry_points.txt +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier.egg-info/top_level.txt +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/src/python_minifier.egg-info/zip-safe +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_assignment_expressions.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_await_fstring.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_comprehension_rename.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_decorator_expressions.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_dict_expansion.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_import.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_iterable_unpacking.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_match.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_match_rename.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_name_generator.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_nonlocal.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_posargs.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_preserve_shebang.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_remove_assert.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_remove_debug.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_remove_explicit_return_none.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_remove_literal_statements.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_remove_object.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_remove_pass.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_rename_builtins.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.0}/test/test_rename_locals.py +0 -0
- {python_minifier-2.9.0 → python_minifier-2.11.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.11.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
|
|
@@ -8,6 +8,7 @@ Author-email: daniel@flook.org
|
|
|
8
8
|
License: MIT
|
|
9
9
|
Project-URL: Issues, https://github.com/dflook/python-minifier/issues
|
|
10
10
|
Project-URL: Documentation, https://dflook.github.io/python-minifier/
|
|
11
|
+
Project-URL: Changelog, https://github.com/dflook/python-minifier/blob/main/CHANGELOG.md
|
|
11
12
|
Keywords: minify minifier
|
|
12
13
|
Classifier: Development Status :: 5 - Production/Stable
|
|
13
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -22,13 +23,15 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
22
23
|
Classifier: Programming Language :: Python :: 3.9
|
|
23
24
|
Classifier: Programming Language :: Python :: 3.10
|
|
24
25
|
Classifier: Programming Language :: Python :: 3.11
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
28
|
Classifier: Programming Language :: Python :: 2
|
|
26
29
|
Classifier: Programming Language :: Python :: 2.7
|
|
27
30
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
28
31
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
29
32
|
Classifier: Intended Audience :: Developers
|
|
30
33
|
Classifier: Topic :: Software Development
|
|
31
|
-
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.
|
|
34
|
+
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.14
|
|
32
35
|
Description-Content-Type: text/markdown
|
|
33
36
|
License-File: LICENSE
|
|
34
37
|
|
|
@@ -38,7 +41,7 @@ Transforms Python source code into its most compact representation.
|
|
|
38
41
|
|
|
39
42
|
[Try it out!](https://python-minifier.com)
|
|
40
43
|
|
|
41
|
-
python-minifier currently supports Python 2.7 and Python 3.3 to 3.
|
|
44
|
+
python-minifier currently supports Python 2.7 and Python 3.3 to 3.13. Previous releases supported Python 2.6.
|
|
42
45
|
|
|
43
46
|
* [PyPI](https://pypi.org/project/python-minifier/)
|
|
44
47
|
* [Documentation](https://dflook.github.io/python-minifier/)
|
|
@@ -138,10 +141,13 @@ To install python-minifier use pip:
|
|
|
138
141
|
$ pip install python-minifier
|
|
139
142
|
```
|
|
140
143
|
|
|
141
|
-
Note that python-minifier depends on the python interpreter for parsing source code,
|
|
142
|
-
|
|
144
|
+
Note that python-minifier depends on the python interpreter for parsing source code,
|
|
145
|
+
and outputs source code compatible with the version of the interpreter it is run with.
|
|
143
146
|
|
|
144
|
-
|
|
147
|
+
This means that if you minify code written for Python 3.11 using python-minifier running with Python 3.12,
|
|
148
|
+
the minified code may only run with Python 3.12.
|
|
149
|
+
|
|
150
|
+
python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.13.
|
|
145
151
|
|
|
146
152
|
## Usage
|
|
147
153
|
|
|
@@ -166,4 +172,4 @@ Documentation is available at [dflook.github.io/python-minifier/](https://dflook
|
|
|
166
172
|
|
|
167
173
|
Available under the MIT License. Full text is in the [LICENSE](LICENSE) file.
|
|
168
174
|
|
|
169
|
-
Copyright (c)
|
|
175
|
+
Copyright (c) 2024 Daniel Flook
|
|
@@ -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.13. 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/)
|
|
@@ -104,10 +104,13 @@ To install python-minifier use pip:
|
|
|
104
104
|
$ pip install python-minifier
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
Note that python-minifier depends on the python interpreter for parsing source code,
|
|
108
|
-
|
|
107
|
+
Note that python-minifier depends on the python interpreter for parsing source code,
|
|
108
|
+
and outputs source code compatible with the version of the interpreter it is run with.
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
This means that if you minify code written for Python 3.11 using python-minifier running with Python 3.12,
|
|
111
|
+
the minified code may only run with Python 3.12.
|
|
112
|
+
|
|
113
|
+
python-minifier runs with and can minify code written for Python 2.7 and Python 3.3 to 3.13.
|
|
111
114
|
|
|
112
115
|
## Usage
|
|
113
116
|
|
|
@@ -132,4 +135,4 @@ Documentation is available at [dflook.github.io/python-minifier/](https://dflook
|
|
|
132
135
|
|
|
133
136
|
Available under the MIT License. Full text is in the [LICENSE](LICENSE) file.
|
|
134
137
|
|
|
135
|
-
Copyright (c)
|
|
138
|
+
Copyright (c) 2024 Daniel Flook
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import os.path
|
|
2
|
-
import sys
|
|
3
2
|
|
|
4
3
|
from setuptools import setup, find_packages
|
|
5
4
|
|
|
@@ -17,18 +16,19 @@ setup(
|
|
|
17
16
|
project_urls={
|
|
18
17
|
'Issues': 'https://github.com/dflook/python-minifier/issues',
|
|
19
18
|
'Documentation': 'https://dflook.github.io/python-minifier/',
|
|
19
|
+
'Changelog': 'https://github.com/dflook/python-minifier/blob/main/CHANGELOG.md'
|
|
20
20
|
},
|
|
21
21
|
keywords='minify minifier',
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
package_dir={'': 'src'},
|
|
25
25
|
packages=find_packages('src'),
|
|
26
|
-
package_data={"python_minifier": ["py.typed", "
|
|
26
|
+
package_data={"python_minifier": ["py.typed", "*.pyi", "rename/*.pyi", "transforms/*.pyi"]},
|
|
27
27
|
long_description=long_desc,
|
|
28
28
|
long_description_content_type='text/markdown',
|
|
29
29
|
|
|
30
|
-
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.
|
|
31
|
-
version='2.
|
|
30
|
+
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <3.14',
|
|
31
|
+
version='2.11.0',
|
|
32
32
|
|
|
33
33
|
classifiers=[
|
|
34
34
|
'Development Status :: 5 - Production/Stable',
|
|
@@ -44,6 +44,8 @@ setup(
|
|
|
44
44
|
'Programming Language :: Python :: 3.9',
|
|
45
45
|
'Programming Language :: Python :: 3.10',
|
|
46
46
|
'Programming Language :: Python :: 3.11',
|
|
47
|
+
'Programming Language :: Python :: 3.12',
|
|
48
|
+
'Programming Language :: Python :: 3.13',
|
|
47
49
|
'Programming Language :: Python :: 2',
|
|
48
50
|
'Programming Language :: Python :: 2.7',
|
|
49
51
|
'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,10 +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
|
|
26
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
|
|
27
30
|
from python_minifier.transforms.remove_literal_statements import RemoveLiteralStatements
|
|
28
31
|
from python_minifier.transforms.remove_object_base import RemoveObject
|
|
29
32
|
from python_minifier.transforms.remove_pass import RemovePass
|
|
@@ -52,7 +55,7 @@ class UnstableMinification(RuntimeError):
|
|
|
52
55
|
def minify(
|
|
53
56
|
source,
|
|
54
57
|
filename=None,
|
|
55
|
-
remove_annotations=
|
|
58
|
+
remove_annotations=RemoveAnnotationsOptions(),
|
|
56
59
|
remove_pass=True,
|
|
57
60
|
remove_literal_statements=False,
|
|
58
61
|
combine_imports=True,
|
|
@@ -67,6 +70,8 @@ def minify(
|
|
|
67
70
|
remove_asserts=False,
|
|
68
71
|
remove_debug=False,
|
|
69
72
|
remove_explicit_return_none=True,
|
|
73
|
+
remove_builtin_exception_brackets=True,
|
|
74
|
+
constant_folding=True
|
|
70
75
|
):
|
|
71
76
|
"""
|
|
72
77
|
Minify a python module
|
|
@@ -80,7 +85,8 @@ def minify(
|
|
|
80
85
|
:param str source: The python module source code
|
|
81
86
|
:param str filename: The original source filename if known
|
|
82
87
|
|
|
83
|
-
: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
|
|
84
90
|
:param bool remove_pass: If Pass statements should be removed where possible
|
|
85
91
|
:param bool remove_literal_statements: If statements consisting of a single literal should be removed, including docstrings
|
|
86
92
|
:param bool combine_imports: Combine adjacent import statements where possible
|
|
@@ -97,6 +103,8 @@ def minify(
|
|
|
97
103
|
:param bool remove_asserts: If assert statements should be removed
|
|
98
104
|
:param bool remove_debug: If conditional statements that test '__debug__ is True' should be removed
|
|
99
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
|
|
100
108
|
|
|
101
109
|
:rtype: str
|
|
102
110
|
|
|
@@ -115,8 +123,20 @@ def minify(
|
|
|
115
123
|
if combine_imports:
|
|
116
124
|
module = CombineImports()(module)
|
|
117
125
|
|
|
118
|
-
if remove_annotations:
|
|
119
|
-
|
|
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)
|
|
120
140
|
|
|
121
141
|
if remove_pass:
|
|
122
142
|
module = RemovePass()(module)
|
|
@@ -133,13 +153,31 @@ def minify(
|
|
|
133
153
|
if remove_explicit_return_none:
|
|
134
154
|
module = RemoveExplicitReturnNone()(module)
|
|
135
155
|
|
|
156
|
+
if constant_folding:
|
|
157
|
+
module = FoldConstants()(module)
|
|
158
|
+
|
|
136
159
|
bind_names(module)
|
|
137
160
|
resolve_names(module)
|
|
138
161
|
|
|
162
|
+
if remove_builtin_exception_brackets and not module.tainted:
|
|
163
|
+
remove_no_arg_exception_call(module)
|
|
164
|
+
|
|
139
165
|
if module.tainted:
|
|
140
166
|
rename_globals = False
|
|
141
167
|
rename_locals = False
|
|
142
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
|
+
|
|
143
181
|
allow_rename_locals(module, rename_locals, preserve_locals)
|
|
144
182
|
allow_rename_globals(module, rename_globals, preserve_globals)
|
|
145
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 = ...,
|
|
@@ -22,7 +23,9 @@ def minify(
|
|
|
22
23
|
preserve_shebang: bool = ...,
|
|
23
24
|
remove_asserts: bool = ...,
|
|
24
25
|
remove_debug: bool = ...,
|
|
25
|
-
remove_explicit_return_none: bool =
|
|
26
|
+
remove_explicit_return_none: bool = ...,
|
|
27
|
+
remove_builtin_exception_brackets: bool = ...,
|
|
28
|
+
constant_folding: bool = ...
|
|
26
29
|
) -> Text: ...
|
|
27
30
|
|
|
28
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',
|
|
@@ -189,6 +191,51 @@ def parse_args():
|
|
|
189
191
|
help='Replace explicit return None with a bare return',
|
|
190
192
|
dest='remove_explicit_return_none',
|
|
191
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
|
+
)
|
|
238
|
+
|
|
192
239
|
parser.add_argument('--version', '-v', action='version', version=version)
|
|
193
240
|
|
|
194
241
|
args = parser.parse_args()
|
|
@@ -207,8 +254,13 @@ def parse_args():
|
|
|
207
254
|
sys.stderr.write('error: path ' + args.path[0] + ' is a directory, --in-place required\n')
|
|
208
255
|
sys.exit(1)
|
|
209
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
|
+
|
|
210
261
|
return args
|
|
211
262
|
|
|
263
|
+
|
|
212
264
|
def source_modules(args):
|
|
213
265
|
|
|
214
266
|
def error(os_error):
|
|
@@ -223,6 +275,7 @@ def source_modules(args):
|
|
|
223
275
|
else:
|
|
224
276
|
yield path_arg
|
|
225
277
|
|
|
278
|
+
|
|
226
279
|
def do_minify(source, filename, minification_args):
|
|
227
280
|
|
|
228
281
|
preserve_globals = []
|
|
@@ -237,12 +290,27 @@ def do_minify(source, filename, minification_args):
|
|
|
237
290
|
names = [name.strip() for name in arg.split(',') if name]
|
|
238
291
|
preserve_locals.extend(names)
|
|
239
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
|
+
|
|
240
308
|
return minify(
|
|
241
309
|
source,
|
|
242
310
|
filename=filename,
|
|
243
311
|
combine_imports=minification_args.combine_imports,
|
|
244
312
|
remove_pass=minification_args.remove_pass,
|
|
245
|
-
remove_annotations=
|
|
313
|
+
remove_annotations=remove_annotations,
|
|
246
314
|
remove_literal_statements=minification_args.remove_literal_statements,
|
|
247
315
|
hoist_literals=minification_args.hoist_literals,
|
|
248
316
|
rename_locals=minification_args.rename_locals,
|
|
@@ -255,6 +323,8 @@ def do_minify(source, filename, minification_args):
|
|
|
255
323
|
remove_asserts=minification_args.remove_asserts,
|
|
256
324
|
remove_debug=minification_args.remove_debug,
|
|
257
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
|
|
258
328
|
)
|
|
259
329
|
|
|
260
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
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import ast
|
|
1
|
+
import python_minifier.ast_compat as ast
|
|
2
2
|
import sys
|
|
3
3
|
|
|
4
4
|
from python_minifier.util import is_ast_node
|
|
@@ -69,7 +69,7 @@ class ExpressionPrinter(object):
|
|
|
69
69
|
|
|
70
70
|
# Python2 parses negative ints as an ast.Num with a negative value.
|
|
71
71
|
# Make sure the Num get the precedence of the USub operator in this case.
|
|
72
|
-
if sys.version_info < (3, 0) and
|
|
72
|
+
if sys.version_info < (3, 0) and is_ast_node(node, ast.Num):
|
|
73
73
|
if str(node.n)[0] == '-':
|
|
74
74
|
return self.precedences['USub']
|
|
75
75
|
|
|
@@ -208,7 +208,7 @@ class ExpressionPrinter(object):
|
|
|
208
208
|
def visit_UnaryOp(self, node):
|
|
209
209
|
self.visit(node.op)
|
|
210
210
|
|
|
211
|
-
if sys.version_info < (3, 0) and isinstance(node.op, ast.USub) and
|
|
211
|
+
if sys.version_info < (3, 0) and isinstance(node.op, ast.USub) and is_ast_node(node.operand, ast.Num):
|
|
212
212
|
# For: -(1), which is parsed as a UnaryOp(USub, Num(1)).
|
|
213
213
|
# Without this special case it would be printed as -1
|
|
214
214
|
# This is fine, but python 2 will then parse it at Num(-1) so the AST wouldn't round-trip.
|
|
@@ -428,7 +428,7 @@ class ExpressionPrinter(object):
|
|
|
428
428
|
value_precedence = self.precedence(node.value)
|
|
429
429
|
attr_precedence = self.precedence(node)
|
|
430
430
|
|
|
431
|
-
if (value_precedence != 0 and (attr_precedence > value_precedence)) or
|
|
431
|
+
if (value_precedence != 0 and (attr_precedence > value_precedence)) or is_ast_node(node.value, ast.Num):
|
|
432
432
|
self.printer.delimiter('(')
|
|
433
433
|
self._expression(node.value)
|
|
434
434
|
self.printer.delimiter(')')
|
|
@@ -462,7 +462,7 @@ class ExpressionPrinter(object):
|
|
|
462
462
|
self.visit_Slice(node.slice)
|
|
463
463
|
elif isinstance(node.slice, ast.ExtSlice):
|
|
464
464
|
self.visit_ExtSlice(node.slice)
|
|
465
|
-
elif
|
|
465
|
+
elif is_ast_node(node.slice, ast.Ellipsis):
|
|
466
466
|
self.visit_Ellipsis(node)
|
|
467
467
|
elif sys.version_info >= (3, 9) and isinstance(node.slice, ast.Tuple):
|
|
468
468
|
self.visit_Tuple(node.slice)
|
|
@@ -735,7 +735,12 @@ class ExpressionPrinter(object):
|
|
|
735
735
|
|
|
736
736
|
import python_minifier.f_string
|
|
737
737
|
|
|
738
|
-
|
|
738
|
+
if sys.version_info < (3, 12):
|
|
739
|
+
pep701 = False
|
|
740
|
+
else:
|
|
741
|
+
pep701 = True
|
|
742
|
+
|
|
743
|
+
self.printer.fstring(str(python_minifier.f_string.OuterFString(node, pep701=pep701)))
|
|
739
744
|
|
|
740
745
|
def visit_NamedExpr(self, node):
|
|
741
746
|
self._expression(node.target)
|