Skip to content

Commit 7b87748

Browse files
committed
Merge remote-tracking branch 'upstream/main' into claude/subprocess-pipe-chaining-01R27VPueru4RfRXYDsV5TmW
# Conflicts: # Lib/subprocess.py
2 parents 3169b93 + 2754e9a commit 7b87748

17 files changed

Lines changed: 408 additions & 184 deletions

Doc/library/ast.rst

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2480,7 +2480,7 @@ and classes for traversing abstract syntax trees:
24802480
node = YourTransformer().visit(node)
24812481

24822482

2483-
.. function:: dump(node, annotate_fields=True, include_attributes=False, *, indent=None, show_empty=False)
2483+
.. function:: dump(node, annotate_fields=True, include_attributes=False, *, color=False, indent=None, show_empty=False)
24842484

24852485
Return a formatted dump of the tree in *node*. This is mainly useful for
24862486
debugging purposes. If *annotate_fields* is true (by default),
@@ -2490,6 +2490,10 @@ and classes for traversing abstract syntax trees:
24902490
numbers and column offsets are not dumped by default. If this is wanted,
24912491
*include_attributes* can be set to true.
24922492

2493+
If *color* is ``True``, the returned string is syntax highlighted using
2494+
ANSI escape sequences.
2495+
If ``False`` (the default), colored output is always disabled.
2496+
24932497
If *indent* is a non-negative integer or string, then the tree will be
24942498
pretty-printed with that indent level. An indent level
24952499
of 0, negative, or ``""`` will only insert newlines. ``None`` (the default)
@@ -2527,6 +2531,9 @@ and classes for traversing abstract syntax trees:
25272531
.. versionchanged:: 3.15
25282532
Omit optional ``Load()`` values by default.
25292533

2534+
.. versionchanged:: next
2535+
Added the *color* parameter.
2536+
25302537

25312538
.. _ast-compiler-flags:
25322539

@@ -2584,6 +2591,10 @@ Command-line usage
25842591

25852592
.. versionadded:: 3.9
25862593

2594+
.. versionchanged:: next
2595+
The output is now syntax highlighted by default. This can be
2596+
:ref:`controlled using environment variables <using-on-controlling-color>`.
2597+
25872598
The :mod:`!ast` module can be executed as a script from the command line.
25882599
It is as simple as:
25892600

Doc/library/tokenize.rst

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ type can be determined by checking the ``exact_type`` property on the
2828
**undefined** when providing invalid Python code and it can change at any
2929
point.
3030

31-
Tokenizing Input
31+
Tokenizing input
3232
----------------
3333

3434
The primary entry point is a :term:`generator`:
@@ -146,7 +146,7 @@ function it uses to do this is available:
146146

147147
.. _tokenize-cli:
148148

149-
Command-Line Usage
149+
Command-line usage
150150
------------------
151151

152152
.. versionadded:: 3.3
@@ -173,8 +173,12 @@ The following options are accepted:
173173
If :file:`filename.py` is specified its contents are tokenized to stdout.
174174
Otherwise, tokenization is performed on stdin.
175175

176+
.. versionadded:: next
177+
Output is in color by default and can be
178+
:ref:`controlled using environment variables <using-on-controlling-color>`.
179+
176180
Examples
177-
------------------
181+
--------
178182

179183
Example of a script rewriter that transforms float literals into Decimal
180184
objects::
@@ -227,7 +231,7 @@ Example of tokenizing from the command line. The script::
227231

228232
will be tokenized to the following output where the first column is the range
229233
of the line/column coordinates where the token is found, the second column is
230-
the name of the token, and the final column is the value of the token (if any)
234+
the name of the token, and the final column is the value of the token (if any):
231235

232236
.. code-block:: shell-session
233237

Doc/whatsnew/3.15.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,20 @@ array
704704
(Contributed by Sergey B Kirpichev in :gh:`146238`.)
705705

706706

707+
ast
708+
---
709+
710+
* Add *color* parameter to :func:`~ast.dump`.
711+
If ``True``, the returned string is syntax highlighted using ANSI escape
712+
sequences.
713+
If ``False`` (the default), colored output is always disabled.
714+
(Contributed by Stan Ulbrych in :gh:`148981`.)
715+
716+
* The :ref:`command-line <ast-cli>` output is now syntax highlighted by default.
717+
This can be :ref:`controlled using environment variables <using-on-controlling-color>`.
718+
(Contributed by Stan Ulbrych in :gh:`148981`.)
719+
720+
707721
base64
708722
------
709723

@@ -1244,6 +1258,15 @@ tkinter
12441258
(Contributed by Matthias Kievernagel and Serhiy Storchaka in :gh:`47655`.)
12451259

12461260

