Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/argcomplete/packages/_argparse.py: 15%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

205 statements  

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 

6# This file contains argparse introspection utilities used in the course of argcomplete execution. 

7 

8from __future__ import annotations 

9 

10from argparse import ( 

11 ONE_OR_MORE, 

12 OPTIONAL, 

13 PARSER, 

14 REMAINDER, 

15 SUPPRESS, 

16 ZERO_OR_MORE, 

17 Action, 

18 ArgumentError, 

19 ArgumentParser, 

20 _get_action_name, 

21 _SubParsersAction, 

22) 

23from copy import copy 

24from gettext import gettext 

25from typing import cast 

26 

27_OptionTuple = tuple[Action | None, str, str | None] | tuple[Action | None, str, str | None, str | None] 

28_OptionTupleEntry = _OptionTuple | list[_OptionTuple] 

29 

30_num_consumed_args: dict[Action, int] = {} 

31 

32 

33def get_num_consumed_args(action: Action) -> int: 

34 """Return the number of completed values assigned to an action during completion. 

35 

36 Call this from a completer with its ``action`` argument. The word being 

37 completed is excluded. For repeated options, the count refers to the current 

38 occurrence, not the total number of values from earlier occurrences. 

39 """ 

40 return _num_consumed_args.get(action, 0) 

41 

42 

43def action_is_satisfied(action): 

44 '''Returns False if the parse would raise an error if no more arguments are given to this action, True otherwise.''' 

45 num_consumed_args = _num_consumed_args.get(action, 0) 

46 

47 if action.nargs in [OPTIONAL, ZERO_OR_MORE, REMAINDER]: 

48 return True 

49 if action.nargs == ONE_OR_MORE: 

50 return num_consumed_args >= 1 

51 if action.nargs == PARSER: 

52 # Not sure what this should be, but this previously always returned False 

53 # so at least this won't break anything that wasn't already broken. 

54 return False 

55 if action.nargs is None: 

56 return num_consumed_args == 1 

57 

58 assert isinstance(action.nargs, int), f'failed to handle a possible nargs value: {action.nargs!r}' 

59 return num_consumed_args == action.nargs 

60 

61 

62def action_is_open(action): 

63 '''Returns True if action could consume more arguments (i.e., its pattern is open).''' 

64 num_consumed_args = _num_consumed_args.get(action, 0) 

65 

66 if action.nargs in [ZERO_OR_MORE, ONE_OR_MORE, PARSER, REMAINDER]: 

67 return True 

68 if action.nargs == OPTIONAL or action.nargs is None: 

69 return num_consumed_args == 0 

70 

71 assert isinstance(action.nargs, int), f'failed to handle a possible nargs value: {action.nargs!r}' 

72 return num_consumed_args < action.nargs 

73 

74 

75def action_is_greedy(action, isoptional=False): 

76 '''Returns True if action will necessarily consume the next argument. 

77 isoptional indicates whether the argument is an optional (starts with -). 

78 ''' 

79 num_consumed_args = _num_consumed_args.get(action, 0) 

80 

81 if action.option_strings: 

82 if not isoptional and not action_is_satisfied(action): 

83 return True 

84 return action.nargs == REMAINDER 

85 else: 

86 return action.nargs == REMAINDER and num_consumed_args >= 1 

87 

88 

89class IntrospectiveArgumentParser(ArgumentParser): 

90 '''The following is a verbatim copy of ArgumentParser._parse_known_args (Python 2.7.3), 

91 except for the lines that contain the string "Added by argcomplete". 

92 ''' 

93 

94 def _get_partial_num_consumed_args(self, action: Action, arg_strings_pattern: str) -> int: 

95 # Let argparse match the unfinished action without requiring all its 

96 # values. Copy it so its declared nargs and normal parsing stay intact. 

97 partial_action = copy(action) 

98 partial_action.nargs = ZERO_OR_MORE 

99 arg_count = self._match_argument(partial_action, arg_strings_pattern) 

100 return arg_strings_pattern[:arg_count].count("A") 

101 

