[Python-checkins] gh-90300: split --help output into separate options (#30331)

merwok webhook-mailer at python.org
Wed Jun 1 05:50:08 EDT 2022


https://github.com/python/cpython/commit/8aa9d40b00741213c5a53b1ae15509998893ae31
commit: 8aa9d40b00741213c5a53b1ae15509998893ae31
branch: main
author: Éric <earaujo at caravan.coop>
committer: merwok <merwok at netwok.org>
date: 2022-06-01T05:50:01-04:00
summary:

gh-90300: split --help output into separate options (#30331)

Make --help output shorter and add new help options.

--help-env, --help-xoptions and --help-all command-line options are
added to complement --help.

files:
A Misc/NEWS.d/next/Core and Builtins/2022-01-02-14-53-59.bpo-46142.WayjgT.rst
M Doc/using/cmdline.rst
M Lib/test/test_cmd_line.py
M Misc/python.man
M Python/getopt.c
M Python/initconfig.c

diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst
index 07c05a94b99f9..d9f6afb3760bd 100644
--- a/Doc/using/cmdline.rst
+++ b/Doc/using/cmdline.rst
@@ -183,6 +183,8 @@ automatically enabled, if available on your platform (see
    Automatic enabling of tab-completion and history editing.
 
 
+.. _using-on-generic-options:
+
 Generic options
 ~~~~~~~~~~~~~~~
 
@@ -190,8 +192,28 @@ Generic options
                -h
                --help
 
-   Print a short description of all command line options.
+   Print a short description of all command line options and corresponding
+   environment variables and exit.
+
+.. cmdoption:: --help-env
+
+   Print a short description of Python-specific environment variables
+   and exit.
+
+   .. versionadded:: 3.11
+
+.. cmdoption:: --help-xoptions
 
+   Print a description of implementation-specific :option:`-X` options
+   and exit.
+
+   .. versionadded:: 3.11
+
+.. cmdoption:: --help-all
+
+   Print complete usage information and exit.
+
+   .. versionadded:: 3.11
 
 .. cmdoption:: -V
                --version
@@ -212,6 +234,7 @@ Generic options
    .. versionadded:: 3.6
       The ``-VV`` option.
 
+
 .. _using-on-misc-options:
 
 Miscellaneous options
@@ -460,6 +483,7 @@ Miscellaneous options
    See :ref:`warning-filter` and :ref:`describing-warning-filters` for more
    details.
 
+
 .. cmdoption:: -x
 
    Skip the first line of the source, allowing use of non-Unix forms of
@@ -553,6 +577,7 @@ Miscellaneous options
       The ``-X frozen_modules`` option.
 
 
+
 Options you shouldn't use
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py
index ed733d2f61666..bc52bbdb0f94d 100644
--- a/Lib/test/test_cmd_line.py
+++ b/Lib/test/test_cmd_line.py
@@ -25,32 +25,55 @@ def _kill_python_and_exit_code(p):
     returncode = p.wait()
     return data, returncode
 
+
 class CmdLineTest(unittest.TestCase):
     def test_directories(self):
         assert_python_failure('.')
         assert_python_failure('< .')
 
     def verify_valid_flag(self, cmd_line):
-        rc, out, err = assert_python_ok(*cmd_line)
+        rc, out, err = assert_python_ok(cmd_line)
         self.assertTrue(out == b'' or out.endswith(b'\n'))
         self.assertNotIn(b'Traceback', out)
         self.assertNotIn(b'Traceback', err)
+        return out
 
-    def test_optimize(self):
-        self.verify_valid_flag('-O')
-        self.verify_valid_flag('-OO')
+    def test_help(self):
+        self.verify_valid_flag('-h')
+        self.verify_valid_flag('-?')
+        out = self.verify_valid_flag('--help')
+        lines = out.splitlines()
+        self.assertIn(b'usage', lines[0])
+        self.assertNotIn(b'PYTHONHOME', out)
+        self.assertNotIn(b'-X dev', out)
+        self.assertLess(len(lines), 50)
 
-    def test_site_flag(self):
-        self.verify_valid_flag('-S')
+    def test_help_env(self):
+        out = self.verify_valid_flag('--help-env')
+        self.assertIn(b'PYTHONHOME', out)
+
+    def test_help_xoptions(self):
+        out = self.verify_valid_flag('--help-xoptions')
+        self.assertIn(b'-X dev', out)
 
-    def test_usage(self):
-        rc, out, err = assert_python_ok('-h')
+    def test_help_all(self):
+        out = self.verify_valid_flag('--help-all')
         lines = out.splitlines()
         self.assertIn(b'usage', lines[0])
+        self.assertIn(b'PYTHONHOME', out)
+        self.assertIn(b'-X dev', out)
+
         # The first line contains the program name,
         # but the rest should be ASCII-only
         b''.join(lines[1:]).decode('ascii')
 
+    def test_optimize(self):
+        self.verify_valid_flag('-O')
+        self.verify_valid_flag('-OO')
+
+    def test_site_flag(self):
+        self.verify_valid_flag('-S')
+
     def test_version(self):
         version = ('Python %d.%d' % sys.version_info[:2]).encode("ascii")
         for switch in '-V', '--version', '-VV':
@@ -90,7 +113,7 @@ def get_xoptions(*args):
     def test_unknown_xoptions(self):
         rc, out, err = assert_python_failure('-X', 'blech')
         self.assertIn(b'Unknown value for option -X', err)
-        msg = b'Fatal Python error: Unknown value for option -X'
+        msg = b'Fatal Python error: Unknown value for option -X (see --help-xoptions)'
         self.assertEqual(err.splitlines().count(msg), 1)
         self.assertEqual(b'', out)
 
@@ -134,7 +157,6 @@ def test_xoption_frozen_modules(self):
         }
         for raw, expected in tests:
             cmd = ['-X', f'frozen_modules{raw}',
-                   #'-c', 'import os; print(os.__spec__.loader.__name__, end="")']
                    '-c', 'import os; print(os.__spec__.loader, end="")']
             with self.subTest(raw):
                 res = assert_python_ok(*cmd)
@@ -167,7 +189,6 @@ def test_relativedir_bug46421(self):
         # Test `python -m unittest` with a relative directory beginning with ./
         # Note: We have to switch to the project's top module's directory, as per
         # the python unittest wiki. We will switch back when we are done.
-        defaultwd = os.getcwd()
         projectlibpath = os.path.dirname(__file__).removesuffix("test")
         with os_helper.change_cwd(projectlibpath):
             # Testing with and without ./
@@ -247,7 +268,6 @@ def test_invalid_utf8_arg(self):
         #
         # Test with default config, in the C locale, in the Python UTF-8 Mode.
         code = 'import sys, os; s=os.fsencode(sys.argv[1]); print(ascii(s))'
-        base_cmd = [sys.executable, '-c', code]
 
         def run_default(arg):
             cmd = [sys.executable, '-c', code, arg]
@@ -892,6 +912,7 @@ def test_sys_flags_not_set(self):
             PYTHONSAFEPATH="1",
         )
 
+
 class SyntaxErrorTests(unittest.TestCase):
     def check_string(self, code):
         proc = subprocess.run([sys.executable, "-"], input=code,
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-01-02-14-53-59.bpo-46142.WayjgT.rst b/Misc/NEWS.d/next/Core and Builtins/2022-01-02-14-53-59.bpo-46142.WayjgT.rst
new file mode 100644
index 0000000000000..3c62c54289946
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2022-01-02-14-53-59.bpo-46142.WayjgT.rst	
@@ -0,0 +1,3 @@
+Make ``--help`` output shorter by moving some info to the new
+``--help-env`` and ``--help-xoptions`` command-line options.
+Also add ``--help-all`` option to print complete usage.
diff --git a/Misc/python.man b/Misc/python.man
index c6628fe75f40f..1705eeb0c9c12 100644
--- a/Misc/python.man
+++ b/Misc/python.man
@@ -84,6 +84,19 @@ python \- an interpreted, interactive, object-oriented programming language
 |
 .I never
 ]
+.br
+       [
+.B \--help
+]
+[
+.B \--help-env
+]
+[
+.B \--help-xoptions
+]
+[
+.B \--help-all
+]
 .br
        [
 .B \-c
@@ -149,6 +162,16 @@ the behavior of the interpreter.
 .B \-h ", " \-? ", "\-\-help
 Prints the usage for the interpreter executable and exits.
 .TP
+.B "\-\-help\-env"
+Prints help about Python-specific environment variables and exits.
+.TP
+.B "\-\-help\-xoptions"
+Prints help about implementation-specific \fB\-X\fP options and exits.
+.TP
+.TP
+.B "\-\-help\-all"
+Prints complete usage information and exits.
+.TP
 .B \-i
 When a script is passed as first argument or the \fB\-c\fP option is
 used, enter interactive mode after executing the script or the
@@ -287,7 +310,7 @@ a regular expression on the warning message.
 
 .TP
 .BI "\-X " option
-Set implementation specific option. The following options are available:
+Set implementation-specific option. The following options are available:
 
     -X faulthandler: enable faulthandler
 
@@ -332,7 +355,7 @@ Set implementation specific option. The following options are available:
        files are desired as well as suppressing the extra visual location indicators
        when the interpreter displays tracebacks.
 
-    -X frozen_modules=[on|off]: whether or not frozen modules should be used
+    -X frozen_modules=[on|off]: whether or not frozen modules should be used.
        The default is "on" (or "off" if you are running a local build).
 
 .TP
diff --git a/Python/getopt.c b/Python/getopt.c
index fcea60759d12c..4135bf1446ecf 100644
--- a/Python/getopt.c
+++ b/Python/getopt.c
@@ -44,8 +44,12 @@ static const wchar_t *opt_ptr = L"";
 #define SHORT_OPTS L"bBc:dEhiIJm:OPqRsStuvVW:xX:?"
 
 static const _PyOS_LongOption longopts[] = {
+    /* name, has_arg, val (used in switch in initconfig.c) */
     {L"check-hash-based-pycs", 1, 0},
-    {NULL, 0, 0},
+    {L"help-all", 0, 1},
+    {L"help-env", 0, 2},
+    {L"help-xoptions", 0, 3},
+    {NULL, 0, -1},                     /* sentinel */
 };
 
 
diff --git a/Python/initconfig.c b/Python/initconfig.c
index 5c9c7ee41e753..86dcdd9a8bd6b 100644
--- a/Python/initconfig.c
+++ b/Python/initconfig.c
@@ -28,9 +28,10 @@
 static const char usage_line[] =
 "usage: %ls [option] ... [-c cmd | -m mod | file | -] [arg] ...\n";
 
-/* Long usage message, split into parts < 512 bytes */
-static const char usage_1[] = "\
-Options and arguments (and corresponding environment variables):\n\
+/* Long help message */
+/* Lines sorted by option name; keep in sync with usage_envvars* below */
+static const char usage_help[] = "\
+Options (and corresponding environment variables):\n\
 -b     : issue warnings about str(bytes_instance), str(bytearray_instance)\n\
          and comparing bytes/bytearray with str. (-bb: issue errors)\n\
 -B     : don't write .pyc files on import; also PYTHONDONTWRITEBYTECODE=x\n\
@@ -39,8 +40,6 @@ Options and arguments (and corresponding environment variables):\n\
          debug builds); also PYTHONDEBUG=x\n\
 -E     : ignore PYTHON* environment variables (such as PYTHONPATH)\n\
 -h     : print this help message and exit (also -? or --help)\n\
-";
-static const char usage_2[] = "\
 -i     : inspect interactively after running script; forces a prompt even\n\
          if stdin does not appear to be a terminal; also PYTHONINSPECT=x\n\
 -I     : isolate Python from the user's environment (implies -E and -s)\n\
@@ -53,8 +52,6 @@ static const char usage_2[] = "\
 -q     : don't print version and copyright messages on interactive startup\n\
 -s     : don't add user site directory to sys.path; also PYTHONNOUSERSITE\n\
 -S     : don't imply 'import site' on initialization\n\
-";
-static const char usage_3[] = "\
 -u     : force the stdout and stderr streams to be unbuffered;\n\
          this option has no effect on stdin; also PYTHONUNBUFFERED=x\n\
 -v     : verbose (trace import statements); also PYTHONVERBOSE=x\n\
@@ -64,55 +61,72 @@ static const char usage_3[] = "\
 -W arg : warning control; arg is action:message:category:module:lineno\n\
          also PYTHONWARNINGS=arg\n\
 -x     : skip first line of source, allowing use of non-Unix forms of #!cmd\n\
--X opt : set implementation-specific option. The following options are available:\n\
-         -X faulthandler: enable faulthandler\n\
-         -X showrefcount: output the total reference count and number of used\n\
-             memory blocks when the program finishes or after each statement in the\n\
-             interactive interpreter. This only works on debug builds\n\
-         -X tracemalloc: start tracing Python memory allocations using the\n\
-             tracemalloc module. By default, only the most recent frame is stored in a\n\
-             traceback of a trace. Use -X tracemalloc=NFRAME to start tracing with a\n\
-             traceback limit of NFRAME frames\n\
-         -X importtime: show how long each import takes. It shows module name,\n\
-             cumulative time (including nested imports) and self time (excluding\n\
-             nested imports). Note that its output may be broken in multi-threaded\n\
-             application. Typical usage is python3 -X importtime -c 'import asyncio'\n\
-         -X dev: enable CPython's \"development mode\", introducing additional runtime\n\
-             checks which are too expensive to be enabled by default. Effect of the\n\
-             developer mode:\n\
-                * Add default warning filter, as -W default\n\
-                * Install debug hooks on memory allocators: see the PyMem_SetupDebugHooks()\n\
-                  C function\n\
-                * Enable the faulthandler module to dump the Python traceback on a crash\n\
-                * Enable asyncio debug mode\n\
-                * Set the dev_mode attribute of sys.flags to True\n\
-                * io.IOBase destructor logs close() exceptions\n\
-         -X utf8: enable UTF-8 mode for operating system interfaces, overriding the default\n\
-             locale-aware mode. -X utf8=0 explicitly disables UTF-8 mode (even when it would\n\
-             otherwise activate automatically)\n\
-         -X pycache_prefix=PATH: enable writing .pyc files to a parallel tree rooted at the\n\
-             given directory instead of to the code tree\n\
-         -X warn_default_encoding: enable opt-in EncodingWarning for 'encoding=None'\n\
-         -X no_debug_ranges: disable the inclusion of the tables mapping extra location \n\
-            information (end line, start column offset and end column offset) to every \n\
-            instruction in code objects. This is useful when smaller code objects and pyc \n\
-            files are desired as well as suppressing the extra visual location indicators \n\
-            when the interpreter displays tracebacks.\n\
-         -X frozen_modules=[on|off]: whether or not frozen modules should be used.\n\
-            The default is \"on\" (or \"off\" if you are running a local build).\n\
+-X opt : set implementation-specific option\n\
 --check-hash-based-pycs always|default|never:\n\
-    control how Python invalidates hash-based .pyc files\n\
-";
-static const char usage_4[] = "\
+         control how Python invalidates hash-based .pyc files\n\
+--help-env      : print help about Python environment variables and exit\n\
+--help-xoptions : print help about implementation-specific -X options and exit\n\
+--help-all      : print complete help information and exit\n\
+Arguments:\n\
 file   : program read from script file\n\
 -      : program read from stdin (default; interactive mode if a tty)\n\
-arg ...: arguments passed to program in sys.argv[1:]\n\n\
-Other environment variables:\n\
-PYTHONSTARTUP: file executed on interactive startup (no default)\n\
-PYTHONPATH   : '%lc'-separated list of directories prefixed to the\n\
-               default module search path.  The result is sys.path.\n\
+arg ...: arguments passed to program in sys.argv[1:]\n\
 ";
-static const char usage_5[] =
+
+static const char usage_xoptions[] = "\
+The following implementation-specific options are available:\n\
+\n\
+-X faulthandler: enable faulthandler\n\
+\n\
+-X showrefcount: output the total reference count and number of used\n\
+    memory blocks when the program finishes or after each statement in the\n\
+    interactive interpreter. This only works on debug builds\n\
+\n\
+-X tracemalloc: start tracing Python memory allocations using the\n\
+    tracemalloc module. By default, only the most recent frame is stored in a\n\
+    traceback of a trace. Use -X tracemalloc=NFRAME to start tracing with a\n\
+    traceback limit of NFRAME frames\n\
+\n\
+-X importtime: show how long each import takes. It shows module name,\n\
+    cumulative time (including nested imports) and self time (excluding\n\
+    nested imports). Note that its output may be broken in multi-threaded\n\
+    application. Typical usage is python3 -X importtime -c 'import asyncio'\n\
+\n\
+-X dev: enable CPython's \"development mode\", introducing additional runtime\n\
+    checks which are too expensive to be enabled by default. Effect of the\n\
+    developer mode:\n\
+       * Add default warning filter, as -W default\n\
+       * Install debug hooks on memory allocators: see the PyMem_SetupDebugHooks()\n\
+         C function\n\
+       * Enable the faulthandler module to dump the Python traceback on a crash\n\
+       * Enable asyncio debug mode\n\
+       * Set the dev_mode attribute of sys.flags to True\n\
+       * io.IOBase destructor logs close() exceptions\n\
+\n\
+-X utf8: enable UTF-8 mode for operating system interfaces, overriding the default\n\
+    locale-aware mode. -X utf8=0 explicitly disables UTF-8 mode (even when it would\n\
+    otherwise activate automatically)\n\
+\n\
+-X pycache_prefix=PATH: enable writing .pyc files to a parallel tree rooted at the\n\
+    given directory instead of to the code tree\n\
+\n\
+-X warn_default_encoding: enable opt-in EncodingWarning for 'encoding=None'\n\
+\n\
+-X no_debug_ranges: disable the inclusion of the tables mapping extra location \n\
+   information (end line, start column offset and end column offset) to every \n\
+   instruction in code objects. This is useful when smaller code objects and pyc \n\
+   files are desired as well as suppressing the extra visual location indicators \n\
+   when the interpreter displays tracebacks.\n\
+\n\
+-X frozen_modules=[on|off]: whether or not frozen modules should be used.\n\
+   The default is \"on\" (or \"off\" if you are running a local build).";
+
+/* Envvars that don't have equivalent command-line options are listed first */
+static const char usage_envvars[] =
+"Environment variables that change behavior:\n"
+"PYTHONSTARTUP: file executed on interactive startup (no default)\n"
+"PYTHONPATH   : '%lc'-separated list of directories prefixed to the\n"
+"               default module search path.  The result is sys.path.\n"
 "PYTHONSAFEPATH: don't prepend a potentially unsafe path to sys.path.\n"
 "PYTHONHOME   : alternate <prefix> directory (or <prefix>%lc<exec_prefix>).\n"
 "               The default module search path uses %s.\n"
@@ -120,8 +134,7 @@ static const char usage_5[] =
 "PYTHONCASEOK : ignore case in 'import' statements (Windows).\n"
 "PYTHONUTF8: if set to 1, enable the UTF-8 mode.\n"
 "PYTHONIOENCODING: Encoding[:errors] used for stdin/stdout/stderr.\n"
-"PYTHONFAULTHANDLER: dump the Python traceback on fatal errors.\n";
-static const char usage_6[] =
+"PYTHONFAULTHANDLER: dump the Python traceback on fatal errors.\n"
 "PYTHONHASHSEED: if this variable is set to 'random', a random value is used\n"
 "   to seed the hashes of str and bytes objects.  It can also be set to an\n"
 "   integer in the range [0,4294967295] to get hash values with a\n"
@@ -141,7 +154,16 @@ static const char usage_6[] =
 "   tables mapping extra location information (end line, start column offset \n"
 "   and end column offset) to every instruction in code objects. This is useful \n"
 "   when smaller code objects and pyc files are desired as well as suppressing the \n"
-"   extra visual location indicators when the interpreter displays tracebacks.\n";
+"   extra visual location indicators when the interpreter displays tracebacks.\n"
+"These variables have equivalent command-line parameters (see --help for details):\n"
+"PYTHONDEBUG             : enable parser debug mode (-d)\n"
+"PYTHONDONTWRITEBYTECODE : don't write .pyc files (-B)\n"
+"PYTHONINSPECT           : inspect interactively after running script (-i)\n"
+"PYTHONNOUSERSITE        : disable user site directory (-s)\n"
+"PYTHONOPTIMIZE          : enable level 1 optimizations (-O)\n"
+"PYTHONUNBUFFERED        : disable stdout/stderr buffering (-u)\n"
+"PYTHONVERBOSE           : trace import statements (-v)\n"
+"PYTHONWARNINGS=arg      : warning control (-W arg)\n";
 
 #if defined(MS_WINDOWS)
 #  define PYTHONHOMEHELP "<prefix>\\python{major}{minor}"
@@ -2084,7 +2106,7 @@ config_read(PyConfig *config, int compute_path_config)
     /* -X options */
     const wchar_t* option = _Py_check_xoptions(&config->xoptions, known_xoptions);
     if (option != NULL) {
-        return PyStatus_Error("Unknown value for option -X");
+        return PyStatus_Error("Unknown value for option -X (see --help-xoptions)");
     }
 
     if (config_get_xoption(config, L"showrefcount")) {
@@ -2237,15 +2259,32 @@ config_usage(int error, const wchar_t* program)
     if (error)
         fprintf(f, "Try `python -h' for more information.\n");
     else {
-        fputs(usage_1, f);
-        fputs(usage_2, f);
-        fputs(usage_3, f);
-        fprintf(f, usage_4, (wint_t)DELIM);
-        fprintf(f, usage_5, (wint_t)DELIM, PYTHONHOMEHELP);
-        fputs(usage_6, f);
+        fputs(usage_help, f);
     }
 }
 
+static void
+config_envvars_usage()
+{
+    printf(usage_envvars, (wint_t)DELIM, (wint_t)DELIM, PYTHONHOMEHELP);
+}
+
+static void
+config_xoptions_usage()
+{
+    puts(usage_xoptions);
+}
+
+static void
+config_complete_usage(const wchar_t* program)
+{
+   config_usage(0, program);
+   puts("\n");
+   config_envvars_usage();
+   puts("\n");
+   config_xoptions_usage();
+}
+
 
 /* Parse the command line arguments */
 static PyStatus
@@ -2297,9 +2336,9 @@ config_parse_cmdline(PyConfig *config, PyWideStringList *warnoptions,
         }
 
         switch (c) {
+        // Integers represent long options, see Python/getopt.c
         case 0:
-            // Handle long option.
-            assert(longindex == 0); // Only one long option now.
+            // check-hash-based-pycs
             if (wcscmp(_PyOS_optarg, L"always") == 0
                 || wcscmp(_PyOS_optarg, L"never") == 0
                 || wcscmp(_PyOS_optarg, L"default") == 0)
@@ -2317,6 +2356,21 @@ config_parse_cmdline(PyConfig *config, PyWideStringList *warnoptions,
             }
             break;
 
+        case 1:
+            // help-all
+            config_complete_usage(program);
+            return _PyStatus_EXIT(0);
+
+        case 2:
+            // help-env
+            config_envvars_usage();
+            return _PyStatus_EXIT(0);
+
+        case 3:
+            // help-xoptions
+            config_xoptions_usage();
+            return _PyStatus_EXIT(0);
+
         case 'b':
             config->bytes_warning++;
             break;



More information about the Python-checkins mailing list