mirror of https://github.com/python/cpython
bpo-37995: Add an option to ast.dump() to produce a multiline output. (GH-15631)
This commit is contained in:
parent
92709a263e
commit
850573b836
|
@ -319,7 +319,7 @@ and classes for traversing abstract syntax trees:
|
|||
node = YourTransformer().visit(node)
|
||||
|
||||
|
||||
.. function:: dump(node, annotate_fields=True, include_attributes=False)
|
||||
.. function:: dump(node, annotate_fields=True, include_attributes=False, *, indent=None)
|
||||
|
||||
Return a formatted dump of the tree in *node*. This is mainly useful for
|
||||
debugging purposes. If *annotate_fields* is true (by default),
|
||||
|
@ -329,6 +329,17 @@ and classes for traversing abstract syntax trees:
|
|||
numbers and column offsets are not dumped by default. If this is wanted,
|
||||
*include_attributes* can be set to true.
|
||||
|
||||
If *indent* is a non-negative integer or string, then the tree will be
|
||||
pretty-printed with that indent level. An indent level
|
||||
of 0, negative, or ``""`` will only insert newlines. ``None`` (the default)
|
||||
selects the single line representation. Using a positive integer indent
|
||||
indents that many spaces per level. If *indent* is a string (such as ``"\t"``),
|
||||
that string is used to indent each level.
|
||||
|
||||
.. versionchanged:: 3.9
|
||||
Added the *indent* option.
|
||||
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Green Tree Snakes <https://greentreesnakes.readthedocs.io/>`_, an external documentation resource, has good
|
||||
|
|
|
@ -109,6 +109,14 @@ New Modules
|
|||
Improved Modules
|
||||
================
|
||||
|
||||
ast
|
||||
---
|
||||
|
||||
Added the *indent* option to :func:`~ast.dump` which allows it to produce a
|
||||
multiline indented output.
|
||||
(Contributed by Serhiy Storchaka in :issue:`37995`.)
|
||||
|
||||
|
||||
threading
|
||||
---------
|
||||
|
||||
|
|
45
Lib/ast.py
45
Lib/ast.py
|
@ -96,7 +96,7 @@ def literal_eval(node_or_string):
|
|||
return _convert(node_or_string)
|
||||
|
||||
|
||||
def dump(node, annotate_fields=True, include_attributes=False):
|
||||
def dump(node, annotate_fields=True, include_attributes=False, *, indent=None):
|
||||
"""
|
||||
Return a formatted dump of the tree in node. This is mainly useful for
|
||||
debugging purposes. If annotate_fields is true (by default),
|
||||
|
@ -104,11 +104,21 @@ def dump(node, annotate_fields=True, include_attributes=False):
|
|||
If annotate_fields is false, the result string will be more compact by
|
||||
omitting unambiguous field names. Attributes such as line
|
||||
numbers and column offsets are not dumped by default. If this is wanted,
|
||||
include_attributes can be set to true.
|
||||
include_attributes can be set to true. If indent is a non-negative
|
||||
integer or string, then the tree will be pretty-printed with that indent
|
||||
level. None (the default) selects the single line representation.
|
||||
"""
|
||||
def _format(node):
|
||||
def _format(node, level=0):
|
||||
if indent is not None:
|
||||
level += 1
|
||||
prefix = '\n' + indent * level
|
||||
sep = ',\n' + indent * level
|
||||
else:
|
||||
prefix = ''
|
||||
sep = ', '
|
||||
if isinstance(node, AST):
|
||||
args = []
|
||||
allsimple = True
|
||||
keywords = annotate_fields
|
||||
for field in node._fields:
|
||||
try:
|
||||
|
@ -116,23 +126,36 @@ def dump(node, annotate_fields=True, include_attributes=False):
|
|||
except AttributeError:
|
||||
keywords = True
|
||||
else:
|
||||
value, simple = _format(value, level)
|
||||
allsimple = allsimple and simple
|
||||
if keywords:
|
||||
args.append('%s=%s' % (field, _format(value)))
|
||||
args.append('%s=%s' % (field, value))
|
||||
else:
|
||||
args.append(_format(value))
|
||||
args.append(value)
|
||||
if include_attributes and node._attributes:
|
||||
for a in node._attributes:
|
||||
for attr in node._attributes:
|
||||
try:
|
||||
args.append('%s=%s' % (a, _format(getattr(node, a))))
|
||||
value = getattr(node, attr)
|
||||
except AttributeError:
|
||||
pass
|
||||
return '%s(%s)' % (node.__class__.__name__, ', '.join(args))
|
||||
else:
|
||||
value, simple = _format(value, level)
|
||||
allsimple = allsimple and simple
|
||||
args.append('%s=%s' % (attr, value))
|
||||
if allsimple and len(args) <= 3:
|
||||
return '%s(%s)' % (node.__class__.__name__, ', '.join(args)), not args
|
||||
return '%s(%s%s)' % (node.__class__.__name__, prefix, sep.join(args)), False
|
||||
elif isinstance(node, list):
|
||||
return '[%s]' % ', '.join(_format(x) for x in node)
|
||||
return repr(node)
|
||||
if not node:
|
||||
return '[]', True
|
||||
return '[%s%s]' % (prefix, sep.join(_format(x, level)[0] for x in node)), False
|
||||
return repr(node), True
|
||||
|
||||
if not isinstance(node, AST):
|
||||
raise TypeError('expected AST, got %r' % node.__class__.__name__)
|
||||
return _format(node)
|
||||
if indent is not None and not isinstance(indent, str):
|
||||
indent = ' ' * indent
|
||||
return _format(node)[0]
|
||||
|
||||
|
||||
def copy_location(new_node, old_node):
|
||||
|
|
|
@ -645,6 +645,68 @@ class ASTHelpers_Test(unittest.TestCase):
|
|||
"lineno=1, col_offset=0, end_lineno=1, end_col_offset=24)], type_ignores=[])"
|
||||
)
|
||||
|
||||
def test_dump_indent(self):
|
||||
node = ast.parse('spam(eggs, "and cheese")')
|
||||
self.assertEqual(ast.dump(node, indent=3), """\
|
||||
Module(
|
||||
body=[
|
||||
Expr(
|
||||
value=Call(
|
||||
func=Name(id='spam', ctx=Load()),
|
||||
args=[
|
||||
Name(id='eggs', ctx=Load()),
|
||||
Constant(value='and cheese', kind=None)],
|
||||
keywords=[]))],
|
||||
type_ignores=[])""")
|
||||
self.assertEqual(ast.dump(node, annotate_fields=False, indent='\t'), """\
|
||||
Module(
|
||||
\t[
|
||||
\t\tExpr(
|
||||
\t\t\tCall(
|
||||
\t\t\t\tName('spam', Load()),
|
||||
\t\t\t\t[
|
||||
\t\t\t\t\tName('eggs', Load()),
|
||||
\t\t\t\t\tConstant('and cheese', None)],
|
||||
\t\t\t\t[]))],
|
||||
\t[])""")
|
||||
self.assertEqual(ast.dump(node, include_attributes=True, indent=3), """\
|
||||
Module(
|
||||
body=[
|
||||
Expr(
|
||||
value=Call(
|
||||
func=Name(
|
||||
id='spam',
|
||||
ctx=Load(),
|
||||
lineno=1,
|
||||
col_offset=0,
|
||||
end_lineno=1,
|
||||
end_col_offset=4),
|
||||
args=[
|
||||
Name(
|
||||
id='eggs',
|
||||
ctx=Load(),
|
||||
lineno=1,
|
||||
col_offset=5,
|
||||
end_lineno=1,
|
||||
end_col_offset=9),
|
||||
Constant(
|
||||
value='and cheese',
|
||||
kind=None,
|
||||
lineno=1,
|
||||
col_offset=11,
|
||||
end_lineno=1,
|
||||
end_col_offset=23)],
|
||||
keywords=[],
|
||||
lineno=1,
|
||||
col_offset=0,
|
||||
end_lineno=1,
|
||||
end_col_offset=24),
|
||||
lineno=1,
|
||||
col_offset=0,
|
||||
end_lineno=1,
|
||||
end_col_offset=24)],
|
||||
type_ignores=[])""")
|
||||
|
||||
def test_dump_incomplete(self):
|
||||
node = ast.Raise(lineno=3, col_offset=4)
|
||||
self.assertEqual(ast.dump(node),
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Added the *indent* option to :func:`ast.dump` which allows it to produce a
|
||||
multiline indented output.
|
Loading…
Reference in New Issue