Skip to content

Commit

Permalink
gh-126390: Support for preserving order of options and nonoption argu…
Browse files Browse the repository at this point in the history
…ments in gnu_getopt() (GH-126393)
  • Loading branch information
serhiy-storchaka authored Nov 13, 2024
1 parent 12ca7e6 commit 35010b8
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 5 deletions.
24 changes: 24 additions & 0 deletions Doc/library/getopt.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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::
Expand Down
3 changes: 3 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
----

Expand Down
18 changes: 13 additions & 5 deletions Lib/getopt.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@
# 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"

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"):
Expand All @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_getopt.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,12 @@ def test_gnu_getopt(self):
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', '')])
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add support for returning intermixed options and non-option arguments in
order in :func:`getopt.gnu_getopt`.

0 comments on commit 35010b8

Please sign in to comment.