1261+
tokenize
1262+
--------
1263+
1264+
* The output of the :mod:`tokenize` :ref:`command-line interface
1265+
<tokenize-cli>` is colored by default. This can be controlled with
1266+
:ref:`environment variables <using-on-controlling-color>`.
1267+
(Contributed by Hugo van Kemenade in :gh:`148991`.)
1268+
1269+
12471270
.. _whatsnew315-tomllib-1-1-0:
12481271

12491272
tomllib

Lib/_colorize.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,17 @@ class Argparse(ThemeSection):
189189
message: str = ANSIColors.MAGENTA
190190

191191

192+
@dataclass(frozen=True, kw_only=True)
193+
class Ast(ThemeSection):
194+
node: str = ANSIColors.CYAN
195+
field: str = ANSIColors.BLUE
196+
attribute: str = ANSIColors.GREY
197+
string: str = ANSIColors.GREEN
198+
number: str = ANSIColors.YELLOW
199+
keyword: str = ANSIColors.BOLD_BLUE
200+
reset: str = ANSIColors.RESET
201+
202+
192203
@dataclass(frozen=True, kw_only=True)
193204
class Difflib(ThemeSection):
194205
"""A 'git diff'-like theme for `difflib.unified_diff`."""
@@ -375,6 +386,14 @@ class Timeit(ThemeSection):
375386
reset: str = ANSIColors.RESET
376387

377388

389+
@dataclass(frozen=True, kw_only=True)
390+
class Tokenize(ThemeSection):
391+
whitespace: str = ANSIColors.GREY
392+
error: str = ANSIColors.BOLD_RED
393+
position: str = ANSIColors.GREY
394+
delimiter: str = ANSIColors.RESET
395+
396+
378397
@dataclass(frozen=True, kw_only=True)
379398
class Traceback(ThemeSection):
380399
type: str = ANSIColors.BOLD_MAGENTA
@@ -405,25 +424,29 @@ class Theme:
405424
below.
406425
"""
407426
argparse: Argparse = field(default_factory=Argparse)
427+
ast: Ast = field(default_factory=Ast)
408428
difflib: Difflib = field(default_factory=Difflib)
409429
fancycompleter: FancyCompleter = field(default_factory=FancyCompleter)
410430
http_server: HttpServer = field(default_factory=HttpServer)
411431
live_profiler: LiveProfiler = field(default_factory=LiveProfiler)
412432
syntax: Syntax = field(default_factory=Syntax)
413433
timeit: Timeit = field(default_factory=Timeit)
434+
tokenize: Tokenize = field(default_factory=Tokenize)
414435
traceback: Traceback = field(default_factory=Traceback)
415436
unittest: Unittest = field(default_factory=Unittest)
416437

417438
def copy_with(
418439
self,
419440
*,
420441
argparse: Argparse | None = None,
442+
ast: Ast | None = None,
421443
difflib: Difflib | None = None,
422444
fancycompleter: FancyCompleter | None = None,
423445
http_server: HttpServer | None = None,
424446
live_profiler: LiveProfiler | None = None,
425447
syntax: Syntax | None = None,
426448
timeit: Timeit | None = None,
449+
tokenize: Tokenize | None = None,
427450
traceback: Traceback | None = None,
428451
unittest: Unittest | None = None,
429452
) -> Self:
@@ -434,12 +457,14 @@ def copy_with(
434457
"""
435458
return type(self)(
436459
argparse=argparse or self.argparse,
460+
ast=ast or self.ast,
437461
difflib=difflib or self.difflib,
438462
fancycompleter=fancycompleter or self.fancycompleter,
439463
http_server=http_server or self.http_server,
440464
live_profiler=live_profiler or self.live_profiler,
441465
syntax=syntax or self.syntax,
442466
timeit=timeit or self.timeit,
467+
tokenize=tokenize or self.tokenize,
443468
traceback=traceback or self.traceback,
444469
unittest=unittest or self.unittest,
445470
)
@@ -454,12 +479,14 @@ def no_colors(cls) -> Self:
454479
"""
455480
return cls(
456481
argparse=Argparse.no_colors(),
482+
ast=Ast.no_colors(),
457483
difflib=Difflib.no_colors(),
458484
fancycompleter=FancyCompleter.no_colors(),
459485
http_server=HttpServer.no_colors(),
460486
live_profiler=LiveProfiler.no_colors(),
461487
syntax=Syntax.no_colors(),
462488
timeit=Timeit.no_colors(),
489+
tokenize=Tokenize.no_colors(),
463490
traceback=Traceback.no_colors(),
464491
unittest=Unittest.no_colors(),
465492
)

Lib/ast.py

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
:license: Python License.
2222
"""
2323
from _ast import *
24+
lazy from _colorize import can_colorize, get_theme
2425

2526

