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

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

188 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 gettext import gettext 

24from typing import cast 

25 

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

27_OptionTupleEntry = _OptionTuple | list[_OptionTuple] 

28 

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

30 

31 

32def action_is_satisfied(action): 

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

34 num_consumed_args = _num_consumed_args.get(action, 0) 

35 

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

37 return True 

38 if action.nargs == ONE_OR_MORE: 

39 return num_consumed_args >= 1 

40 if action.nargs == PARSER: 

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

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

43 return False 

44 if action.nargs is None: 

45 return num_consumed_args == 1 

46 

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

48 return num_consumed_args == action.nargs 

49 

50 

51def action_is_open(action): 

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

53 num_consumed_args = _num_consumed_args.get(action, 0) 

54 

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

56 return True 

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

58 return num_consumed_args == 0 

59 

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

61 return num_consumed_args < action.nargs 

62 

63 

64def action_is_greedy(action, isoptional=False): 

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

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

67 ''' 

68 num_consumed_args = _num_consumed_args.get(action, 0) 

69 

70 if action.option_strings: 

71 if not isoptional and not action_is_satisfied(action): 

72 return True 

73 return action.nargs == REMAINDER 

74 else: 

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

76 

77 

78class IntrospectiveArgumentParser(ArgumentParser): 

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

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

81 ''' 

82 

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

84 _num_consumed_args.clear() # Added by argcomplete 

85 self._argcomplete_namespace = namespace 

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

87 # replace arg strings that are file references 

88 if self.fromfile_prefix_chars is not None: 

89 arg_strings = self._read_args_from_files(arg_strings) 

90 

91 # map all mutually exclusive arguments to the other arguments 

92 # they can't occur with 

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

94 self._action_conflicts = action_conflicts # Added by argcomplete 

95 for mutex_group in self._mutually_exclusive_groups: 

96 group_actions = mutex_group._group_actions 

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

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

99 conflicts.extend(group_actions[:i]) 

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

101 

102 # find all option indices, and determine the arg_string_pattern 

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

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

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

106 arg_string_pattern_parts = [] 

107 arg_strings_iter = iter(arg_strings) 

108 for i, arg_string in enumerate(arg_strings_iter): 

109 # all args after -- are non-options 

110 if arg_string == '--': 

111 arg_string_pattern_parts.append('-') 

112 for arg_string in arg_strings_iter: 

113 arg_string_pattern_parts.append('A') 

114 

115 # otherwise, add the arg to the arg strings 

116 # and note the index if it was an option 

117 else: 

118 option_tuple = self._parse_optional(arg_string) 

119 if option_tuple is None: 

120 pattern = 'A' 

121 else: 

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

123 pattern = 'O' 

124 arg_string_pattern_parts.append(pattern) 

125 

126 # join the pieces together to form the pattern 

127 arg_strings_pattern = ''.join(arg_string_pattern_parts) 

128 

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

130 seen_actions: set[Action] = set() 

131 seen_non_default_actions: set[Action] = set() 

132 self._seen_non_default_actions = seen_non_default_actions # Added by argcomplete 

133 

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

135 seen_actions.add(action) 

136 argument_values = self._get_values(action, argument_strings) 

137 

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

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

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

141 if argument_values is not action.default: 

142 seen_non_default_actions.add(action) 

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

144 if conflict_action in seen_non_default_actions: 

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

146 action_name = _get_action_name(conflict_action) 

147 raise ArgumentError(action, msg % action_name) 

148 

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

150 # (e.g. from a default) 

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

152 try: 

153 action(self, namespace, argument_values, option_string) 

154 except BaseException: 

155 # Begin added by argcomplete 

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

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

158 # access the partially parsed arguments for the subparser. 

159 if isinstance(action, _SubParsersAction): 

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

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

162 setattr(namespace, key, value) 

163 # End added by argcomplete 

164 raise 

165 

166 # function to convert arg_strings into an optional action 

167 def consume_optional(start_index): 

168 # get the optional identified at this index 

169 raw_option_tuple = option_string_indices[start_index] 

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

171 option_tuple = raw_option_tuple[0] 

172 else: 

173 option_tuple = raw_option_tuple 

174 if len(option_tuple) == 3: 

175 action, option_string, explicit_arg = option_tuple 

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

177 action, option_string, _, explicit_arg = option_tuple 

178 

179 # identify additional optionals in the same arg string 

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

181 match_argument = self._match_argument 

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

183 while True: 

