]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-133367: Add missing options to `ast` CLI (#133369)
authorSemyon Moroz <donbarbos@proton.me>
Mon, 5 May 2025 17:17:43 +0000 (21:17 +0400)
committerGitHub <noreply@github.com>
Mon, 5 May 2025 17:17:43 +0000 (17:17 +0000)
Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com>
Co-authored-by: sobolevn <mail@sobolevn.me>
Doc/library/ast.rst
Doc/whatsnew/3.14.rst
Lib/ast.py
Lib/test/test_ast/test_ast.py
Misc/NEWS.d/next/Library/2025-05-04-13-40-05.gh-issue-133367.E5nl2u.rst [new file with mode: 0644]

index 776c63d1f0fda0200fbb25120ceac1ab217ab7ce..c9ae0abdd663c7786e581a5f7c0bb1213e70733a 100644 (file)
@@ -1,4 +1,4 @@
-:mod:`!ast` --- Abstract Syntax Trees
+:mod:`!ast` --- Abstract syntax trees
 =====================================
 
 .. module:: ast
@@ -29,7 +29,7 @@ compiled into a Python code object using the built-in :func:`compile` function.
 
 .. _abstract-grammar:
 
-Abstract Grammar
+Abstract grammar
 ----------------
 
 The abstract grammar is currently defined as follows:
@@ -2156,10 +2156,10 @@ Async and await
    of :class:`ast.operator`, :class:`ast.unaryop`, :class:`ast.cmpop`,
    :class:`ast.boolop` and :class:`ast.expr_context`) on the returned tree
    will be singletons. Changes to one will be reflected in all other
-   occurrences of the same value (e.g. :class:`ast.Add`).
+   occurrences of the same value (for example, :class:`ast.Add`).
 
 
-:mod:`ast` Helpers
+:mod:`ast` helpers
 ------------------
 
 Apart from the node classes, the :mod:`ast` module defines these utility functions
@@ -2484,7 +2484,7 @@ and classes for traversing abstract syntax trees:
 
 .. _ast-compiler-flags:
 
-Compiler Flags
+Compiler flags
 --------------
 
 The following flags may be passed to :func:`compile` in order to change
@@ -2533,7 +2533,7 @@ effects on the compilation of a program:
 
 .. _ast-cli:
 
-Command-Line Usage
+Command-line usage
 ------------------
 
 .. versionadded:: 3.9
@@ -2572,6 +2572,28 @@ The following options are accepted:
 
    Indentation of nodes in AST (number of spaces).
 
+.. option:: --feature-version <version>
+
+   Python version in the format 3.x (for example, 3.10). Defaults to the
+   current version of the interpreter.
+
+   .. versionadded:: next
+
+.. option:: -O <level>
+            --optimize <level>
+
+   Optimization level for parser. Defaults to no optimization.
+
+   .. versionadded:: next
+
+.. option:: --show-empty
+
+   Show empty lists and fields that are ``None``. Defaults to not showing empty
+   objects.
+
+   .. versionadded:: next
+
+
 If :file:`infile` is specified its contents are parsed to AST and dumped
 to stdout.  Otherwise, the content is read from stdin.
 
index 68ffedf4729a9356eba5d1d5f3d996ef1378cb0a..0c4bd8600858f6cb72eec146eddb264726328227 100644 (file)
@@ -875,6 +875,11 @@ ast
   that the root node type is appropriate.
   (Contributed by Irit Katriel in :gh:`130139`.)
 
+* Add new ``--feature-version``, ``--optimize``, ``--show-empty`` options to
+  command-line interface.
+  (Contributed by Semyon Moroz in :gh:`133367`.)
+
+
 bdb
 ---
 
index af4fe8ff5a8e9540771113299bbb272572f1fbb2..be6ed0805d63ddbbd158c1bdb16133c2a0a455a8 100644 (file)
@@ -643,6 +643,15 @@ def main(args=None):
                              'column offsets')
     parser.add_argument('-i', '--indent', type=int, default=3,
                         help='indentation of nodes (number of spaces)')
+    parser.add_argument('--feature-version',
+                        type=str, default=None, metavar='VERSION',
+                        help='Python version in the format 3.x '
+                             '(for example, 3.10)')
+    parser.add_argument('-O', '--optimize',
+                        type=int, default=-1, metavar='LEVEL',
+                        help='optimization level for parser (default -1)')
+    parser.add_argument('--show-empty', default=False, action='store_true',
+                        help='show empty lists and fields in dump output')
     args = parser.parse_args(args)
 
     if args.infile == '-':
@@ -652,8 +661,22 @@ def main(args=None):
         name = args.infile
         with open(args.infile, 'rb') as infile:
             source = infile.read()