2627
def parse(source, filename='<unknown>', mode='exec', *,
@@ -117,21 +118,32 @@ def _convert_literal(node):
117118
def dump(
118119
node, annotate_fields=True, include_attributes=False,
119120
*,
120-
indent=None, show_empty=False,
121+
color=False, indent=None, show_empty=False,
121122
):
122123
"""
123124
Return a formatted dump of the tree in node. This is mainly useful for
124-
debugging purposes. If annotate_fields is true (by default),
125-
the returned string will show the names and the values for fields.
126-
If annotate_fields is false, the result string will be more compact by
127-
omitting unambiguous field names. Attributes such as line
128-
numbers and column offsets are not dumped by default. If this is wanted,
129-
include_attributes can be set to true. If indent is a non-negative
130-
integer or string, then the tree will be pretty-printed with that indent
131-
level. None (the default) selects the single line representation.
125+
debugging purposes.
126+
127+
If annotate_fields is true (by default), the returned string will show the
128+
names and the values for fields. If annotate_fields is false, the result
129+
string will be more compact by omitting unambiguous field names.
130+
131+
Attributes such as line numbers and column offsets are not dumped by default.
132+
If this is wanted, include_attributes can be set to true.
133+
134+
If color is true, the returned string is syntax highlighted using ANSI
135+
escape sequences. If color is false (the default), colored output is always
136+
disabled.
137+
138+
If indent is a non-negative integer or string, then the tree will be
139+
pretty-printed with that indent level. If indent is None (the default),
140+
the tree is dumped on a single line.
141+
132142
If show_empty is False, then empty lists and fields that are None
133143
will be omitted from the output for better readability.
134144
"""
145+
t = get_theme(force_color=color, force_no_color=not color).ast
146+
135147
def _format(node, level=0):
136148
if indent is not None:
137149
level += 1
@@ -166,15 +178,17 @@ def _format(node, level=0):
166178
field_type = cls._field_types.get(name, object)
167179
if field_type is expr_context:
168180
if not keywords:
169-
args_buffer.append(repr(value))
181+
args_buffer.append(
182+
f'{t.node}{type(value).__name__}'
183+
f'{t.reset}()')
170184
continue
171185
if not keywords:
172186
args.extend(args_buffer)
173187
args_buffer = []
174188
value, simple = _format(value, level)
175189
allsimple = allsimple and simple
176190
if keywords:
177-
args.append('%s=%s' % (name, value))
191+
args.append(f'{t.field}{name}{t.reset}={value}')
178192
else:
179193
args.append(value)
180194
if include_attributes and node._attributes:
@@ -187,14 +201,21 @@ def _format(node, level=0):
187201
continue
188202
value, simple = _format(value, level)
189203
allsimple = allsimple and simple
190-
args.append('%s=%s' % (name, value))
204+
args.append(f'{t.attribute}{name}{t.reset}={value}')
205+
cls_name = f'{t.node}{cls.__name__}{t.reset}'
191206
if allsimple and len(args) <= 3:
192-
return '%s(%s)' % (node.__class__.__name__, ', '.join(args)), not args
193-
return '%s(%s%s)' % (node.__class__.__name__, prefix, sep.join(args)), False
207+
return f'{cls_name}({", ".join(args)})', not args
208+
return f'{cls_name}({prefix}{sep.join(args)})', False
194209
elif isinstance(node, list):
195210
if not node:
196211
return '[]', True
197212
return '[%s%s]' % (prefix, sep.join(_format(x, level)[0] for x in node)), False
213+
if isinstance(node, bool) or node is None or node is Ellipsis:
214+
return f'{t.keyword}{node!r}{t.reset}', True
215+
if isinstance(node, (int, float, complex)):
216+
return f'{t.number}{node!r}{t.reset}', True
217+
if isinstance(node, (str, bytes)):
218+
return f'{t.string}{node!r}{t.reset}', True
198219
return repr(node), True
199220

200221
if not isinstance(node, AST):
@@ -642,7 +663,7 @@ def main(args=None):
642663
import argparse
643664
import sys
644665

645-
parser = argparse.ArgumentParser(color=True)
666+
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
646667
parser.add_argument('infile', nargs='?', default='-',
647668
help='the file to parse; defaults to stdin')
648669
parser.add_argument('-m', '--mode', default='exec',
@@ -661,7 +682,7 @@ def main(args=None):
661682
'(for example, 3.10)')
662683
parser.add_argument('-O', '--optimize',
663684
type=int, default=-1, metavar='LEVEL',
664-
help='optimization level for parser (default -1)')
685+
help='optimization level for parser')
665686
parser.add_argument('--show-empty', default=False, action='store_true',
666687
help='show empty lists and fields in dump output')
667688
args = parser.parse_args(args)
@@ -688,6 +709,7 @@ def main(args=None):
688709
tree = parse(source, name, args.mode, type_comments=args.no_type_comments,
689710
feature_version=feature_version, optimize=args.optimize)
690711
print(dump(tree, include_attributes=args.include_attributes,
712+
color=can_colorize(file=sys.stdout),
691713
indent=args.indent, show_empty=args.show_empty))
692714

693715
if __name__ == '__main__':

0 commit comments

Comments
 (0)