Fix "Unrecognized argument" error when the same value is with different command-line flags (#881).
This commit is contained in:
parent
df4668754d
commit
7894600408
4 changed files with 214 additions and 17 deletions
1
NEWS
1
NEWS
|
@ -6,6 +6,7 @@
|
||||||
of files at once.
|
of files at once.
|
||||||
* #874: Add the configured repository label as "repository_label" to the interpolated variables
|
* #874: Add the configured repository label as "repository_label" to the interpolated variables
|
||||||
passed to before/after command hooks.
|
passed to before/after command hooks.
|
||||||
|
* #881: Fix "Unrecognized argument" error when the same value is with different command-line flags.
|
||||||
* In the "spot" check, don't try to hash symlinked directories.
|
* In the "spot" check, don't try to hash symlinked directories.
|
||||||
|
|
||||||
1.8.11
|
1.8.11
|
||||||
|
|
|
@ -113,6 +113,54 @@ def parse_and_record_action_arguments(
|
||||||
return tuple(argument for argument in remaining if argument != action_name)
|
return tuple(argument for argument in remaining if argument != action_name)
|
||||||
|
|
||||||
|
|
||||||
|
def argument_is_flag(argument):
|
||||||
|
'''
|
||||||
|
Return True if the given argument looks like a flag, e.g. '--some-flag', as opposed to a
|
||||||
|
non-flag value.
|
||||||
|
'''
|
||||||
|
return isinstance(argument, str) and argument.startswith('--')
|
||||||
|
|
||||||
|
|
||||||
|
def group_arguments_with_values(arguments):
|
||||||
|
'''
|
||||||
|
Given a sequence of arguments, return a sequence of tuples where each one contains either a
|
||||||
|
single argument (such as for a stand-alone flag) or a flag argument and its corresponding value.
|
||||||
|
|
||||||
|
For instance, given the following arguments sequence as input:
|
||||||
|
|
||||||
|
('--foo', '--bar', '33', '--baz')
|
||||||
|
|
||||||
|
... return the following output:
|
||||||
|
|
||||||
|
(('--foo',), ('--bar', '33'), ('--baz',))
|
||||||
|
'''
|
||||||
|
grouped_arguments = []
|
||||||
|
index = 0
|
||||||
|
|
||||||
|
while index < len(arguments):
|
||||||
|
this_argument = arguments[index]
|
||||||
|
|
||||||
|
try:
|
||||||
|
next_argument = arguments[index + 1]
|
||||||
|
except IndexError:
|
||||||
|
grouped_arguments.append((this_argument,))
|
||||||
|
break
|
||||||
|
|
||||||
|
if (
|
||||||
|
argument_is_flag(this_argument)
|
||||||
|
and not argument_is_flag(next_argument)
|
||||||
|
and next_argument not in ACTION_ALIASES
|
||||||
|
):
|
||||||
|
grouped_arguments.append((this_argument, next_argument))
|
||||||
|
index += 2
|
||||||
|
continue
|
||||||
|
|
||||||
|
grouped_arguments.append((this_argument,))
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
return tuple(grouped_arguments)
|
||||||
|
|
||||||
|
|
||||||
def get_unparsable_arguments(remaining_action_arguments):
|
def get_unparsable_arguments(remaining_action_arguments):
|
||||||
'''
|
'''
|
||||||
Given a sequence of argument tuples (one per action parser that parsed arguments), determine the
|
Given a sequence of argument tuples (one per action parser that parsed arguments), determine the
|
||||||
|
@ -121,12 +169,21 @@ def get_unparsable_arguments(remaining_action_arguments):
|
||||||
if not remaining_action_arguments:
|
if not remaining_action_arguments:
|
||||||
return ()
|
return ()
|
||||||
|
|
||||||
|
grouped_action_arguments = tuple(
|
||||||
|
group_arguments_with_values(action_arguments)
|
||||||
|
for action_arguments in remaining_action_arguments
|
||||||
|
)
|
||||||
|
|
||||||
return tuple(
|
return tuple(
|
||||||
argument
|
itertools.chain.from_iterable(
|
||||||
for argument in dict.fromkeys(
|
argument_group
|
||||||
itertools.chain.from_iterable(remaining_action_arguments)
|
for argument_group in dict.fromkeys(
|
||||||
).keys()
|
itertools.chain.from_iterable(grouped_action_arguments)
|
||||||
if all(argument in action_arguments for action_arguments in remaining_action_arguments)
|
).keys()
|
||||||
|
if all(
|
||||||
|
argument_group in action_arguments for action_arguments in grouped_action_arguments
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -89,7 +89,7 @@ class Multi_stream_handler(logging.Handler):
|
||||||
|
|
||||||
|
|
||||||
class Console_no_color_formatter(logging.Formatter):
|
class Console_no_color_formatter(logging.Formatter):
|
||||||
def format(self, record):
|
def format(self, record): # pragma: no cover
|
||||||
return record.msg
|
return record.msg
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -130,36 +130,175 @@ def test_parse_and_record_action_arguments_with_borg_action_consumes_arguments_a
|
||||||
assert borg_parsed_arguments.options == ('list',)
|
assert borg_parsed_arguments.options == ('list',)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'argument, expected',
|
||||||
|
[
|
||||||
|
('--foo', True),
|
||||||
|
('foo', False),
|
||||||
|
(33, False),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_argument_is_flag_only_for_string_starting_with_double_dash(argument, expected):
|
||||||
|
assert module.argument_is_flag(argument) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'arguments, expected',
|
'arguments, expected',
|
||||||
[
|
[
|
||||||
# A global flag remaining from each parsed action.
|
# Ending with a valueless flag.
|
||||||
(
|
(
|
||||||
|
('--foo', '--bar', 33, '--baz'),
|
||||||
(
|
(
|
||||||
('--latest', 'archive', 'prune', 'extract', 'list', '--test-flag'),
|
('--foo',),
|
||||||
('--latest', 'archive', 'check', 'extract', 'list', '--test-flag'),
|
('--bar', 33),
|
||||||
('prune', 'check', 'list', '--test-flag'),
|
('--baz',),
|
||||||
('prune', 'check', 'extract', '--test-flag'),
|
|
||||||
),
|
),
|
||||||
('--test-flag',),
|
|
||||||
),
|
),
|
||||||
# No global flags remaining.
|
# Ending with a flag and its corresponding value.
|
||||||
|
(
|
||||||
|
('--foo', '--bar', 33, '--baz', '--quux', 'thing'),
|
||||||
|
(('--foo',), ('--bar', 33), ('--baz',), ('--quux', 'thing')),
|
||||||
|
),
|
||||||
|
# Starting with an action name.
|
||||||
|
(
|
||||||
|
('check', '--foo', '--bar', 33, '--baz'),
|
||||||
|
(
|
||||||
|
('check',),
|
||||||
|
('--foo',),
|
||||||
|
('--bar', 33),
|
||||||
|
('--baz',),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
# Action name that one could mistake for a flag value.
|
||||||
|
(('--progress', 'list'), (('--progress',), ('list',))),
|
||||||
|
# No arguments.
|
||||||
|
((), ()),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_group_arguments_with_values_returns_flags_with_corresponding_values(arguments, expected):
|
||||||
|
flexmock(module).should_receive('argument_is_flag').with_args('--foo').and_return(True)
|
||||||
|
flexmock(module).should_receive('argument_is_flag').with_args('--bar').and_return(True)
|
||||||
|
flexmock(module).should_receive('argument_is_flag').with_args('--baz').and_return(True)
|
||||||
|
flexmock(module).should_receive('argument_is_flag').with_args('--quux').and_return(True)
|
||||||
|
flexmock(module).should_receive('argument_is_flag').with_args('--progress').and_return(True)
|
||||||
|
flexmock(module).should_receive('argument_is_flag').with_args(33).and_return(False)
|
||||||
|
flexmock(module).should_receive('argument_is_flag').with_args('thing').and_return(False)
|
||||||
|
flexmock(module).should_receive('argument_is_flag').with_args('check').and_return(False)
|
||||||
|
flexmock(module).should_receive('argument_is_flag').with_args('list').and_return(False)
|
||||||
|
|
||||||
|
assert module.group_arguments_with_values(arguments) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'arguments, grouped_arguments, expected',
|
||||||
|
[
|
||||||
|
# An unparsable flag remaining from each parsed action.
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
('--latest', 'archive', 'prune', 'extract', 'list'),
|
('--latest', 'archive', 'prune', 'extract', 'list', '--flag'),
|
||||||
('--latest', 'archive', 'check', 'extract', 'list'),
|
('--latest', 'archive', 'check', 'extract', 'list', '--flag'),
|
||||||
|
('prune', 'check', 'list', '--flag'),
|
||||||
|
('prune', 'check', 'extract', '--flag'),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(
|
||||||
|
('--latest',),
|
||||||
|
('archive',),
|
||||||
|
('prune',),
|
||||||
|
('extract',),
|
||||||
|
('list',),
|
||||||
|
('--flag',),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
('--latest',),
|
||||||
|
('archive',),
|
||||||
|
('check',),
|
||||||
|
('extract',),
|
||||||
|
('list',),
|
||||||
|
('--flag',),
|
||||||
|
),
|
||||||
|
(('prune',), ('check',), ('list',), ('--flag',)),
|
||||||
|
(('prune',), ('check',), ('extract',), ('--flag',)),
|
||||||
|
),
|
||||||
|
('--flag',),
|
||||||
|
),
|
||||||
|
# No unparsable flags remaining.
|
||||||
|
(
|
||||||
|
(
|
||||||
|
('--archive', 'archive', 'prune', 'extract', 'list'),
|
||||||
|
('--archive', 'archive', 'check', 'extract', 'list'),
|
||||||
('prune', 'check', 'list'),
|
('prune', 'check', 'list'),
|
||||||
('prune', 'check', 'extract'),
|
('prune', 'check', 'extract'),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
'--archive',
|
||||||
|
'archive',
|
||||||
|
),
|
||||||
|
('prune',),
|
||||||
|
('extract',),
|
||||||
|
('list',),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(
|
||||||
|
'--archive',
|
||||||
|
'archive',
|
||||||
|
),
|
||||||
|
('check',),
|
||||||
|
('extract',),
|
||||||
|
('list',),
|
||||||
|
),
|
||||||
|
(('prune',), ('check',), ('list',)),
|
||||||
|
(('prune',), ('check',), ('extract',)),
|
||||||
|
),
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
# No unparsable flags remaining, but some values in common.
|
||||||
|
(
|
||||||
|
(
|
||||||
|
('--verbosity', '5', 'archive', 'prune', 'extract', 'list'),
|
||||||
|
('--last', '5', 'archive', 'check', 'extract', 'list'),
|
||||||
|
('prune', 'check', 'list', '--last', '5'),
|
||||||
|
('prune', 'check', '--verbosity', '5', 'extract'),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(('--verbosity', '5'), ('archive',), ('prune',), ('extract',), ('list',)),
|
||||||
|
(
|
||||||
|
(
|
||||||
|
'--last',
|
||||||
|
'5',
|
||||||
|
),
|
||||||
|
('archive',),
|
||||||
|
('check',),
|
||||||
|
('extract',),
|
||||||
|
('list',),
|
||||||
|
),
|
||||||
|
(('prune',), ('check',), ('list',), ('--last', '5')),
|
||||||
|
(
|
||||||
|
('prune',),
|
||||||
|
('check',),
|
||||||
|
(
|
||||||
|
'--verbosity',
|
||||||
|
'5',
|
||||||
|
),
|
||||||
|
('extract',),
|
||||||
|
),
|
||||||
|
),
|
||||||
(),
|
(),
|
||||||
),
|
),
|
||||||
# No flags.
|
# No flags.
|
||||||
((), ()),
|
((), (), ()),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_get_unparsable_arguments_returns_remaining_arguments_that_no_action_can_parse(
|
def test_get_unparsable_arguments_returns_remaining_arguments_that_no_action_can_parse(
|
||||||
arguments, expected
|
arguments, grouped_arguments, expected
|
||||||
):
|
):
|
||||||
|
for action_arguments, grouped_action_arguments in zip(arguments, grouped_arguments):
|
||||||
|
flexmock(module).should_receive('group_arguments_with_values').with_args(
|
||||||
|
action_arguments
|
||||||
|
).and_return(grouped_action_arguments)
|
||||||
|
|
||||||
assert module.get_unparsable_arguments(arguments) == expected
|
assert module.get_unparsable_arguments(arguments) == expected
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue