Skip to content

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

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 2 commits into from
Nov 13, 2024
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
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 @@ -320,6 +320,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 @@ -172,6 +172,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`.
Loading