Skip to content

gh-130645: Add color to stdlib argparse CLIs #133380

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Lib/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,7 @@ def main(args=None):
import argparse
import sys

parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(color=True)
parser.add_argument('infile', nargs='?', default='-',
help='the file to parse; defaults to stdin')
parser.add_argument('-m', '--mode', default='exec',
Expand Down
2 changes: 1 addition & 1 deletion Lib/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -810,7 +810,7 @@ def timegm(tuple):

def main(args=None):
import argparse
parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(color=True)
textgroup = parser.add_argument_group('text only arguments')
htmlgroup = parser.add_argument_group('html only arguments')
textgroup.add_argument(
Expand Down
2 changes: 1 addition & 1 deletion Lib/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ def interact(banner=None, readfunc=None, local=None, exitmsg=None, local_exit=Fa
if __name__ == "__main__":
import argparse

parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(color=True)
parser.add_argument('-q', action='store_true',
help="don't print version and copyright messages")
args = parser.parse_args()
Expand Down
4 changes: 3 additions & 1 deletion Lib/compileall.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,9 @@ def main():
import argparse

parser = argparse.ArgumentParser(
description='Utilities to support installing Python libraries.')
description='Utilities to support installing Python libraries.',
color=True,
)
parser.add_argument('-l', action='store_const', const=0,
default=None, dest='maxlevels',
help="don't recurse into subdirectories")
Expand Down
2 changes: 1 addition & 1 deletion Lib/dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -1131,7 +1131,7 @@ def dis(self):
def main(args=None):
import argparse

parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(color=True)
parser.add_argument('-C', '--show-caches', action='store_true',
help='show inline caches')
parser.add_argument('-O', '--show-offsets', action='store_true',
Expand Down
2 changes: 1 addition & 1 deletion Lib/doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2870,7 +2870,7 @@ def get(self):
def _test():
import argparse

parser = argparse.ArgumentParser(description="doctest runner")
parser = argparse.ArgumentParser(description="doctest runner", color=True)
parser.add_argument('-v', '--verbose', action='store_true', default=False,
help='print very verbose output for all tests')
parser.add_argument('-o', '--option', action='append',
Expand Down
2 changes: 1 addition & 1 deletion Lib/ensurepip/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ def _uninstall_helper(*, verbosity=0):

def _main(argv=None):
import argparse
parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(color=True)
parser.add_argument(
"--version",
action="version",
Expand Down
4 changes: 3 additions & 1 deletion Lib/gzip.py
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,9 @@ def main():
from argparse import ArgumentParser
parser = ArgumentParser(description=
"A simple command line interface for the gzip module: act like gzip, "
"but do not delete the input file.")
"but do not delete the input file.",
color=True,
)
group = parser.add_mutually_exclusive_group()
group.add_argument('--fast', action='store_true', help='compress faster')
group.add_argument('--best', action='store_true', help='compress better')
Expand Down
2 changes: 1 addition & 1 deletion Lib/http/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1340,7 +1340,7 @@ def test(HandlerClass=BaseHTTPRequestHandler,
import argparse
import contextlib

parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(color=True)
parser.add_argument('--cgi', action='store_true',
help='run as CGI server')
parser.add_argument('-b', '--bind', metavar='ADDRESS',
Expand Down
2 changes: 1 addition & 1 deletion Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -3343,7 +3343,7 @@ def _main():
import argparse
import importlib

parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(color=True)
parser.add_argument(
'object',
help="The object to be analysed. "
Expand Down
2 changes: 1 addition & 1 deletion Lib/json/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def _colorize_json(json_str):
def main():
description = ('A simple command line interface for json module '
'to validate and pretty-print JSON objects.')
parser = argparse.ArgumentParser(description=description)
parser = argparse.ArgumentParser(description=description, color=True)
parser.add_argument('infile', nargs='?',
help='a JSON file to be validated or pretty-printed',
default='-')
Expand Down
4 changes: 3 additions & 1 deletion Lib/mimetypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,9 @@ def _default_mime_types():
def _parse_args(args):
from argparse import ArgumentParser

parser = ArgumentParser(description='map filename extensions to MIME types')
parser = ArgumentParser(
description='map filename extensions to MIME types', color=True
)
parser.add_argument(
'-e', '--extension',
action='store_true',
Expand Down
11 changes: 7 additions & 4 deletions Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -3296,10 +3296,13 @@ def help():
def main():
import argparse

parser = argparse.ArgumentParser(usage="%(prog)s [-h] [-c command] (-m module | -p pid | pyfile) [args ...]",
description=_usage,
formatter_class=argparse.RawDescriptionHelpFormatter,
allow_abbrev=False)
parser = argparse.ArgumentParser(
usage="%(prog)s [-h] [-c command] (-m module | -p pid | pyfile) [args ...]",
description=_usage,
formatter_class=argparse.RawDescriptionHelpFormatter,
allow_abbrev=False,
color=True,
)

# We need to maunally get the script from args, because the first positional
# arguments could be either the script we need to debug, or the argument
Expand Down
4 changes: 3 additions & 1 deletion Lib/pickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -1911,7 +1911,9 @@ def _main(args=None):
import argparse
import pprint
parser = argparse.ArgumentParser(
description='display contents of the pickle files')
description='display contents of the pickle files',
color=True,
)
parser.add_argument(
'pickle_file',
nargs='+', help='the pickle file')
Expand Down
4 changes: 3 additions & 1 deletion Lib/pickletools.py
Original file line number Diff line number Diff line change
Expand Up @@ -2842,7 +2842,9 @@ def __init__(self, value):
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description='disassemble one or more pickle files')
description='disassemble one or more pickle files',
color=True,
)
parser.add_argument(
'pickle_file',
nargs='+', help='the pickle file')
Expand Down
2 changes: 1 addition & 1 deletion Lib/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -1467,7 +1467,7 @@ def invalidate_caches():
def _parse_args(args: list[str] | None):
import argparse

parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(color=True)
parser.add_argument("args", nargs="*", choices=["nonaliased", "terse"])
parser.add_argument(
"--terse",
Expand Down
2 changes: 1 addition & 1 deletion Lib/py_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def main():
import argparse

description = 'A simple command-line interface for py_compile module.'
parser = argparse.ArgumentParser(description=description)
parser = argparse.ArgumentParser(description=description, color=True)
parser.add_argument(
'-q', '--quiet',
action='store_true',
Expand Down
2 changes: 1 addition & 1 deletion Lib/random.py
Original file line number Diff line number Diff line change
Expand Up @@ -1011,7 +1011,7 @@ def _test(N=10_000):
def _parse_args(arg_list: list[str] | None):
import argparse
parser = argparse.ArgumentParser(
formatter_class=argparse.RawTextHelpFormatter)
formatter_class=argparse.RawTextHelpFormatter, color=True)
group = parser.add_mutually_exclusive_group()
group.add_argument(
"-c", "--choice", nargs="+",
Expand Down
1 change: 1 addition & 0 deletions Lib/sqlite3/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def runsource(self, source, filename="<input>", symbol="single"):
def main(*args):
parser = ArgumentParser(
description="Python sqlite3 CLI",
color=True,
)
parser.add_argument(
"filename", type=str, default=":memory:", nargs="?",
Expand Down
2 changes: 1 addition & 1 deletion Lib/tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -2883,7 +2883,7 @@ def main():
import argparse

description = 'A simple command-line interface for tarfile module.'
parser = argparse.ArgumentParser(description=description)
parser = argparse.ArgumentParser(description=description, color=True)
parser.add_argument('-v', '--verbose', action='store_true', default=False,
help='Verbose output')
parser.add_argument('--filter', metavar='<filtername>',
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_ast/test_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -3286,6 +3286,7 @@ def f(x: int) -> int:
with self.subTest(flags=args):
self.invoke_ast(*args)

@support.force_not_colorized
def test_help_message(self):
for flag in ('-h', '--help', '--unknown'):
with self.subTest(flag=flag):
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,7 @@ def assertFailure(self, *args):
self.assertCLIFails(*args)
self.assertCmdFails(*args)

@support.force_not_colorized
def test_help(self):
stdout = self.run_cmd_ok('-h')
self.assertIn(b'usage:', stdout)
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_mimetypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import unittest.mock
from platform import win32_edition
from test import support
from test.support import os_helper
from test.support import force_not_colorized, os_helper

try:
import _winapi
Expand Down Expand Up @@ -437,6 +437,7 @@ def test__all__(self):


class CommandLineTest(unittest.TestCase):
@force_not_colorized
def test_parse_args(self):
args, help_text = mimetypes._parse_args("-h")
self.assertTrue(help_text.startswith("usage: "))
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_pickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,7 @@ def test_invocation(self):
expect = self.text_normalize(expect)
self.assertListEqual(res.splitlines(), expect.splitlines())

@support.force_not_colorized
def test_unknown_flag(self):
stderr = io.StringIO()
with self.assertRaises(SystemExit):
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,7 @@ def test_arg_parsing(self):
self.invoke_platform(*flags)
obj.assert_called_once_with(aliased, terse)

@support.force_not_colorized
def test_help(self):
output = io.StringIO()

Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_random.py
Original file line number Diff line number Diff line change
Expand Up @@ -1411,6 +1411,7 @@ def test_after_fork(self):


class CommandLineTest(unittest.TestCase):
@support.force_not_colorized
def test_parse_args(self):
args, help_text = random._parse_args(shlex.split("--choice a b c"))
self.assertEqual(args.choice, ["a", "b", "c"])
Expand Down
8 changes: 7 additions & 1 deletion Lib/test/test_sqlite3/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@

from sqlite3.__main__ import main as cli
from test.support.os_helper import TESTFN, unlink
from test.support import captured_stdout, captured_stderr, captured_stdin
from test.support import (
captured_stdout,
captured_stderr,
captured_stdin,
force_not_colorized,
)


class CommandLineInterface(unittest.TestCase):
Expand Down Expand Up @@ -32,6 +37,7 @@ def expect_failure(self, *args):
self.assertEqual(out, "")
return err

@force_not_colorized
def test_cli_help(self):
out = self.expect_success("-h")
self.assertIn("usage: ", out)
Expand Down
2 changes: 1 addition & 1 deletion Lib/tokenize.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ def error(message, filename=None, location=None):
sys.exit(1)

# Parse the arguments and options
parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(color=True)
parser.add_argument(dest='filename', nargs='?',
metavar='filename.py',
help='the file to tokenize; defaults to stdin')
Expand Down
2 changes: 1 addition & 1 deletion Lib/trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ def results(self):
def main():
import argparse

parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(color=True)
parser.add_argument('--version', action='version', version='trace 2.0')

grp = parser.add_argument_group('Main options',
Expand Down
4 changes: 2 additions & 2 deletions Lib/unittest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ def _getParentArgParser(self):
return parser

def _getMainArgParser(self, parent):
parser = argparse.ArgumentParser(parents=[parent])
parser = argparse.ArgumentParser(parents=[parent], color=True)
parser.prog = self.progName
parser.print_help = self._print_help

Expand All @@ -208,7 +208,7 @@ def _getMainArgParser(self, parent):
return parser

def _getDiscoveryArgParser(self, parent):
parser = argparse.ArgumentParser(parents=[parent])
parser = argparse.ArgumentParser(parents=[parent], color=True)
parser.prog = '%s discover' % self.progName
parser.epilog = ('For test discovery all test modules must be '
'importable from the top level directory of the '
Expand Down
4 changes: 3 additions & 1 deletion Lib/uuid.py
Original file line number Diff line number Diff line change
Expand Up @@ -949,7 +949,9 @@ def main():
import argparse
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description="Generate a UUID using the selected UUID function.")
description="Generate a UUID using the selected UUID function.",
color=True,
)
parser.add_argument("-u", "--uuid",
choices=uuid_funcs.keys(),
default="uuid4",
Expand Down
4 changes: 3 additions & 1 deletion Lib/venv/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,9 @@ def main(args=None):
'created, you may wish to '
'activate it, e.g. by '
'sourcing an activate script '
'in its bin directory.')
'in its bin directory.',
color=True,
)
parser.add_argument('dirs', metavar='ENV_DIR', nargs='+',
help='A directory to create the environment in.')
parser.add_argument('--system-site-packages', default=False,
Expand Down
4 changes: 3 additions & 1 deletion Lib/webbrowser.py
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,9 @@ def open(self, url, new=0, autoraise=True):

def parse_args(arg_list: list[str] | None):
import argparse
parser = argparse.ArgumentParser(description="Open URL in a web browser.")
parser = argparse.ArgumentParser(
description="Open URL in a web browser.", color=True,
)
parser.add_argument("url", help="URL to open")

group = parser.add_mutually_exclusive_group()
Expand Down
2 changes: 1 addition & 1 deletion Lib/zipapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def main(args=None):
"""
import argparse

parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(color=True)
parser.add_argument('--output', '-o', default=None,
help="The name of the output archive. "
"Required if SOURCE is an archive.")
Expand Down
2 changes: 1 addition & 1 deletion Lib/zipfile/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2317,7 +2317,7 @@ def main(args=None):
import argparse

description = 'A simple command-line interface for zipfile module.'
parser = argparse.ArgumentParser(description=description)
parser = argparse.ArgumentParser(description=description, color=True)
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-l', '--list', metavar='<zipfile>',
help='Show listing of a zipfile')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add color to stdlib argparse CLIs. Patch by Hugo van Kemenade.
Loading