Skip to content

Commit 2b4e2b7

Browse files
donBarbosStanFromIrelandsobolevn
authored
gh-133367: Add missing options to ast CLI (#133369)
Co-authored-by: Stan Ulbrych <[email protected]> Co-authored-by: sobolevn <[email protected]>
1 parent 5c245ff commit 2b4e2b7

File tree

5 files changed

+154
-9
lines changed

5 files changed

+154
-9
lines changed

Doc/library/ast.rst

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
:mod:`!ast` --- Abstract Syntax Trees
1+
:mod:`!ast` --- Abstract syntax trees
22
=====================================
33

44
.. module:: ast
@@ -29,7 +29,7 @@ compiled into a Python code object using the built-in :func:`compile` function.
2929

3030
.. _abstract-grammar:
3131

32-
Abstract Grammar
32+
Abstract grammar
3333
----------------
3434

3535
The abstract grammar is currently defined as follows:
@@ -2156,10 +2156,10 @@ Async and await
21562156
of :class:`ast.operator`, :class:`ast.unaryop`, :class:`ast.cmpop`,
21572157
:class:`ast.boolop` and :class:`ast.expr_context`) on the returned tree
21582158
will be singletons. Changes to one will be reflected in all other
2159-
occurrences of the same value (e.g. :class:`ast.Add`).
2159+
occurrences of the same value (for example, :class:`ast.Add`).
21602160

21612161

2162-
:mod:`ast` Helpers
2162+
:mod:`ast` helpers
21632163
------------------
21642164

21652165
Apart from the node classes, the :mod:`ast` module defines these utility functions
@@ -2484,7 +2484,7 @@ and classes for traversing abstract syntax trees:
24842484

24852485
.. _ast-compiler-flags:
24862486

2487-
Compiler Flags
2487+
Compiler flags
24882488
--------------
24892489

24902490
The following flags may be passed to :func:`compile` in order to change
@@ -2533,7 +2533,7 @@ effects on the compilation of a program:
25332533

25342534
.. _ast-cli:
25352535

2536-
Command-Line Usage
2536+
Command-line usage
25372537
------------------
25382538

25392539
.. versionadded:: 3.9
@@ -2572,6 +2572,28 @@ The following options are accepted:
25722572

25732573
Indentation of nodes in AST (number of spaces).
25742574

2575+
.. option:: --feature-version <version>
2576+
2577+
Python version in the format 3.x (for example, 3.10). Defaults to the
2578+
current version of the interpreter.
2579+
2580+
.. versionadded:: next
2581+
2582+
.. option:: -O <level>
2583+
--optimize <level>
2584+
2585+
Optimization level for parser. Defaults to no optimization.
2586+
2587+
.. versionadded:: next
2588+
2589+
.. option:: --show-empty
2590+
2591+
Show empty lists and fields that are ``None``. Defaults to not showing empty
2592+
objects.
2593+
2594+
.. versionadded:: next
2595+
2596+
25752597
If :file:`infile` is specified its contents are parsed to AST and dumped
25762598
to stdout. Otherwise, the content is read from stdin.
25772599

Doc/whatsnew/3.14.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,11 @@ ast
875875
that the root node type is appropriate.
876876
(Contributed by Irit Katriel in :gh:`130139`.)
877877

878+
* Add new ``--feature-version``, ``--optimize``, ``--show-empty`` options to
879+
command-line interface.
880+
(Contributed by Semyon Moroz in :gh:`133367`.)
881+
882+
878883
bdb
879884
---
880885

Lib/ast.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,15 @@ def main(args=None):
643643
'column offsets')
644644
parser.add_argument('-i', '--indent', type=int, default=3,
645645
help='indentation of nodes (number of spaces)')
646+
parser.add_argument('--feature-version',
647+
type=str, default=None, metavar='VERSION',
648+
help='Python version in the format 3.x '
649+
'(for example, 3.10)')
650+
parser.add_argument('-O', '--optimize',
651+
type=int, default=-1, metavar='LEVEL',
652+
help='optimization level for parser (default -1)')
653+
parser.add_argument('--show-empty', default=False, action='store_true',
654+
help='show empty lists and fields in dump output')
646655
args = parser.parse_args(args)
647656

648657
if args.infile == '-':
@@ -652,8 +661,22 @@ def main(args=None):
652661
name = args.infile
653662
with open(args.infile, 'rb') as infile:
654663
source = infile.read()
655-
tree = parse(source, name, args.mode, type_comments=args.no_type_comments)
656-
print(dump(tree, include_attributes=args.include_attributes, indent=args.indent))
664+
665+
# Process feature_version
666+
feature_version = None
667+
if args.feature_version:
668+
try:
669+
major, minor = map(int, args.feature_version.split('.', 1))
670+
except ValueError:
671+
parser.error('Invalid format for --feature-version; '
672+
'expected format 3.x (for example, 3.10)')
673+
674+
feature_version = (major, minor)
675+
676+
tree = parse(source, name, args.mode, type_comments=args.no_type_comments,
677+
feature_version=feature_version, optimize=args.optimize)
678+
print(dump(tree, include_attributes=args.include_attributes,
679+
indent=args.indent, show_empty=args.show_empty))
657680

