diff -r b9bb77dd71b3 Lib/argparse.py --- a/Lib/argparse.py Sat Sep 10 11:53:34 2016 -0700 +++ b/Lib/argparse.py Sat Sep 10 22:28:24 2016 -0400 @@ -74,6 +74,7 @@ 'MetavarTypeHelpFormatter', 'Namespace', 'Action', + 'ConfigFileAction', 'ONE_OR_MORE', 'OPTIONAL', 'PARSER', @@ -85,6 +86,7 @@ import collections as _collections import copy as _copy +import configparser import os as _os import re as _re import sys as _sys @@ -825,6 +827,62 @@ def __call__(self, parser, namespace, values, option_string=None): raise NotImplementedError(_('.__call__() not defined')) +class ConfigFileAction(Action): + + def parse_file(self, path): + """Parses config file for arguments + + Parses configuration file using SafeConfigParser. + + Override this function for use with a custom parser. + + The return must be a list of lists with the first value in the + list being the sub list being the dest of the argument and the + second be a list of all the values for that argument. + """ + + config = configparser.SafeConfigParser() + config.read(path) + sections = config.sections() + options = [] + for section in sections: + for dest, value in config.items(section): + # The value must be wrapped in list to be processed correctly + options.append((dest, [value])) + return options + + def parse_files(self, paths): + """Parses a list of configuration files and returns result + + Only override this function if acccess to the all the variables after + parsing is required. Please instead override the parse_file method. + + """ + + options = [] + for path in paths: + options.extend(self.parse_file(path)) + return options + + def __call__(self, parser, namespace, values, options_string): + """Parse configuration file and take action based on dest """ + + # First we parse all the configration files getting back a list of list + # where the first value in the list is the dest of the value and the + # second item is the list of the values themselves. + + configargs = self.parse_files(values) + actions = {action.dest: action for action in parser._actions} + for (key, values) in configargs: + try: + action = actions[key] + except LookupError: + parser._extras.append(key) + parser._extras.extend(values) + continue + # msg = 'Action for config file argument "{}" not found' + # parser.error(msg.format(key)) + parser.take_action(action, namespace, values, key) class _StoreAction(Action): @@ -1775,11 +1833,11 @@ # map all mutually exclusive arguments to the other arguments # they can't occur with - action_conflicts = {} + self.action_conflicts = {} 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 = self.action_conflicts.setdefault(mutex_action, []) conflicts.extend(group_actions[:i]) conflicts.extend(group_actions[i + 1:]) @@ -1812,28 +1870,8 @@ 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() - - 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: - action(self, namespace, argument_values, option_string) + self._seen_actions = set() + self._seen_non_default_actions = set() # function to convert arg_strings into an optional action def consume_optional(start_index): @@ -1850,7 +1888,7 @@ # if we found no optional action, skip it if action is None: - extras.append(arg_strings[start_index]) + self._extras.append(arg_strings[start_index]) return start_index + 1 # if there is an explicit argument, try to match the @@ -1905,7 +1943,7 @@ # the Optional's string args stopped assert action_tuples for action, args, option_string in action_tuples: - take_action(action, args, option_string) + self.take_action(action, namespace, args, option_string) return stop # the list of Positionals left to be parsed; this is modified @@ -1924,7 +1962,7 @@ for action, arg_count in zip(positionals, arg_counts): args = arg_strings[start_index: start_index + arg_count] start_index += arg_count - take_action(action, args) + self.take_action(action, namespace, args) # slice off the Positionals that we just parsed and return the # index at which the Positionals' string args stopped @@ -1933,7 +1971,7 @@ # consume Positionals and Optionals alternately, until we have # passed the last option string - extras = [] + self._extras = [] start_index = 0 if option_string_indices: max_option_string_index = max(option_string_indices) @@ -1961,7 +1999,7 @@ # 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) + self._extras.extend(strings) start_index = next_option_string_index # consume the next optional and any arguments for it @@ -1971,13 +2009,13 @@ stop_index = consume_positionals(start_index) # if we didn't consume all the argument strings, there were extras - extras.extend(arg_strings[stop_index:]) + self._extras.extend(arg_strings[stop_index:]) # make sure all required actions were present and also convert # action defaults which were not given as arguments required_actions = [] for action in self._actions: - if action not in seen_actions: + if action not in self._seen_actions: if action.required: required_actions.append(_get_action_name(action)) else: @@ -2000,7 +2038,7 @@ for group in self._mutually_exclusive_groups: if group.required: for action in group._group_actions: - if action in seen_non_default_actions: + if action in self._seen_non_default_actions: break # if no actions were used, report the error @@ -2012,7 +2050,7 @@ self.error(msg % ' '.join(names)) # return the updated namespace and the extra arguments - return namespace, extras + return namespace, self._extras def _read_args_from_files(self, arg_strings): # expand arguments referencing files @@ -2224,6 +2262,27 @@ # return the pattern return nargs_pattern + def take_action(self, action, namespace, argument_strings, + option_string=None): + self._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: + self._seen_non_default_actions.add(action) + for conflict_action in self.action_conflicts.get(action, []): + if conflict_action in self._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: + action(self, namespace, argument_values, option_string) + # ======================== # Value conversion methods # ========================