-    tree = parse(source, name, args.mode, type_comments=args.no_type_comments)
-    print(dump(tree, include_attributes=args.include_attributes, indent=args.indent))
+
+    # Process feature_version
+    feature_version = None
+    if args.feature_version:
+        try:
+            major, minor = map(int, args.feature_version.split('.', 1))
+        except ValueError:
+            parser.error('Invalid format for --feature-version; '
+                         'expected format 3.x (for example, 3.10)')
+
+        feature_version = (major, minor)
+
+    tree = parse(source, name, args.mode, type_comments=args.no_type_comments,
+                 feature_version=feature_version, optimize=args.optimize)
+    print(dump(tree, include_attributes=args.include_attributes,
+               indent=args.indent, show_empty=args.show_empty))
 
 if __name__ == '__main__':
     main()
index 6a9b7812ef6185a61ce2c6ab6740b5393c553628..ae82395e9a005a89ed0cfc994a414a14a135e3d5 100644 (file)
@@ -3272,6 +3272,9 @@ class CommandLineTests(unittest.TestCase):
             ('--no-type-comments', '--no-type-comments'),
             ('-a', '--include-attributes'),
             ('-i=4', '--indent=4'),
+            ('--feature-version=3.13', '--feature-version=3.13'),
+            ('-O=-1', '--optimize=-1'),
+            ('--show-empty', '--show-empty'),
         )
         self.set_source('''
             print(1, 2, 3)
@@ -3389,7 +3392,7 @@ class CommandLineTests(unittest.TestCase):
                 self.check_output(source, expect, flag)
 
     def test_indent_flag(self):
-        # test 'python -m ast -i/--indent'
+        # test 'python -m ast -i/--indent 0'
         source = 'pass'
         expect = '''
             Module(
@@ -3400,6 +3403,96 @@ class CommandLineTests(unittest.TestCase):
             with self.subTest(flag=flag):
                 self.check_output(source, expect, flag)
 
+    def test_feature_version_flag(self):
+        # test 'python -m ast --feature-version 3.9/3.10'
+        source = '''
+            match x:
+                case 1:
+                    pass
+        '''
+        expect = '''
+            Module(
+               body=[
+                  Match(
+                     subject=Name(id='x', ctx=Load()),
+                     cases=[
+                        match_case(
+                           pattern=MatchValue(
+                              value=Constant(value=1)),
+                           body=[
+                              Pass()])])])
+        '''
+        self.check_output(source, expect, '--feature-version=3.10')
+        with self.assertRaises(SyntaxError):
+            self.invoke_ast('--feature-version=3.9')
+
+    def test_no_optimize_flag(self):
+        # test 'python -m ast -O/--optimize -1/0'
+        source = '''
+            match a:
+                case 1+2j:
+                    pass
+        '''
+        expect = '''
+            Module(
+               body=[
+                  Match(
+                     subject=Name(id='a', ctx=Load()),
+                     cases=[
+                        match_case(
+                           pattern=MatchValue(
+                              value=BinOp(
+                                 left=Constant(value=1),
+                                 op=Add(),
+                                 right=Constant(value=2j))),
+                           body=[
+                              Pass()])])])
+        '''
+        for flag in ('-O=-1', '--optimize=-1', '-O=0', '--optimize=0'):
+            with self.subTest(flag=flag):
+                self.check_output(source, expect, flag)
+
+    def test_optimize_flag(self):
+        # test 'python -m ast -O/--optimize 1/2'
+        source = '''
+            match a:
+                case 1+2j:
+                    pass
+        '''
+        expect = '''
+            Module(
+               body=[
+                  Match(
+                     subject=Name(id='a', ctx=Load()),
+                     cases=[
+                        match_case(
+                           pattern=MatchValue(
+                              value=Constant(value=(1+2j))),
+                           body=[
+                              Pass()])])])
+        '''
+        for flag in ('-O=1', '--optimize=1', '-O=2', '--optimize=2'):
+            with self.subTest(flag=flag):
+                self.check_output(source, expect, flag)
+
+    def test_show_empty_flag(self):
+        # test 'python -m ast --show-empty'
+        source = 'print(1, 2, 3)'
+        expect = '''
+            Module(
+               body=[
+                  Expr(
+                     value=Call(
+                        func=Name(id='print', ctx=Load()),
+                        args=[
+                           Constant(value=1),
+                           Constant(value=2),
+                           Constant(value=3)],
+                        keywords=[]))],
+               type_ignores=[])
+        '''
+        self.check_output(source, expect, '--show-empty')
+
 
 class ASTOptimiziationTests(unittest.TestCase):
     def wrap_expr(self, expr):
diff --git a/Misc/NEWS.d/next/Library/2025-05-04-13-40-05.gh-issue-133367.E5nl2u.rst b/Misc/NEWS.d/next/Library/2025-05-04-13-40-05.gh-issue-133367.E5nl2u.rst
new file mode 100644 (file)
index 0000000..1c1b035
--- /dev/null
@@ -0,0 +1,2 @@
+Add the ``--feature-version``, ``--optimize``, and ``--show-empty`` options
+to the :mod:`ast` command-line interface. Patch by Semyon Moroz.