102 def _parse_known_args(self, arg_strings, namespace, intermixed=False, **kwargs): 

103 _num_consumed_args.clear() # Added by argcomplete 

104 self._argcomplete_namespace = namespace 

105 self.active_actions: list[Action] = [] # Added by argcomplete 

106 # replace arg strings that are file references 

107 if self.fromfile_prefix_chars is not None: 

108 arg_strings = self._read_args_from_files(arg_strings) 

109 

110 # map all mutually exclusive arguments to the other arguments 

111 # they can't occur with 

112 action_conflicts: dict[Action, list[Action]] = {} 

113 self._action_conflicts = action_conflicts # Added by argcomplete 

114 for mutex_group in self._mutually_exclusive_groups: 

115 group_actions = mutex_group._group_actions 

116 for i, mutex_action in enumerate(mutex_group._group_actions): 

117 conflicts = action_conflicts.setdefault(mutex_action, []) 

118 conflicts.extend(group_actions[:i]) 

119 conflicts.extend(group_actions[i + 1 :]) 

120 

121 # find all option indices, and determine the arg_string_pattern 

122 # which has an 'O' if there is an option at an index, 

123 # an 'A' if there is an argument, or a '-' if there is a '--' 

124 option_string_indices: dict[int, _OptionTupleEntry] = {} 

125 arg_string_pattern_parts = [] 

126 arg_strings_iter = iter(arg_strings) 

127 for i, arg_string in enumerate(arg_strings_iter): 

128 # all args after -- are non-options 

129 if arg_string == '--': 

130 arg_string_pattern_parts.append('-') 

131 for arg_string in arg_strings_iter: 

132 arg_string_pattern_parts.append('A') 

133 

134 # otherwise, add the arg to the arg strings 

135 # and note the index if it was an option 

136 else: 

137 option_tuple = self._parse_optional(arg_string) 

138 if option_tuple is None: 

139 pattern = 'A' 

140 else: 

141 option_string_indices[i] = cast(_OptionTupleEntry, option_tuple) 

142 pattern = 'O' 

143 arg_string_pattern_parts.append(pattern) 

144 

145 # join the pieces together to form the pattern 

146 arg_strings_pattern = ''.join(arg_string_pattern_parts) 

147 

148 # converts arg strings to the appropriate and then takes the action 

149 seen_actions: set[Action] = set() 

150 seen_non_default_actions: set[Action] = set() 

151 self._seen_non_default_actions = seen_non_default_actions # Added by argcomplete 

152 

153 def take_action(action, argument_strings, option_string=None): 

154 seen_actions.add(action) 

155 argument_values = self._get_values(action, argument_strings) 

156 

157 # error if this argument is not allowed with other previously 

158 # seen arguments, assuming that actions that use the default 

159 # value don't really count as "present" 

160 if argument_values is not action.default: 

161 seen_non_default_actions.add(action) 

162 for conflict_action in action_conflicts.get(action, []): 

163 if conflict_action in seen_non_default_actions: 

164 msg = gettext('not allowed with argument %s') 

165 action_name = _get_action_name(conflict_action) 

166 raise ArgumentError(action, msg % action_name) 

167 

168 # take the action if we didn't receive a SUPPRESS value 

169 # (e.g. from a default) 

170 if argument_values is not SUPPRESS or isinstance(action, _SubParsersAction): 

171 try: 

172 action(self, namespace, argument_values, option_string) 

173 except BaseException: 

174 # Begin added by argcomplete 

175 # When a subparser action is taken and fails due to incomplete arguments, it does not merge the 

176 # contents of its parsed namespace into the parent namespace. Do that here to allow completers to 

177 # access the partially parsed arguments for the subparser. 

178 if isinstance(action, _SubParsersAction): 

179 subnamespace = action._name_parser_map[argument_values[0]]._argcomplete_namespace 

180 for key, value in vars(subnamespace).items(): 

181 setattr(namespace, key, value) 

182 # End added by argcomplete 

183 raise 

184 

185 # function to convert arg_strings into an optional action 

186 def consume_optional(start_index): 

