mirror of
https://github.com/YunoHost-Apps/zwave-js-ui_ynh.git
synced 2024-09-03 18:06:00 +02:00
343 lines
15 KiB
Python
343 lines
15 KiB
Python
|
# Copyright 2012-2021, Andrey Kislyuk and argcomplete contributors.
|
||
|
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
|
||
|
|
||
|
import typing as t
|
||
|
from argparse import _ # type:ignore
|
||
|
from argparse import (
|
||
|
ONE_OR_MORE,
|
||
|
OPTIONAL,
|
||
|
PARSER,
|
||
|
REMAINDER,
|
||
|
SUPPRESS,
|
||
|
ZERO_OR_MORE,
|
||
|
ArgumentError,
|
||
|
ArgumentParser,
|
||
|
_get_action_name,
|
||
|
_SubParsersAction,
|
||
|
)
|
||
|
|
||
|
_num_consumed_args: t.Dict[str, int] = {}
|
||
|
|
||
|
|
||
|
def action_is_satisfied(action):
|
||
|
''' Returns False if the parse would raise an error if no more arguments are given to this action, True otherwise.
|
||
|
'''
|
||
|
num_consumed_args = _num_consumed_args.get(action, 0)
|
||
|
|
||
|
if action.nargs in [OPTIONAL, ZERO_OR_MORE, REMAINDER]:
|
||
|
return True
|
||
|
if action.nargs == ONE_OR_MORE:
|
||
|
return num_consumed_args >= 1
|
||
|
if action.nargs == PARSER:
|
||
|
# Not sure what this should be, but this previously always returned False
|
||
|
# so at least this won't break anything that wasn't already broken.
|
||
|
return False
|
||
|
if action.nargs is None:
|
||
|
return num_consumed_args == 1
|
||
|
|
||
|
assert isinstance(action.nargs, int), 'failed to handle a possible nargs value: %r' % action.nargs
|
||
|
return num_consumed_args == action.nargs
|
||
|
|
||
|
|
||
|
def action_is_open(action):
|
||
|
''' Returns True if action could consume more arguments (i.e., its pattern is open).
|
||
|
'''
|
||
|
num_consumed_args = _num_consumed_args.get(action, 0)
|
||
|
|
||
|
if action.nargs in [ZERO_OR_MORE, ONE_OR_MORE, PARSER, REMAINDER]:
|
||
|
return True
|
||
|
if action.nargs == OPTIONAL or action.nargs is None:
|
||
|
return num_consumed_args == 0
|
||
|
|
||
|
assert isinstance(action.nargs, int), 'failed to handle a possible nargs value: %r' % action.nargs
|
||
|
return num_consumed_args < action.nargs
|
||
|
|
||
|
|
||
|
def action_is_greedy(action, isoptional=False):
|
||
|
''' Returns True if action will necessarily consume the next argument.
|
||
|
isoptional indicates whether the argument is an optional (starts with -).
|
||
|
'''
|
||
|
num_consumed_args = _num_consumed_args.get(action, 0)
|
||
|
|
||
|
if action.option_strings:
|
||
|
if not isoptional and not action_is_satisfied(action):
|
||
|
return True
|
||
|
return action.nargs == REMAINDER
|
||
|
else:
|
||
|
return action.nargs == REMAINDER and num_consumed_args >= 1
|
||
|
|
||
|
|
||
|
class IntrospectiveArgumentParser(ArgumentParser):
|
||
|
''' The following is a verbatim copy of ArgumentParser._parse_known_args (Python 2.7.3),
|
||
|
except for the lines that contain the string "Added by argcomplete".
|
||
|
'''
|
||
|
|
||
|
def _parse_known_args(self, arg_strings, namespace):
|
||
|
_num_consumed_args.clear() # Added by argcomplete
|
||
|
self._argcomplete_namespace = namespace
|
||
|
self.active_actions = [] # Added by argcomplete
|
||
|
# replace arg strings that are file references
|
||
|
if self.fromfile_prefix_chars is not None:
|
||
|
arg_strings = self._read_args_from_files(arg_strings)
|
||
|
|
||
|
# map all mutually exclusive arguments to the other arguments
|
||
|
# they can't occur with
|
||
|
action_conflicts = {}
|
||
|
self._action_conflicts = action_conflicts # Added by argcomplete
|
||
|
for mutex_group in self._mutually_exclusive_groups:
|
||
|
group_actions = mutex_group._group_actions
|
||
|
for i, mutex_action in enumerate(mutex_group._group_actions):
|
||
|
conflicts = action_conflicts.setdefault(mutex_action, [])
|
||
|
conflicts.extend(group_actions[:i])
|
||
|
conflicts.extend(group_actions[i + 1:])
|
||
|
|
||
|
# find all option indices, and determine the arg_string_pattern
|
||
|
# which has an 'O' if there is an option at an index,
|
||
|
# an 'A' if there is an argument, or a '-' if there is a '--'
|
||
|
option_string_indices = {}
|
||
|
arg_string_pattern_parts = []
|
||
|
arg_strings_iter = iter(arg_strings)
|
||
|
for i, arg_string in enumerate(arg_strings_iter):
|
||
|
|
||
|
# all args after -- are non-options
|
||
|
if arg_string == '--':
|
||
|
arg_string_pattern_parts.append('-')
|
||
|
for arg_string in arg_strings_iter:
|
||
|
arg_string_pattern_parts.append('A')
|
||
|
|
||
|
# otherwise, add the arg to the arg strings
|
||
|
# and note the index if it was an option
|
||
|
else:
|
||
|
option_tuple = self._parse_optional(arg_string)
|
||
|
if option_tuple is None:
|
||
|
pattern = 'A'
|
||
|
else:
|
||
|
option_string_indices[i] = option_tuple
|
||
|
pattern = 'O'
|
||
|
arg_string_pattern_parts.append(pattern)
|
||
|
|
||
|
# join the pieces together to form the pattern
|
||
|
arg_strings_pattern = ''.join(arg_string_pattern_parts)
|
||
|
|
||
|
# converts arg strings to the appropriate and then takes the action
|
||
|
seen_actions = set()
|
||
|
seen_non_default_actions = set()
|
||
|
self._seen_non_default_actions = seen_non_default_actions # Added by argcomplete
|
||
|
|
||
|
def take_action(action, argument_strings, option_string=None):
|
||
|
seen_actions.add(action)
|
||
|
argument_values = self._get_values(action, argument_strings)
|
||
|
|
||
|
# error if this argument is not allowed with other previously
|
||
|
# seen arguments, assuming that actions that use the default
|
||
|
# value don't really count as "present"
|
||
|
if argument_values is not action.default:
|
||
|
seen_non_default_actions.add(action)
|
||
|
for conflict_action in action_conflicts.get(action, []):
|
||
|
if conflict_action in seen_non_default_actions:
|
||
|
msg = _('not allowed with argument %s')
|
||
|
action_name = _get_action_name(conflict_action)
|
||
|
raise ArgumentError(action, msg % action_name)
|
||
|
|
||
|
# take the action if we didn't receive a SUPPRESS value
|
||
|
# (e.g. from a default)
|
||
|
if argument_values is not SUPPRESS \
|
||
|
or isinstance(action, _SubParsersAction):
|
||
|
try:
|
||
|
action(self, namespace, argument_values, option_string)
|
||
|
except BaseException:
|
||
|
# Begin added by argcomplete
|
||
|
# When a subparser action is taken and fails due to incomplete arguments, it does not merge the
|
||
|
# contents of its parsed namespace into the parent namespace. Do that here to allow completers to
|
||
|
# access the partially parsed arguments for the subparser.
|
||
|
if isinstance(action, _SubParsersAction):
|
||
|
subnamespace = action._name_parser_map[argument_values[0]]._argcomplete_namespace
|
||
|
for key, value in vars(subnamespace).items():
|
||
|
setattr(namespace, key, value)
|
||
|
# End added by argcomplete
|
||
|
raise
|
||
|
|
||
|
# function to convert arg_strings into an optional action
|
||
|
def consume_optional(start_index):
|
||
|
|
||
|
# get the optional identified at this index
|
||
|
option_tuple = option_string_indices[start_index]
|
||
|
action, option_string, explicit_arg = option_tuple
|
||
|
|
||
|
# identify additional optionals in the same arg string
|
||
|
# (e.g. -xyz is the same as -x -y -z if no args are required)
|
||
|
match_argument = self._match_argument
|
||
|
action_tuples = []
|
||
|
while True:
|
||
|
|
||
|
# if we found no optional action, skip it
|
||
|
if action is None:
|
||
|
extras.append(arg_strings[start_index])
|
||
|
return start_index + 1
|
||
|
|
||
|
# if there is an explicit argument, try to match the
|
||
|
# optional's string arguments to only this
|
||
|
if explicit_arg is not None:
|
||
|
arg_count = match_argument(action, 'A')
|
||
|
|
||
|
# if the action is a single-dash option and takes no
|
||
|
# arguments, try to parse more single-dash options out
|
||
|
# of the tail of the option string
|
||
|
chars = self.prefix_chars
|
||
|
if arg_count == 0 and option_string[1] not in chars:
|
||
|
action_tuples.append((action, [], option_string))
|
||
|
char = option_string[0]
|
||
|
option_string = char + explicit_arg[0]
|
||
|
new_explicit_arg = explicit_arg[1:] or None
|
||
|
optionals_map = self._option_string_actions
|
||
|
if option_string in optionals_map:
|
||
|
action = optionals_map[option_string]
|
||
|
explicit_arg = new_explicit_arg
|
||
|
else:
|
||
|
msg = _('ignored explicit argument %r')
|
||
|
raise ArgumentError(action, msg % explicit_arg)
|
||
|
|
||
|
# if the action expect exactly one argument, we've
|
||
|
# successfully matched the option; exit the loop
|
||
|
elif arg_count == 1:
|
||
|
stop = start_index + 1
|
||
|
args = [explicit_arg]
|
||
|
action_tuples.append((action, args, option_string))
|
||
|
break
|
||
|
|
||
|
# error if a double-dash option did not use the
|
||
|
# explicit argument
|
||
|
else:
|
||
|
msg = _('ignored explicit argument %r')
|
||
|
raise ArgumentError(action, msg % explicit_arg)
|
||
|
|
||
|
# if there is no explicit argument, try to match the
|
||
|
# optional's string arguments with the following strings
|
||
|
# if successful, exit the loop
|
||
|
else:
|
||
|
start = start_index + 1
|
||
|
selected_patterns = arg_strings_pattern[start:]
|
||
|
self.active_actions = [action] # Added by argcomplete
|
||
|
_num_consumed_args[action] = 0 # Added by argcomplete
|
||
|
arg_count = match_argument(action, selected_patterns)
|
||
|
stop = start + arg_count
|
||
|
args = arg_strings[start:stop]
|
||
|
|
||
|
# Begin added by argcomplete
|
||
|
# If the pattern is not open (e.g. no + at the end), remove the action from active actions (since
|
||
|
# it wouldn't be able to consume any more args)
|
||
|
_num_consumed_args[action] = len(args)
|
||
|
if not action_is_open(action):
|
||
|
self.active_actions.remove(action)
|
||
|
# End added by argcomplete
|
||
|
|
||
|
action_tuples.append((action, args, option_string))
|
||
|
break
|
||
|
|
||
|
# add the Optional to the list and return the index at which
|
||
|
# the Optional's string args stopped
|
||
|
assert action_tuples
|
||
|
for action, args, option_string in action_tuples:
|
||
|
take_action(action, args, option_string)
|
||
|
return stop
|
||
|
|
||
|
# the list of Positionals left to be parsed; this is modified
|
||
|
# by consume_positionals()
|
||
|
positionals = self._get_positional_actions()
|
||
|
|
||
|
# function to convert arg_strings into positional actions
|
||
|
def consume_positionals(start_index):
|
||
|
# match as many Positionals as possible
|
||
|
match_partial = self._match_arguments_partial
|
||
|
selected_pattern = arg_strings_pattern[start_index:]
|
||
|
arg_counts = match_partial(positionals, selected_pattern)
|
||
|
|
||
|
# slice off the appropriate arg strings for each Positional
|
||
|
# and add the Positional and its args to the list
|
||
|
for action, arg_count in zip(positionals, arg_counts): # Added by argcomplete
|
||
|
self.active_actions.append(action) # Added by argcomplete
|
||
|
for action, arg_count in zip(positionals, arg_counts):
|
||
|
args = arg_strings[start_index: start_index + arg_count]
|
||
|
start_index += arg_count
|
||
|
_num_consumed_args[action] = len(args) # Added by argcomplete
|
||
|
take_action(action, args)
|
||
|
|
||
|
# slice off the Positionals that we just parsed and return the
|
||
|
# index at which the Positionals' string args stopped
|
||
|
positionals[:] = positionals[len(arg_counts):]
|
||
|
return start_index
|
||
|
|
||
|
# consume Positionals and Optionals alternately, until we have
|
||
|
# passed the last option string
|
||
|
extras = []
|
||
|
start_index = 0
|
||
|
if option_string_indices:
|
||
|
max_option_string_index = max(option_string_indices)
|
||
|
else:
|
||
|
max_option_string_index = -1
|
||
|
while start_index <= max_option_string_index:
|
||
|
|
||
|
# consume any Positionals preceding the next option
|
||
|
next_option_string_index = min([
|
||
|
index
|
||
|
for index in option_string_indices
|
||
|
if index >= start_index])
|
||
|
if start_index != next_option_string_index:
|
||
|
positionals_end_index = consume_positionals(start_index)
|
||
|
|
||
|
# only try to parse the next optional if we didn't consume
|
||
|
# the option string during the positionals parsing
|
||
|
if positionals_end_index > start_index:
|
||
|
start_index = positionals_end_index
|
||
|
continue
|
||
|
else:
|
||
|
start_index = positionals_end_index
|
||
|
|
||
|
# if we consumed all the positionals we could and we're not
|
||
|
# at the index of an option string, there were extra arguments
|
||
|
if start_index not in option_string_indices:
|
||
|
strings = arg_strings[start_index:next_option_string_index]
|
||
|
extras.extend(strings)
|
||
|
start_index = next_option_string_index
|
||
|
|
||
|
# consume the next optional and any arguments for it
|
||
|
start_index = consume_optional(start_index)
|
||
|
|
||
|
# consume any positionals following the last Optional
|
||
|
stop_index = consume_positionals(start_index)
|
||
|
|
||
|
# if we didn't consume all the argument strings, there were extras
|
||
|
extras.extend(arg_strings[stop_index:])
|
||
|
|
||
|
# if we didn't use all the Positional objects, there were too few
|
||
|
# arg strings supplied.
|
||
|
|
||
|
if positionals:
|
||
|
self.active_actions.append(positionals[0]) # Added by argcomplete
|
||
|
self.error(_('too few arguments'))
|
||
|
|
||
|
# make sure all required actions were present
|
||
|
for action in self._actions:
|
||
|
if action.required:
|
||
|
if action not in seen_actions:
|
||
|
name = _get_action_name(action)
|
||
|
self.error(_('argument %s is required') % name)
|
||
|
|
||
|
# make sure all required groups had one option present
|
||
|
for group in self._mutually_exclusive_groups:
|
||
|
if group.required:
|
||
|
for action in group._group_actions:
|
||
|
if action in seen_non_default_actions:
|
||
|
break
|
||
|
|
||
|
# if no actions were used, report the error
|
||
|
else:
|
||
|
names = [_get_action_name(action)
|
||
|
for action in group._group_actions
|
||
|
if action.help is not SUPPRESS]
|
||
|
msg = _('one of the arguments %s is required')
|
||
|
self.error(msg % ' '.join(names))
|
||
|
|
||
|
# return the updated namespace and the extra arguments
|
||
|
return namespace, extras
|