From 35010b8cf2e6f5f2791fb336951c518e4f087a43 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 13 Nov 2024 22:50:46 +0200 Subject: [PATCH] gh-126390: Support for preserving order of options and nonoption arguments in gnu_getopt() (GH-126393) --- Doc/library/getopt.rst | 24 +++++++++++++++++++ Doc/whatsnew/3.14.rst | 3 +++ Lib/getopt.py | 18 ++++++++++---- Lib/test/test_getopt.py | 6 +++++ ...-11-04-13-16-18.gh-issue-126390.Cxvqa5.rst | 2 ++ 5 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-11-04-13-16-18.gh-issue-126390.Cxvqa5.rst diff --git a/Doc/library/getopt.rst b/Doc/library/getopt.rst index def0ea357bc..891885d3afb 100644 --- a/Doc/library/getopt.rst +++ b/Doc/library/getopt.rst @@ -85,6 +85,16 @@ exception: variable :envvar:`!POSIXLY_CORRECT` is set, then option processing stops as soon as a non-option argument is encountered. + If the first character of the option string is ``'-'``, non-option arguments + that are followed by options are added to the list of option-and-value pairs + as a pair that has ``None`` as its first element and the list of non-option + arguments as its second element. + The second element of the :func:`!gnu_getopt` result is a list of + program arguments after the last option. + + .. versionchanged:: 3.14 + Support for returning intermixed options and non-option arguments in order. + .. exception:: GetoptError @@ -144,6 +154,20 @@ Optional arguments should be specified explicitly: >>> args ['a1', 'a2'] +The order of options and non-option arguments can be preserved: + +.. doctest:: + + >>> s = 'a1 -x a2 a3 a4 --long a5 a6' + >>> args = s.split() + >>> args + ['a1', '-x', 'a2', 'a3', 'a4', '--long', 'a5', 'a6'] + >>> optlist, args = getopt.gnu_getopt(args, '-x:', ['long=']) + >>> optlist + [(None, ['a1']), ('-x', 'a2'), (None, ['a3', 'a4']), ('--long', 'a5')] + >>> args + ['a6'] + In a script, typical usage is something like this: .. testcode:: diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 31754fb55fc..d38188f0054 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -331,6 +331,9 @@ getopt * Add support for options with optional arguments. (Contributed by Serhiy Storchaka in :gh:`126374`.) +* Add support for returning intermixed options and non-option arguments in order. + (Contributed by Serhiy Storchaka in :gh:`126390`.) + http ---- diff --git a/Lib/getopt.py b/Lib/getopt.py index c12c08b29c7..a9c452a601e 100644 --- a/Lib/getopt.py +++ b/Lib/getopt.py @@ -24,9 +24,6 @@ option involved with the exception. # TODO for gnu_getopt(): # # - GNU getopt_long_only mechanism -# - allow the caller to specify ordering -# - RETURN_IN_ORDER option -# - GNU extension with '-' as first character of option string # - an option string with a W followed by semicolon should # treat "-W foo" as "--foo" @@ -63,7 +60,7 @@ def getopt(args, shortopts, longopts = []): long options which should be supported. The leading '--' characters should not be included in the option name. Options which require an argument should be followed by an equal sign - ('='). Options which acept an optional argument should be + ('='). Options which accept an optional argument should be followed by an equal sign and question mark ('=?'). The return value consists of two elements: the first is a list of @@ -116,8 +113,13 @@ def gnu_getopt(args, shortopts, longopts = []): else: longopts = list(longopts) + return_in_order = False + if shortopts.startswith('-'): + shortopts = shortopts[1:] + all_options_first = False + return_in_order = True # Allow options after non-option arguments? - if shortopts.startswith('+'): + elif shortopts.startswith('+'): shortopts = shortopts[1:] all_options_first = True elif os.environ.get("POSIXLY_CORRECT"): @@ -131,8 +133,14 @@ def gnu_getopt(args, shortopts, longopts = []): break if args[0][:2] == '--': + if return_in_order and prog_args: + opts.append((None, prog_args)) + prog_args = [] opts, args = do_longs(opts, args[0][2:], longopts, args[1:]) elif args[0][:1] == '-' and args[0] != '-': + if return_in_order and prog_args: + opts.append((None, prog_args)) + prog_args = [] opts, args = do_shorts(opts, args[0][1:], shortopts, args[1:]) else: if all_options_first: diff --git a/Lib/test/test_getopt.py b/Lib/test/test_getopt.py index 0675bcbb4e8..ed967ad2761 100644 --- a/Lib/test/test_getopt.py +++ b/Lib/test/test_getopt.py @@ -173,6 +173,12 @@ class GetoptTests(unittest.TestCase): self.assertEqual(args, ['-']) self.assertEqual(opts, [('-a', ''), ('-b', '-')]) + # Return positional arguments intermixed with options. + opts, args = getopt.gnu_getopt(cmdline, '-ab:', ['alpha', 'beta=']) + self.assertEqual(args, ['arg2']) + self.assertEqual(opts, [('-a', ''), (None, ['arg1']), ('-b', '1'), ('--alpha', ''), + ('--beta', '2'), ('--beta', '3')]) + # Posix style via + opts, args = getopt.gnu_getopt(cmdline, '+ab:', ['alpha', 'beta=']) self.assertEqual(opts, [('-a', '')]) diff --git a/Misc/NEWS.d/next/Library/2024-11-04-13-16-18.gh-issue-126390.Cxvqa5.rst b/Misc/NEWS.d/next/Library/2024-11-04-13-16-18.gh-issue-126390.Cxvqa5.rst new file mode 100644 index 00000000000..3b32bb512f6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-04-13-16-18.gh-issue-126390.Cxvqa5.rst @@ -0,0 +1,2 @@ +Add support for returning intermixed options and non-option arguments in +order in :func:`getopt.gnu_getopt`.