[3.8] bpo-39546: argparse: Honor allow_abbrev=False for specified prefix_chars (GH-18337) (GH-18543)
When `allow_abbrev` was first added, disabling the abbreviation of long options broke the grouping of short flags ([bpo-26967](https://bugs.python.org/issue26967)). As a fix,b1e4d1b603
(contained in v3.8) ignores `allow_abbrev=False` for a given argument string if the string does _not_ start with "--" (i.e. it doesn't look like a long option). This fix, however, doesn't take into account that long options can start with alternative characters specified via `prefix_chars`, introducing a regression: `allow_abbrev=False` has no effect on long options that start with an alternative prefix character. The most minimal fix would be to replace the "starts with --" check with a "starts with two prefix_chars characters". But `_get_option_tuples` already distinguishes between long and short options, so let's instead piggyback off of that check by moving the `allow_abbrev` condition into `_get_option_tuples`. https://bugs.python.org/issue39546 (cherry picked from commit8edfc47bae
) Co-authored-by: Kyle Meyer <kyle@kyleam.com> https://bugs.python.org/issue39546 Automerge-Triggered-By: @encukou
This commit is contained in:
parent
7fd752c1bc
commit
e412cbba52
|
@ -2144,24 +2144,23 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
|||
action = self._option_string_actions[option_string]
|
||||
return action, option_string, explicit_arg
|
||||
|
||||
if self.allow_abbrev or not arg_string.startswith('--'):
|
||||
# search through all possible prefixes of the option string
|
||||
# and all actions in the parser for possible interpretations
|
||||
option_tuples = self._get_option_tuples(arg_string)
|
||||
# search through all possible prefixes of the option string
|
||||
# and all actions in the parser for possible interpretations
|
||||
option_tuples = self._get_option_tuples(arg_string)
|
||||
|
||||
# if multiple actions match, the option string was ambiguous
|
||||
if len(option_tuples) > 1:
|
||||
options = ', '.join([option_string
|
||||
for action, option_string, explicit_arg in option_tuples])
|
||||
args = {'option': arg_string, 'matches': options}
|
||||
msg = _('ambiguous option: %(option)s could match %(matches)s')
|
||||
self.error(msg % args)
|
||||
# if multiple actions match, the option string was ambiguous
|
||||
if len(option_tuples) > 1:
|
||||
options = ', '.join([option_string
|
||||
for action, option_string, explicit_arg in option_tuples])
|
||||
args = {'option': arg_string, 'matches': options}
|
||||
msg = _('ambiguous option: %(option)s could match %(matches)s')
|
||||
self.error(msg % args)
|
||||
|
||||
# if exactly one action matched, this segmentation is good,
|
||||
# so return the parsed action
|
||||
elif len(option_tuples) == 1:
|
||||
option_tuple, = option_tuples
|
||||
return option_tuple
|
||||
# if exactly one action matched, this segmentation is good,
|
||||
# so return the parsed action
|
||||
elif len(option_tuples) == 1:
|
||||
option_tuple, = option_tuples
|
||||
return option_tuple
|
||||
|
||||
# if it was not found as an option, but it looks like a negative
|
||||
# number, it was meant to be positional
|
||||
|
@ -2185,16 +2184,17 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
|||
# split at the '='
|
||||
chars = self.prefix_chars
|
||||
if option_string[0] in chars and option_string[1] in chars:
|
||||
if '=' in option_string:
|
||||
option_prefix, explicit_arg = option_string.split('=', 1)
|
||||
else:
|
||||
option_prefix = option_string
|
||||
explicit_arg = None
|
||||
for option_string in self._option_string_actions:
|
||||
if option_string.startswith(option_prefix):
|
||||
action = self._option_string_actions[option_string]
|
||||
tup = action, option_string, explicit_arg
|
||||
result.append(tup)
|
||||
if self.allow_abbrev:
|
||||
if '=' in option_string:
|
||||
option_prefix, explicit_arg = option_string.split('=', 1)
|
||||
else:
|
||||
option_prefix = option_string
|
||||
explicit_arg = None
|
||||
for option_string in self._option_string_actions:
|
||||
if option_string.startswith(option_prefix):
|
||||
action = self._option_string_actions[option_string]
|
||||
tup = action, option_string, explicit_arg
|
||||
result.append(tup)
|
||||
|
||||
# single character options can be concatenated with their arguments
|
||||
# but multiple character options always have to have their argument
|
||||
|
|
|
@ -786,6 +786,23 @@ class TestOptionalsDisallowLongAbbreviation(ParserTestCase):
|
|||
]
|
||||
|
||||
|
||||
class TestOptionalsDisallowLongAbbreviationPrefixChars(ParserTestCase):
|
||||
"""Disallowing abbreviations works with alternative prefix characters"""
|
||||
|
||||
parser_signature = Sig(prefix_chars='+', allow_abbrev=False)
|
||||
argument_signatures = [
|
||||
Sig('++foo'),
|
||||
Sig('++foodle', action='store_true'),
|
||||
Sig('++foonly'),
|
||||
]
|
||||
failures = ['+foon 3', '++foon 3', '++food', '++food ++foo 2']
|
||||
successes = [
|
||||
('', NS(foo=None, foodle=False, foonly=None)),
|
||||
('++foo 3', NS(foo='3', foodle=False, foonly=None)),
|
||||
('++foonly 7 ++foodle ++foo 2', NS(foo='2', foodle=True, foonly='7')),
|
||||
]
|
||||
|
||||
|
||||
class TestDisallowLongAbbreviationAllowsShortGrouping(ParserTestCase):
|
||||
"""Do not allow abbreviations of long options at all"""
|
||||
|
||||
|
@ -804,6 +821,26 @@ class TestDisallowLongAbbreviationAllowsShortGrouping(ParserTestCase):
|
|||
('-ccrcc', NS(r='cc', c=2)),
|
||||
]
|
||||
|
||||
|
||||
class TestDisallowLongAbbreviationAllowsShortGroupingPrefix(ParserTestCase):
|
||||
"""Short option grouping works with custom prefix and allow_abbrev=False"""
|
||||
|
||||
parser_signature = Sig(prefix_chars='+', allow_abbrev=False)
|
||||
argument_signatures = [
|
||||
Sig('+r'),
|
||||
Sig('+c', action='count'),
|
||||
]
|
||||
failures = ['+r', '+c +r']
|
||||
successes = [
|
||||
('', NS(r=None, c=None)),
|
||||
('+ra', NS(r='a', c=None)),
|
||||
('+rcc', NS(r='cc', c=None)),
|
||||
('+cc', NS(r=None, c=2)),
|
||||
('+cc +ra', NS(r='a', c=2)),
|
||||
('+ccrcc', NS(r='cc', c=2)),
|
||||
]
|
||||
|
||||
|
||||
# ================
|
||||
# Positional tests
|
||||
# ================
|
||||
|
|
|
@ -1096,6 +1096,7 @@ Brian Merrell
|
|||
Alexis Métaireau
|
||||
Luke Mewburn
|
||||
Carl Meyer
|
||||
Kyle Meyer
|
||||
Mike Meyer
|
||||
Piotr Meyer
|
||||
Steven Miale
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
Fix a regression in :class:`~argparse.ArgumentParser` where
|
||||
``allow_abbrev=False`` was ignored for long options that used a prefix
|
||||
character other than "-".
|
Loading…
Reference in New Issue