_argparse.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. # Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors. Licensed under the terms of the
  2. # `Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. Distribution of the LICENSE and NOTICE
  3. # files with source copies of this package and derivative works is **REQUIRED** as specified by the Apache License.
  4. # See https://github.com/kislyuk/argcomplete for more info.
  5. # This file contains argparse introspection utilities used in the course of argcomplete execution.
  6. from argparse import (
  7. ONE_OR_MORE,
  8. OPTIONAL,
  9. PARSER,
  10. REMAINDER,
  11. SUPPRESS,
  12. ZERO_OR_MORE,
  13. Action,
  14. ArgumentError,
  15. ArgumentParser,
  16. _get_action_name,
  17. _SubParsersAction,
  18. )
  19. from gettext import gettext
  20. from typing import Dict, List, Set, Tuple
  21. _num_consumed_args: Dict[Action, int] = {}
  22. def action_is_satisfied(action):
  23. '''Returns False if the parse would raise an error if no more arguments are given to this action, True otherwise.'''
  24. num_consumed_args = _num_consumed_args.get(action, 0)
  25. if action.nargs in [OPTIONAL, ZERO_OR_MORE, REMAINDER]:
  26. return True
  27. if action.nargs == ONE_OR_MORE:
  28. return num_consumed_args >= 1
  29. if action.nargs == PARSER:
  30. # Not sure what this should be, but this previously always returned False
  31. # so at least this won't break anything that wasn't already broken.
  32. return False
  33. if action.nargs is None:
  34. return num_consumed_args == 1
  35. assert isinstance(action.nargs, int), 'failed to handle a possible nargs value: %r' % action.nargs
  36. return num_consumed_args == action.nargs
  37. def action_is_open(action):
  38. '''Returns True if action could consume more arguments (i.e., its pattern is open).'''
  39. num_consumed_args = _num_consumed_args.get(action, 0)
  40. if action.nargs in [ZERO_OR_MORE, ONE_OR_MORE, PARSER, REMAINDER]:
  41. return True
  42. if action.nargs == OPTIONAL or action.nargs is None:
  43. return num_consumed_args == 0
  44. assert isinstance(action.nargs, int), 'failed to handle a possible nargs value: %r' % action.nargs
  45. return num_consumed_args < action.nargs
  46. def action_is_greedy(action, isoptional=False):
  47. '''Returns True if action will necessarily consume the next argument.
  48. isoptional indicates whether the argument is an optional (starts with -).
  49. '''
  50. num_consumed_args = _num_consumed_args.get(action, 0)
  51. if action.option_strings:
  52. if not isoptional and not action_is_satisfied(action):
  53. return True
  54. return action.nargs == REMAINDER
  55. else:
  56. return action.nargs == REMAINDER and num_consumed_args >= 1
  57. class IntrospectiveArgumentParser(ArgumentParser):
  58. '''The following is a verbatim copy of ArgumentParser._parse_known_args (Python 2.7.3),
  59. except for the lines that contain the string "Added by argcomplete".
  60. '''
  61. def _parse_known_args(self, arg_strings, namespace, intermixed=False, **kwargs):
  62. _num_consumed_args.clear() # Added by argcomplete
  63. self._argcomplete_namespace = namespace
  64. self.active_actions: List[Action] = [] # Added by argcomplete
  65. # replace arg strings that are file references
  66. if self.fromfile_prefix_chars is not None:
  67. arg_strings = self._read_args_from_files(arg_strings)
  68. # map all mutually exclusive arguments to the other arguments
  69. # they can't occur with
  70. action_conflicts: Dict[Action, List[Action]] = {}
  71. self._action_conflicts = action_conflicts # Added by argcomplete
  72. for mutex_group in self._mutually_exclusive_groups:
  73. group_actions = mutex_group._group_actions
  74. for i, mutex_action in enumerate(mutex_group._group_actions):
  75. conflicts = action_conflicts.setdefault(mutex_action, [])
  76. conflicts.extend(group_actions[:i])
  77. conflicts.extend(group_actions[i + 1 :])
  78. # find all option indices, and determine the arg_string_pattern
  79. # which has an 'O' if there is an option at an index,
  80. # an 'A' if there is an argument, or a '-' if there is a '--'
  81. option_string_indices = {}
  82. arg_string_pattern_parts = []
  83. arg_strings_iter = iter(arg_strings)
  84. for i, arg_string in enumerate(arg_strings_iter):
  85. # all args after -- are non-options
  86. if arg_string == '--':
  87. arg_string_pattern_parts.append('-')
  88. for arg_string in arg_strings_iter:
  89. arg_string_pattern_parts.append('A')
  90. # otherwise, add the arg to the arg strings
  91. # and note the index if it was an option
  92. else:
  93. option_tuple = self._parse_optional(arg_string)
  94. if option_tuple is None:
  95. pattern = 'A'
  96. else:
  97. option_string_indices[i] = option_tuple
  98. pattern = 'O'
  99. arg_string_pattern_parts.append(pattern)
  100. # join the pieces together to form the pattern
  101. arg_strings_pattern = ''.join(arg_string_pattern_parts)
  102. # converts arg strings to the appropriate and then takes the action
  103. seen_actions: Set[Action] = set()
  104. seen_non_default_actions: Set[Action] = set()
  105. self._seen_non_default_actions = seen_non_default_actions # Added by argcomplete
  106. def take_action(action, argument_strings, option_string=None):
  107. seen_actions.add(action)
  108. argument_values = self._get_values(action, argument_strings)
  109. # error if this argument is not allowed with other previously
  110. # seen arguments, assuming that actions that use the default
  111. # value don't really count as "present"
  112. if argument_values is not action.default:
  113. seen_non_default_actions.add(action)
  114. for conflict_action in action_conflicts.get(action, []):
  115. if conflict_action in seen_non_default_actions:
  116. msg = gettext('not allowed with argument %s')
  117. action_name = _get_action_name(conflict_action)
  118. raise ArgumentError(action, msg % action_name)
  119. # take the action if we didn't receive a SUPPRESS value
  120. # (e.g. from a default)
  121. if argument_values is not SUPPRESS or isinstance(action, _SubParsersAction):
  122. try:
  123. action(self, namespace, argument_values, option_string)
  124. except BaseException:
  125. # Begin added by argcomplete
  126. # When a subparser action is taken and fails due to incomplete arguments, it does not merge the
  127. # contents of its parsed namespace into the parent namespace. Do that here to allow completers to
  128. # access the partially parsed arguments for the subparser.
  129. if isinstance(action, _SubParsersAction):
  130. subnamespace = action._name_parser_map[argument_values[0]]._argcomplete_namespace
  131. for key, value in vars(subnamespace).items():
  132. setattr(namespace, key, value)
  133. # End added by argcomplete
  134. raise
  135. # function to convert arg_strings into an optional action
  136. def consume_optional(start_index):
  137. # get the optional identified at this index
  138. option_tuple = option_string_indices[start_index]
  139. if isinstance(option_tuple, list): # Python 3.12.7+
  140. option_tuple = option_tuple[0]
  141. if len(option_tuple) == 3:
  142. action, option_string, explicit_arg = option_tuple
  143. else: # Python 3.11.9+, 3.12.3+, 3.13+
  144. action, option_string, _, explicit_arg = option_tuple
  145. # identify additional optionals in the same arg string
  146. # (e.g. -xyz is the same as -x -y -z if no args are required)
  147. match_argument = self._match_argument
  148. action_tuples: List[Tuple[Action, List[str], str]] = []
  149. while True:
  150. # if we found no optional action, skip it
  151. if action is None:
  152. extras.append(arg_strings[start_index])
  153. return start_index + 1
  154. # if there is an explicit argument, try to match the
  155. # optional's string arguments to only this
  156. if explicit_arg is not None:
  157. arg_count = match_argument(action, 'A')
  158. # if the action is a single-dash option and takes no
  159. # arguments, try to parse more single-dash options out
  160. # of the tail of the option string
  161. chars = self.prefix_chars
  162. if arg_count == 0 and option_string[1] not in chars:
  163. action_tuples.append((action, [], option_string))
  164. char = option_string[0]
  165. option_string = char + explicit_arg[0]
  166. new_explicit_arg = explicit_arg[1:] or None
  167. optionals_map = self._option_string_actions
  168. if option_string in optionals_map:
  169. action = optionals_map[option_string]
  170. explicit_arg = new_explicit_arg
  171. else:
  172. msg = gettext('ignored explicit argument %r')
  173. raise ArgumentError(action, msg % explicit_arg)
  174. # if the action expect exactly one argument, we've
  175. # successfully matched the option; exit the loop
  176. elif arg_count == 1:
  177. stop = start_index + 1
  178. args = [explicit_arg]
  179. action_tuples.append((action, args, option_string))
  180. break
  181. # error if a double-dash option did not use the
  182. # explicit argument
  183. else:
  184. msg = gettext('ignored explicit argument %r')
  185. raise ArgumentError(action, msg % explicit_arg)
  186. # if there is no explicit argument, try to match the
  187. # optional's string arguments with the following strings
  188. # if successful, exit the loop
  189. else:
  190. start = start_index + 1
  191. selected_patterns = arg_strings_pattern[start:]
  192. self.active_actions = [action] # Added by argcomplete
  193. _num_consumed_args[action] = 0 # Added by argcomplete
  194. arg_count = match_argument(action, selected_patterns)
  195. stop = start + arg_count
  196. args = arg_strings[start:stop]
  197. # Begin added by argcomplete
  198. # If the pattern is not open (e.g. no + at the end), remove the action from active actions (since
  199. # it wouldn't be able to consume any more args)
  200. _num_consumed_args[action] = len(args)
  201. if not action_is_open(action):
  202. self.active_actions.remove(action)
  203. # End added by argcomplete
  204. action_tuples.append((action, args, option_string))
  205. break
  206. # add the Optional to the list and return the index at which
  207. # the Optional's string args stopped
  208. assert action_tuples
  209. for action, args, option_string in action_tuples:
  210. take_action(action, args, option_string)
  211. return stop
  212. # the list of Positionals left to be parsed; this is modified
  213. # by consume_positionals()
  214. positionals = self._get_positional_actions()
  215. # function to convert arg_strings into positional actions
  216. def consume_positionals(start_index):
  217. # match as many Positionals as possible
  218. match_partial = self._match_arguments_partial
  219. selected_pattern = arg_strings_pattern[start_index:]
  220. arg_counts = match_partial(positionals, selected_pattern)
  221. # slice off the appropriate arg strings for each Positional
  222. # and add the Positional and its args to the list
  223. for action, arg_count in zip(positionals, arg_counts): # Added by argcomplete
  224. self.active_actions.append(action) # Added by argcomplete
  225. for action, arg_count in zip(positionals, arg_counts):
  226. args = arg_strings[start_index : start_index + arg_count]
  227. start_index += arg_count
  228. _num_consumed_args[action] = len(args) # Added by argcomplete
  229. take_action(action, args)
  230. # slice off the Positionals that we just parsed and return the
  231. # index at which the Positionals' string args stopped
  232. positionals[:] = positionals[len(arg_counts) :]
  233. return start_index
  234. # consume Positionals and Optionals alternately, until we have
  235. # passed the last option string
  236. extras = []
  237. start_index = 0
  238. if option_string_indices:
  239. max_option_string_index = max(option_string_indices)
  240. else:
  241. max_option_string_index = -1
  242. while start_index <= max_option_string_index:
  243. # consume any Positionals preceding the next option
  244. next_option_string_index = min([index for index in option_string_indices if index >= start_index])
  245. if start_index != next_option_string_index:
  246. positionals_end_index = consume_positionals(start_index)
  247. # only try to parse the next optional if we didn't consume
  248. # the option string during the positionals parsing
  249. if positionals_end_index > start_index:
  250. start_index = positionals_end_index
  251. continue
  252. else:
  253. start_index = positionals_end_index
  254. # if we consumed all the positionals we could and we're not
  255. # at the index of an option string, there were extra arguments
  256. if start_index not in option_string_indices:
  257. strings = arg_strings[start_index:next_option_string_index]
  258. extras.extend(strings)
  259. start_index = next_option_string_index
  260. # consume the next optional and any arguments for it
  261. start_index = consume_optional(start_index)
  262. # consume any positionals following the last Optional
  263. stop_index = consume_positionals(start_index)
  264. # if we didn't consume all the argument strings, there were extras
  265. extras.extend(arg_strings[stop_index:])
  266. # if we didn't use all the Positional objects, there were too few
  267. # arg strings supplied.
  268. if positionals:
  269. self.active_actions.append(positionals[0]) # Added by argcomplete
  270. self.error(gettext('too few arguments'))
  271. # make sure all required actions were present
  272. for action in self._actions:
  273. if action.required:
  274. if action not in seen_actions:
  275. name = _get_action_name(action)
  276. self.error(gettext('argument %s is required') % name)
  277. # make sure all required groups had one option present
  278. for group in self._mutually_exclusive_groups:
  279. if group.required:
  280. for action in group._group_actions:
  281. if action in seen_non_default_actions:
  282. break
  283. # if no actions were used, report the error
  284. else:
  285. names = [
  286. str(_get_action_name(action)) for action in group._group_actions if action.help is not SUPPRESS
  287. ]
  288. msg = gettext('one of the arguments %s is required')
  289. self.error(msg % ' '.join(names))
  290. # return the updated namespace and the extra arguments
  291. return namespace, extras