| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342 |
- # Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors. Licensed under the terms of the
- # `Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. Distribution of the LICENSE and NOTICE
- # files with source copies of this package and derivative works is **REQUIRED** as specified by the Apache License.
- # See https://github.com/kislyuk/argcomplete for more info.
- # This file contains argparse introspection utilities used in the course of argcomplete execution.
- from argparse import (
- ONE_OR_MORE,
- OPTIONAL,
- PARSER,
- REMAINDER,
- SUPPRESS,
- ZERO_OR_MORE,
- Action,
- ArgumentError,
- ArgumentParser,
- _get_action_name,
- _SubParsersAction,
- )
- from gettext import gettext
- from typing import Dict, List, Set, Tuple
- _num_consumed_args: Dict[Action, 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, intermixed=False, **kwargs):
- _num_consumed_args.clear() # Added by argcomplete
- self._argcomplete_namespace = namespace
- self.active_actions: List[Action] = [] # 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: Dict[Action, List[Action]] = {}
- 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[Action] = set()
- seen_non_default_actions: Set[Action] = 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 = gettext('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]
- if isinstance(option_tuple, list): # Python 3.12.7+
- option_tuple = option_tuple[0]
- if len(option_tuple) == 3:
- action, option_string, explicit_arg = option_tuple
- else: # Python 3.11.9+, 3.12.3+, 3.13+
- 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: List[Tuple[Action, List[str], str]] = []
- 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 = gettext('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 = gettext('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(gettext('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(gettext('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 = [
- str(_get_action_name(action)) for action in group._group_actions if action.help is not SUPPRESS
- ]
- msg = gettext('one of the arguments %s is required')
- self.error(msg % ' '.join(names))
- # return the updated namespace and the extra arguments
- return namespace, extras
|