184 # if we found no optional action, skip it 

185 if action is None: 

186 extras.append(arg_strings[start_index]) 

187 return start_index + 1 

188 

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

190 # optional's string arguments to only this 

191 if explicit_arg is not None: 

192 arg_count = match_argument(action, 'A') 

193 

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

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

196 # of the tail of the option string 

197 chars = self.prefix_chars 

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

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

200 char = option_string[0] 

201 option_string = char + explicit_arg[0] 

202 new_explicit_arg = explicit_arg[1:] or None 

203 optionals_map = self._option_string_actions 

204 if option_string in optionals_map: 

205 action = optionals_map[option_string] 

206 explicit_arg = new_explicit_arg 

207 else: 

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

209 raise ArgumentError(action, msg % explicit_arg) 

210 

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

212 # successfully matched the option; exit the loop 

213 elif arg_count == 1: 

214 stop = start_index + 1 

215 args = [explicit_arg] 

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

217 break 

218 

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

220 # explicit argument 

221 else: 

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

223 raise ArgumentError(action, msg % explicit_arg) 

224 

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

226 # optional's string arguments with the following strings 

227 # if successful, exit the loop 

228 else: 

229 start = start_index + 1 

230 selected_patterns = arg_strings_pattern[start:] 

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

232 _num_consumed_args[action] = 0 # Added by argcomplete 

233 arg_count = match_argument(action, selected_patterns) 

234 stop = start + arg_count 

235 args = arg_strings[start:stop] 

236 

237 # Begin added by argcomplete 

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

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

240 _num_consumed_args[action] = len(args) 

241 if not action_is_open(action): 

242 self.active_actions.remove(action) 

243 # End added by argcomplete 

244 

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

246 break 

247 

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

249 # the Optional's string args stopped 

250 assert action_tuples 

251 for optional_action, args, option_string in action_tuples: 

252 take_action(optional_action, args, option_string) 

253 return stop 

254 

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

256 # by consume_positionals() 

257 positionals = self._get_positional_actions() 

258 

259 # function to convert arg_strings into positional actions 

260 def consume_positionals(start_index): 

261 # match as many Positionals as possible 

262 match_partial = self._match_arguments_partial 

263 selected_pattern = arg_strings_pattern[start_index:] 

264 arg_counts = match_partial(positionals, selected_pattern) 

265 

266 # slice off the appropriate arg strings for each Positional 

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

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

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

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

271 args = arg_strings[start_index : start_index + arg_count] 

272 start_index += arg_count 

273 _num_consumed_args[action] = len(args) # Added by argcomplete 

274 take_action(action, args) 

275 

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

277 # index at which the Positionals' string args stopped 

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

279 return start_index 

280 

281 # consume Positionals and Optionals alternately, until we have 

282 # passed the last option string 

283 extras = [] 

284 start_index = 0 

285 if option_string_indices: 

286 max_option_string_index = max(option_string_indices) 

287 else: 

288 max_option_string_index = -1 

289 while start_index <= max_option_string_index: 

290 # consume any Positionals preceding the next option 

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

292 if start_index != next_option_string_index: 

293 positionals_end_index = consume_positionals(start_index) 

294 

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

296 # the option string during the positionals parsing 

297 if positionals_end_index > start_index: 

298 start_index = positionals_end_index 

299 continue 

300 else: 

301 start_index = positionals_end_index 

302 

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

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

305 if start_index not in option_string_indices: 

306 strings = arg_strings[start_index:next_option_string_index] 

307 extras.extend(strings) 

308 start_index = next_option_string_index 

309 

310 # consume the next optional and any arguments for it 

311 start_index = consume_optional(start_index) 

312 

313 # consume any positionals following the last Optional 

314 stop_index = consume_positionals(start_index) 

315 

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

317 extras.extend(arg_strings[stop_index:]) 

318 

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

320 # arg strings supplied. 

321 

322 if positionals: 

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

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

325 

326 # make sure all required actions were present 

327 for action in self._actions: 

328 if action.required and action not in seen_actions: 

329 name = _get_action_name(action) 

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

331 

332 # make sure all required groups had one option present 

333 for group in self._mutually_exclusive_groups: 

334 if group.required: 

335 for action in group._group_actions: 

336 if action in seen_non_default_actions: 

337 break 

338 

339 # if no actions were used, report the error 

340 else: 

341 names = [ 

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

343 ] 

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

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

346 

347 # return the updated namespace and the extra arguments 

348 return namespace, extras