187 # get the optional identified at this index 

188 raw_option_tuple = option_string_indices[start_index] 

189 if isinstance(raw_option_tuple, list): # Python 3.12.7+ 

190 option_tuple = raw_option_tuple[0] 

191 else: 

192 option_tuple = raw_option_tuple 

193 if len(option_tuple) == 3: 

194 action, option_string, explicit_arg = option_tuple 

195 else: # Python 3.11.9+, 3.12.3+, 3.13+ 

196 action, option_string, _, explicit_arg = option_tuple 

197 

198 # identify additional optionals in the same arg string 

199 # (e.g. -xyz is the same as -x -y -z if no args are required) 

200 match_argument = self._match_argument 

201 action_tuples: list[tuple[Action, list[str], str]] = [] 

202 while True: 

203 # if we found no optional action, skip it 

204 if action is None: 

205 extras.append(arg_strings[start_index]) 

206 return start_index + 1 

207 

208 # if there is an explicit argument, try to match the 

209 # optional's string arguments to only this 

210 if explicit_arg is not None: 

211 arg_count = match_argument(action, 'A') 

212 

213 # if the action is a single-dash option and takes no 

214 # arguments, try to parse more single-dash options out 

215 # of the tail of the option string 

216 chars = self.prefix_chars 

217 if arg_count == 0 and option_string[1] not in chars: 

218 action_tuples.append((action, [], option_string)) 

219 char = option_string[0] 

220 option_string = char + explicit_arg[0] 

221 new_explicit_arg = explicit_arg[1:] or None 

222 optionals_map = self._option_string_actions 

223 if option_string in optionals_map: 

224 action = optionals_map[option_string] 

225 explicit_arg = new_explicit_arg 

226 else: 

227 msg = gettext('ignored explicit argument %r') 

228 raise ArgumentError(action, msg % explicit_arg) 

229 

230 # if the action expect exactly one argument, we've 

231 # successfully matched the option; exit the loop 

232 elif arg_count == 1: 

233 stop = start_index + 1 

234 args = [explicit_arg] 

235 action_tuples.append((action, args, option_string)) 

236 break 

237 

238 # error if a double-dash option did not use the 

239 # explicit argument 

240 else: 

241 msg = gettext('ignored explicit argument %r') 

242 raise ArgumentError(action, msg % explicit_arg) 

243 

244 # if there is no explicit argument, try to match the 

245 # optional's string arguments with the following strings 

246 # if successful, exit the loop 

247 else: 

248 start = start_index + 1 

249 selected_patterns = arg_strings_pattern[start:] 

250 self.active_actions = [action] # Added by argcomplete 

251 _num_consumed_args[action] = 0 # Added by argcomplete 

252 try: 

253 arg_count = match_argument(action, selected_patterns) 

254 except ArgumentError: 

255 # Fixed nargs can be incomplete at the cursor. Preserve 

256 # the already entered values even though parsing stops. 

257 _num_consumed_args[action] = self._get_partial_num_consumed_args(action, selected_patterns) 

258 raise 

259 stop = start + arg_count 

260 args = arg_strings[start:stop] 

261 

262 # Begin added by argcomplete 

263 # If the pattern is not open (e.g. no + at the end), remove the action from active actions (since 

264 # it wouldn't be able to consume any more args) 

265 _num_consumed_args[action] = len(args) 

266 if not action_is_open(action): 

267 self.active_actions.remove(action) 

268 # End added by argcomplete 

269 

270 action_tuples.append((action, args, option_string)) 

271 break 

272 

273 # add the Optional to the list and return the index at which 

274 # the Optional's string args stopped 

275 assert action_tuples 

276 for optional_action, args, option_string in action_tuples: 

277 take_action(optional_action, args, option_string) 

278 return stop 

279 

280 # the list of Positionals left to be parsed; this is modified 

281 # by consume_positionals() 

282 positionals = self._get_positional_actions() 

283 

284 # function to convert arg_strings into positional actions 

285 def consume_positionals(start_index): 

286 # match as many Positionals as possible 

