]> granicus.if.org Git - python/commitdiff
bpo-37995: Add an option to ast.dump() to produce a multiline output. (GH-15631)
authorSerhiy Storchaka <storchaka@gmail.com>
Mon, 9 Sep 2019 16:33:13 +0000 (19:33 +0300)
committerGitHub <noreply@github.com>
Mon, 9 Sep 2019 16:33:13 +0000 (19:33 +0300)
Doc/library/ast.rst
Doc/whatsnew/3.9.rst
Lib/ast.py
Lib/test/test_ast.py
Misc/NEWS.d/next/Library/2019-08-31-13-36-09.bpo-37995.rS8HzT.rst [new file with mode: 0644]

index 92bf8912eb53dd1af9bd99750af1b072cfe5a21f..cb8e7ec829bed762b60b32c5f16cece7d1f9a0a9 100644 (file)
@@ -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
index 5670cb59f481b2d5740c9210ba51bcc723834baf..deaefc7539167d011c3ccea70ee7809b8b3fec3c 100644 (file)
@@ -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
 ---------
 
index 5ab023f6c3c0699964e5b181e0ad3f1dd3623d49..498484f19855994a3cd64c4c8e86f38063f3f492 100644 (file)
@@ -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):
index 07bbb4cc7723056eb5b46659241c7a0f141f8367..47e259eb2656c7c331a9e07e293a43b0a1fe1445 100644 (file)
@@ -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),
diff --git a/Misc/NEWS.d/next/Library/2019-08-31-13-36-09.bpo-37995.rS8HzT.rst b/Misc/NEWS.d/next/Library/2019-08-31-13-36-09.bpo-37995.rS8HzT.rst
new file mode 100644 (file)
index 0000000..19482b6
--- /dev/null
@@ -0,0 +1,2 @@
+Added the *indent* option to :func:`ast.dump` which allows it to produce a
+multiline indented output.