bpo-39546: argparse: Honor allow_abbrev=False for specified prefix_chars (GH-18337)

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
This commit is contained in:
Kyle Meyer 2020-02-18 04:48:57 -05:00 committed by GitHub
parent ffda25f6b8
commit 8edfc47bae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 67 additions and 26 deletions

View File

@ -2199,24 +2199,23 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
action = self._option_string_actions[option_string] action = self._option_string_actions[option_string]
return action, option_string, explicit_arg return action, option_string, explicit_arg
if self.allow_abbrev or not arg_string.startswith('--'): # search through all possible prefixes of the option string
# search through all possible prefixes of the option string # and all actions in the parser for possible interpretations
# and all actions in the parser for possible interpretations option_tuples = self._get_option_tuples(arg_string)
option_tuples = self._get_option_tuples(arg_string)
# if multiple actions match, the option string was ambiguous # if multiple actions match, the option string was ambiguous
if len(option_tuples) > 1: if len(option_tuples) > 1:
options = ', '.join([option_string options = ', '.join([option_string
for action, option_string, explicit_arg in option_tuples]) for action, option_string, explicit_arg in option_tuples])
args = {'option': arg_string, 'matches': options} args = {'option': arg_string, 'matches': options}
msg = _('ambiguous option: %(option)s could match %(matches)s') msg = _('ambiguous option: %(option)s could match %(matches)s')
self.error(msg % args) self.error(msg % args)
# if exactly one action matched, this segmentation is good, # if exactly one action matched, this segmentation is good,
# so return the parsed action # so return the parsed action
elif len(option_tuples) == 1: elif len(option_tuples) == 1:
option_tuple, = option_tuples option_tuple, = option_tuples
return option_tuple return option_tuple
# if it was not found as an option, but it looks like a negative # if it was not found as an option, but it looks like a negative
# number, it was meant to be positional # number, it was meant to be positional
@ -2240,16 +2239,17 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
# split at the '=' # split at the '='
chars = self.prefix_chars chars = self.prefix_chars
if option_string[0] in chars and option_string[1] in chars: if option_string[0] in chars and option_string[1] in chars:
if '=' in option_string: if self.allow_abbrev:
option_prefix, explicit_arg = option_string.split('=', 1) if '=' in option_string:
else: option_prefix, explicit_arg = option_string.split('=', 1)
option_prefix = option_string else:
explicit_arg = None option_prefix = option_string
for option_string in self._option_string_actions: explicit_arg = None
if option_string.startswith(option_prefix): for option_string in self._option_string_actions:
action = self._option_string_actions[option_string] if option_string.startswith(option_prefix):
tup = action, option_string, explicit_arg action = self._option_string_actions[option_string]
result.append(tup) tup = action, option_string, explicit_arg
result.append(tup)
# single character options can be concatenated with their arguments # single character options can be concatenated with their arguments
# but multiple character options always have to have their argument # but multiple character options always have to have their argument

View File

@ -810,6 +810,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): class TestDisallowLongAbbreviationAllowsShortGrouping(ParserTestCase):
"""Do not allow abbreviations of long options at all""" """Do not allow abbreviations of long options at all"""
@ -828,6 +845,26 @@ class TestDisallowLongAbbreviationAllowsShortGrouping(ParserTestCase):
('-ccrcc', NS(r='cc', c=2)), ('-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 # Positional tests
# ================ # ================

View File

@ -1112,6 +1112,7 @@ Bruce Merry
Alexis Métaireau Alexis Métaireau
Luke Mewburn Luke Mewburn
Carl Meyer Carl Meyer
Kyle Meyer
Mike Meyer Mike Meyer
Piotr Meyer Piotr Meyer
Steven Miale Steven Miale

View File

@ -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 "-".