287 match_partial = self._match_arguments_partial 

288 selected_pattern = arg_strings_pattern[start_index:] 

289 arg_counts = match_partial(positionals, selected_pattern) 

290 

291 # slice off the appropriate arg strings for each Positional 

292 # and add the Positional and its args to the list 

293 for action, arg_count in zip(positionals, arg_counts): # Added by argcomplete 

294 self.active_actions.append(action) # Added by argcomplete 

295 for action, arg_count in zip(positionals, arg_counts): 

296 args = arg_strings[start_index : start_index + arg_count] 

297 # Begin added by argcomplete 

298 # The matcher includes the '--' separator in its token count. 

299 # Ordinary positionals only receive values; PARSER/REMAINDER 

300 # can retain '--' as part of their arguments. 

301 if action.nargs in (PARSER, REMAINDER): 

302 _num_consumed_args[action] = arg_count 

303 else: 

304 _num_consumed_args[action] = arg_strings_pattern[start_index : start_index + arg_count].count("A") 

305 # End added by argcomplete 

306 start_index += arg_count 

307 take_action(action, args) 

308 

309 # slice off the Positionals that we just parsed and return the 

310 # index at which the Positionals' string args stopped 

311 positionals[:] = positionals[len(arg_counts) :] 

312 return start_index 

313 

314 # consume Positionals and Optionals alternately, until we have 

315 # passed the last option string 

316 extras = [] 

317 start_index = 0 

318 if option_string_indices: 

319 max_option_string_index = max(option_string_indices) 

320 else: 

321 max_option_string_index = -1 

322 while start_index <= max_option_string_index: 

323 # consume any Positionals preceding the next option 

324 next_option_string_index = min([index for index in option_string_indices if index >= start_index]) 

325 if start_index != next_option_string_index: 

326 positionals_end_index = consume_positionals(start_index) 

327 

328 # only try to parse the next optional if we didn't consume 

329 # the option string during the positionals parsing 

330 if positionals_end_index > start_index: 

331 start_index = positionals_end_index 

332 continue 

333 else: 

334 start_index = positionals_end_index 

335 

336 # if we consumed all the positionals we could and we're not 

337 # at the index of an option string, there were extra arguments 

338 if start_index not in option_string_indices: 

339 strings = arg_strings[start_index:next_option_string_index] 

340 extras.extend(strings) 

341 start_index = next_option_string_index 

342 

343 # consume the next optional and any arguments for it 

344 start_index = consume_optional(start_index) 

345 

346 # consume any positionals following the last Optional 

347 stop_index = consume_positionals(start_index) 

348 

349 # if we didn't consume all the argument strings, there were extras 

350 extras.extend(arg_strings[stop_index:]) 

351 

352 # if we didn't use all the Positional objects, there were too few 

353 # arg strings supplied. 

354 

355 if positionals: 

356 self.active_actions.append(positionals[0]) # Added by argcomplete 

357 # Begin added by argcomplete 

358 action = positionals[0] 

359 if isinstance(action.nargs, int): 

360 _num_consumed_args[action] = self._get_partial_num_consumed_args( 

361 action, arg_strings_pattern[stop_index:] 

362 ) 

363 # End added by argcomplete 

364 self.error(gettext('too few arguments')) 

365 

366 # make sure all required actions were present 

367 for action in self._actions: 

368 if action.required and action not in seen_actions: 

369 name = _get_action_name(action) 

370 self.error(gettext('argument %s is required') % name) 

371 

372 # make sure all required groups had one option present 

373 for group in self._mutually_exclusive_groups: 

374 if group.required: 

375 for action in group._group_actions: 

376 if action in seen_non_default_actions: 

377 break 

378 

379 # if no actions were used, report the error 

380 else: 

381 names = [ 

382 str(_get_action_name(action)) for action in group._group_actions if action.help is not SUPPRESS 

383 ] 

384 msg = gettext('one of the arguments %s is required') 

385 self.error(msg % ' '.join(names)) 

386 

387 # return the updated namespace and the extra arguments 

388 return namespace, extras