Issue #14910: Add allow_abbrev parameter to argparse.ArgumentParser. · pythoncapi/cpython@8089cd6 · GitHub
Skip to content

Commit 8089cd6

Browse files
committed
Issue python#14910: Add allow_abbrev parameter to argparse.ArgumentParser.
Patch by Jonathan Paugh, Steven Bethard, paul j3 and Daniel Eriksson.
1 parent 0fe6325 commit 8089cd6

5 files changed

Lines changed: 97 additions & 22 deletions

File tree

Doc/library/argparse.rst

Lines changed: 31 additions & 4 deletions

Doc/whatsnew/3.5.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,14 @@ New Modules
146146
Improved Modules
147147
================
148148

149+
argparse
150+
--------
151+
152+
* :class:`~argparse.ArgumentParser` now allows to disable
153+
:ref:`abbreviated usage <prefix-matching>` of long options by setting
154+
:ref:`allow_abbrev` to ``False``.
155+
(Contributed by Jonathan Paugh, Steven Bethard, paul j3 and Daniel Eriksson.)
156+
149157
cgi
150158
---
151159

Lib/argparse.py

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1590,6 +1590,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
15901590
- argument_default -- The default value for all arguments
15911591
- conflict_handler -- String indicating how to handle conflicts
15921592
- add_help -- Add a -h/-help option
1593+
- allow_abbrev -- Allow long options to be abbreviated unambiguously
15931594
"""
15941595

15951596
def __init__(self,
@@ -1603,7 +1604,8 @@ def __init__(self,
16031604
fromfile_prefix_chars=None,
16041605
argument_default=None,
16051606
conflict_handler='error',
1606-
add_help=True):
1607+
add_help=True,
1608+
allow_abbrev=True):
16071609

16081610
superinit = super(ArgumentParser, self).__init__
16091611
superinit(description=description,
@@ -1621,6 +1623,7 @@ def __init__(self,
16211623
self.formatter_class = formatter_class
16221624
self.fromfile_prefix_chars = fromfile_prefix_chars
16231625
self.add_help = add_help
1626+
self.allow_abbrev = allow_abbrev
16241627

16251628
add_group = self.add_argument_group
16261629
self._positionals = add_group(_('positional arguments'))
@@ -2098,23 +2101,24 @@ def _parse_optional(self, arg_string):
20982101
action = self._option_string_actions[option_string]
20992102
return action, option_string, explicit_arg
21002103

2101-
# search through all possible prefixes of the option string
2102-
# and all actions in the parser for possible interpretations
2103-
option_tuples = self._get_option_tuples(arg_string)
2104-
2105-
# if multiple actions match, the option string was ambiguous
2106-
if len(option_tuples) > 1:
2107-
options = ', '.join([option_string
2108-
for action, option_string, explicit_arg in option_tuples])
2109-
args = {'option': arg_string, 'matches': options}
2110-
msg = _('ambiguous option: %(option)s could match %(matches)s')
2111-
self.error(msg % args)
2112-
2113-
# if exactly one action matched, this segmentation is good,
2114-
# so return the parsed action
2115-
elif len(option_tuples) == 1:
2116-
option_tuple, = option_tuples
2117-
return option_tuple
2104+
if self.allow_abbrev:
2105+
# search through all possible prefixes of the option string
2106+
# and all actions in the parser for possible interpretations
2107+
option_tuples = self._get_option_tuples(arg_string)
2108+
2109+
# if multiple actions match, the option string was ambiguous
2110+
if len(option_tuples) > 1:
2111+
options = ', '.join([option_string
2112+
for action, option_string, explicit_arg in option_tuples])
2113+
args = {'option': arg_string, 'matches': options}
2114+
msg = _('ambiguous option: %(option)s could match %(matches)s')
2115+
self.error(msg % args)
2116+
2117+
# if exactly one action matched, this segmentation is good,
2118+
# so return the parsed action
2119+
elif len(option_tuples) == 1:
2120+
option_tuple, = option_tuples
2121+
return option_tuple
21182122

21192123
# if it was not found as an option, but it looks like a negative
21202124
# number, it was meant to be positional

Lib/test/test_argparse.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,39 @@ class TestOptionalsActionCount(ParserTestCase):
753753
]
754754

755755

756+
class TestOptionalsAllowLongAbbreviation(ParserTestCase):
757+
"""Allow long options to be abbreviated unambiguously"""
758+
759+
argument_signatures = [
760+
Sig('--foo'),
761+
Sig('--foobaz'),
762+
Sig('--fooble', action='store_true'),
763+
]
764+
failures = ['--foob 5', '--foob']
765+
successes = [
766+
('', NS(foo=None, foobaz=None, fooble=False)),
767+
('--foo 7', NS(foo='7', foobaz=None, fooble=False)),
768+
('--fooba a', NS(foo=None, foobaz='a', fooble=False)),
769+
('--foobl --foo g', NS(foo='g', foobaz=None, fooble=True)),
770+
]
771+
772+
773+
class TestOptionalsDisallowLongAbbreviation(ParserTestCase):
774+
"""Do not allow abbreviations of long options at all"""
775+
776+
parser_signature = Sig(allow_abbrev=False)
777+
argument_signatures = [
778+
Sig('--foo'),
779+
Sig('--foodle', action='store_true'),
780+
Sig('--foonly'),
781+
]
782+
failures = ['-foon 3', '--foon 3', '--food', '--food --foo 2']
783+
successes = [
784+
('', NS(foo=None, foodle=False, foonly=None)),
785+
('--foo 3', NS(foo='3', foodle=False, foonly=None)),
786+
('--foonly 7 --foodle --foo 2', NS(foo='2', foodle=True, foonly='7')),
787+
]
788+
756789
# ================
757790
# Positional tests
758791
# ================

Misc/NEWS

Lines changed: 3 additions & 0 deletions

0 commit comments

Comments
 (0)