gh-126390: Support for preserving order of options and nonoption arguments in gnu_getopt() (GH-126393)

This commit is contained in:
Serhiy Storchaka 2024-11-13 22:50:46 +02:00 committed by GitHub
parent 12ca7e622f
commit 35010b8cf2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 48 additions and 5 deletions

View file

@ -85,6 +85,16 @@ exception:
variable :envvar:`!POSIXLY_CORRECT` is set, then option processing stops as variable :envvar:`!POSIXLY_CORRECT` is set, then option processing stops as
soon as a non-option argument is encountered. 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 .. exception:: GetoptError
@ -144,6 +154,20 @@ Optional arguments should be specified explicitly:
>>> args >>> args
['a1', 'a2'] ['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: In a script, typical usage is something like this:
.. testcode:: .. testcode::

View file

@ -331,6 +331,9 @@ getopt
* Add support for options with optional arguments. * Add support for options with optional arguments.
(Contributed by Serhiy Storchaka in :gh:`126374`.) (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 http
---- ----

View file

@ -24,9 +24,6 @@ option involved with the exception.
# TODO for gnu_getopt(): # TODO for gnu_getopt():
# #
# - GNU getopt_long_only mechanism # - 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 # - an option string with a W followed by semicolon should
# treat "-W foo" as "--foo" # treat "-W foo" as "--foo"
@ -63,7 +60,7 @@ def getopt(args, shortopts, longopts = []):
long options which should be supported. The leading '--' long options which should be supported. The leading '--'
characters should not be included in the option name. Options characters should not be included in the option name. Options
which require an argument should be followed by an equal sign 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 ('=?'). followed by an equal sign and question mark ('=?').
The return value consists of two elements: the first is a list of The return value consists of two elements: the first is a list of
@ -116,8 +113,13 @@ def gnu_getopt(args, shortopts, longopts = []):
else: else:
longopts = list(longopts) 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? # Allow options after non-option arguments?
if shortopts.startswith('+'): elif shortopts.startswith('+'):
shortopts = shortopts[1:] shortopts = shortopts[1:]
all_options_first = True all_options_first = True
elif os.environ.get("POSIXLY_CORRECT"): elif os.environ.get("POSIXLY_CORRECT"):
@ -131,8 +133,14 @@ def gnu_getopt(args, shortopts, longopts = []):
break break
if args[0][:2] == '--': 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:]) opts, args = do_longs(opts, args[0][2:], longopts, args[1:])
elif args[0][:1] == '-' and args[0] != '-': 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:]) opts, args = do_shorts(opts, args[0][1:], shortopts, args[1:])
else: else:
if all_options_first: if all_options_first:

View file

@ -173,6 +173,12 @@ class GetoptTests(unittest.TestCase):
self.assertEqual(args, ['-']) self.assertEqual(args, ['-'])
self.assertEqual(opts, [('-a', ''), ('-b', '-')]) 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 + # Posix style via +
opts, args = getopt.gnu_getopt(cmdline, '+ab:', ['alpha', 'beta=']) opts, args = getopt.gnu_getopt(cmdline, '+ab:', ['alpha', 'beta='])
self.assertEqual(opts, [('-a', '')]) self.assertEqual(opts, [('-a', '')])

View file

@ -0,0 +1,2 @@
Add support for returning intermixed options and non-option arguments in
order in :func:`getopt.gnu_getopt`.