658681
if __name__ == '__main__':
659682
main()

Lib/test/test_ast/test_ast.py

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3272,6 +3272,9 @@ def test_invocation(self):
32723272
('--no-type-comments', '--no-type-comments'),
32733273
('-a', '--include-attributes'),
32743274
('-i=4', '--indent=4'),
3275+
('--feature-version=3.13', '--feature-version=3.13'),
3276+
('-O=-1', '--optimize=-1'),
3277+
('--show-empty', '--show-empty'),
32753278
)
32763279
self.set_source('''
32773280
print(1, 2, 3)
@@ -3389,7 +3392,7 @@ def test_include_attributes_flag(self):
33893392
self.check_output(source, expect, flag)
33903393

33913394
def test_indent_flag(self):
3392-
# test 'python -m ast -i/--indent'
3395+
# test 'python -m ast -i/--indent 0'
33933396
source = 'pass'
33943397
expect = '''
33953398
Module(
@@ -3400,6 +3403,96 @@ def test_indent_flag(self):
34003403
with self.subTest(flag=flag):
34013404
self.check_output(source, expect, flag)
34023405

3406+
def test_feature_version_flag(self):
3407+
# test 'python -m ast --feature-version 3.9/3.10'
3408+
source = '''
3409+
match x:
3410+
case 1:
3411+
pass
3412+
'''
3413+
expect = '''
3414+
Module(
3415+
body=[
3416+
Match(
3417+
subject=Name(id='x', ctx=Load()),
3418+
cases=[
3419+
match_case(
3420+
pattern=MatchValue(
3421+
value=Constant(value=1)),
3422+
body=[
3423+
Pass()])])])
3424+
'''
3425+
self.check_output(source, expect, '--feature-version=3.10')
3426+
with self.assertRaises(SyntaxError):
3427+
self.invoke_ast('--feature-version=3.9')
3428+
3429+
def test_no_optimize_flag(self):
3430+
# test 'python -m ast -O/--optimize -1/0'
3431+
source = '''
3432+
match a:
3433+
case 1+2j:
3434+
pass
3435+
'''
3436+
expect = '''
3437+
Module(
3438+
body=[
3439+
Match(
3440+
subject=Name(id='a', ctx=Load()),
3441+
cases=[
3442+
match_case(
3443+
pattern=MatchValue(
3444+
value=BinOp(
3445+
left=Constant(value=1),
3446+
op=Add(),
3447+
right=Constant(value=2j))),
3448+
body=[
3449+
Pass()])])])
3450+
'''
3451+
for flag in ('-O=-1', '--optimize=-1', '-O=0', '--optimize=0'):
3452+
with self.subTest(flag=flag):
3453+
self.check_output(source, expect, flag)
3454+
3455+
def test_optimize_flag(self):
3456+
# test 'python -m ast -O/--optimize 1/2'
3457+
source = '''
3458+
match a:
3459+
case 1+2j:
3460+
pass
3461+
'''
3462+
expect = '''
3463+
Module(
3464+
body=[
3465+
Match(
3466+
subject=Name(id='a', ctx=Load()),
3467+
cases=[
3468+
match_case(
3469+
pattern=MatchValue(
3470+
value=Constant(value=(1+2j))),
3471+
body=[
3472+
Pass()])])])
3473+
'''
3474+
for flag in ('-O=1', '--optimize=1', '-O=2', '--optimize=2'):
3475+
with self.subTest(flag=flag):
3476+
self.check_output(source, expect, flag)
3477+
3478+
def test_show_empty_flag(self):
3479+
# test 'python -m ast --show-empty'
3480+
source = 'print(1, 2, 3)'
3481+
expect = '''
3482+
Module(
3483+
body=[
3484+
Expr(
3485+
value=Call(
3486+
func=Name(id='print', ctx=Load()),
3487+
args=[
3488+
Constant(value=1),
3489+
Constant(value=2),
3490+
Constant(value=3)],
3491+
keywords=[]))],
3492+
type_ignores=[])
3493+
'''
3494+
self.check_output(source, expect, '--show-empty')
3495+
34033496

34043497
class ASTOptimiziationTests(unittest.TestCase):
34053498
def wrap_expr(self, expr):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add the ``--feature-version``, ``--optimize``, and ``--show-empty`` options
2+
to the :mod:`ast` command-line interface. Patch by Semyon Moroz.

0 commit comments

Comments
 (0)