diff -r 2cd0aa44d53c Lib/argparse.py --- a/Lib/argparse.py Wed Jan 21 00:47:54 2015 -0500 +++ b/Lib/argparse.py Thu Jan 22 02:42:00 2015 +0000 @@ -247,9 +247,10 @@ if text is not SUPPRESS and text is not None: self._add_item(self._format_text, [text]) - def add_usage(self, usage, actions, groups, prefix=None): + def add_usage(self, usage, actions, mutex_groups, mutdep_groups, + prefix=None): if usage is not SUPPRESS: - args = usage, actions, groups, prefix + args = usage, actions, mutex_groups, mutdep_groups, prefix self._add_item(self._format_usage, args) def add_argument(self, action): @@ -289,7 +290,8 @@ for part in part_strings if part and part is not SUPPRESS]) - def _format_usage(self, usage, actions, groups, prefix): + def _format_usage(self, usage, actions, mutex_groups, mutdep_groups, + prefix): if prefix is None: prefix = _('usage: ') @@ -316,7 +318,8 @@ # build full usage string format = self._format_actions_usage - action_usage = format(optionals + positionals, groups) + action_usage = format( + optionals + positionals, mutex_groups, mutdep_groups) usage = ' '.join([s for s in [prog, action_usage] if s]) # wrap the usage parts if it's too long @@ -325,8 +328,8 @@ # break usage into wrappable parts part_regexp = r'\(.*?\)+|\[.*?\]+|\S+' - opt_usage = format(optionals, groups) - pos_usage = format(positionals, groups) + opt_usage = format(optionals, mutex_groups, mutdep_groups) + pos_usage = format(positionals, mutex_groups, mutdep_groups) opt_parts = _re.findall(part_regexp, opt_usage) pos_parts = _re.findall(part_regexp, pos_usage) assert ' '.join(opt_parts) == opt_usage @@ -381,10 +384,9 @@ # prefix with 'usage:' return '%s%s\n\n' % (prefix, usage) - def _format_actions_usage(self, actions, groups): + def _format_actions_groups(self, inserts, groups, actions, group_actions, + sep): # find group indices and identify actions in groups - group_actions = set() - inserts = {} for group in groups: try: start = actions.index(group._group_actions[0]) @@ -408,7 +410,16 @@ inserts[start] = '(' inserts[end] = ')' for i in range(start + 1, end): - inserts[i] = '|' + inserts[i] = sep + + def _format_actions_usage(self, actions, mutex_groups, mutdep_groups): + # find group indices and identify actions in groups + group_actions = set() + inserts = {} + self._format_actions_groups( + inserts, mutex_groups, actions, group_actions, '|') + self._format_actions_groups( + inserts, mutdep_groups, actions, group_actions, '&') # collect all actions format strings parts = [] @@ -418,9 +429,9 @@ # remove | separators for suppressed arguments if action.help is SUPPRESS: parts.append(None) - if inserts.get(i) == '|': + if inserts.get(i) in ('|', '&'): inserts.pop(i) - elif inserts.get(i + 1) == '|': + elif inserts.get(i + 1) in ('|', '&'): inserts.pop(i + 1) # produce all arg strings @@ -465,13 +476,13 @@ # join all the action items with spaces text = ' '.join([item for item in parts if item is not None]) - # clean up separators for mutually exclusive groups + # clean up separators for mutually exclusive/dependence groups open = r'[\[(]' close = r'[\])]' text = _re.sub(r'(%s) ' % open, r'\1', text) text = _re.sub(r' (%s)' % close, r'\1', text) text = _re.sub(r'%s *%s' % (open, close), r'', text) - text = _re.sub(r'\(([^|]*)\)', r'\1', text) + text = _re.sub(r'\(([^|&]*)\)', r'\1', text) text = text.strip() # return the text @@ -673,7 +684,7 @@ if argument is None: return None elif argument.option_strings: - return '/'.join(argument.option_strings) + return '/'.join(argument.option_strings) elif argument.metavar not in (None, SUPPRESS): return argument.metavar elif argument.dest not in (None, SUPPRESS): @@ -1217,6 +1228,7 @@ # groups self._action_groups = [] self._mutually_exclusive_groups = [] + self._mutually_dependence_groups = [] # defaults storage self._defaults = {} @@ -1303,7 +1315,8 @@ try: self._get_formatter()._format_args(action, None) except TypeError: - raise ValueError("length of metavar tuple does not match nargs") + raise ValueError( + "length of metavar tuple does not match nargs") return self._add_action(action) @@ -1317,6 +1330,11 @@ self._mutually_exclusive_groups.append(group) return group + def add_mutually_dependence_group(self, **kwargs): + group = _MutuallyDependenceGroup(self, **kwargs) + self._mutually_dependence_groups.append(group) + return group + def _add_action(self, action): # resolve any conflicts self._check_conflict(action) @@ -1377,6 +1395,13 @@ for action in group._group_actions: group_map[action] = mutex_group + # add container's mutually dependence groups + for group in container._mutually_dependence_groups: + mutdep_group = self.add_mutually_dependence_group( + required=group.required) + for action in group._group_actions: + group_map[action] = mutdep_group + # add all actions to this container or their group for action in container._actions: group_map.get(action, self)._add_action(action) @@ -1504,6 +1529,8 @@ self._has_negative_number_optionals = \ container._has_negative_number_optionals self._mutually_exclusive_groups = container._mutually_exclusive_groups + self._mutually_dependence_groups = \ + container._mutually_dependence_groups def _add_action(self, action): action = super(_ArgumentGroup, self)._add_action(action) @@ -1535,6 +1562,22 @@ self._group_actions.remove(action) +class _MutuallyDependenceGroup(_MutuallyExclusiveGroup): + + def _add_action(self, action): + if action.required: + msg = _('mutually dependence arguments must be optional') + raise ValueError(msg) + elif isinstance(action, (_StoreFalseAction, _StoreTrueAction)): + msg = _( + 'mutually dependence action can not be store_true/store_false' + ) + raise ValueError(msg) + action = self._container._add_action(action) + self._group_actions.append(action) + return action + + class ArgumentParser(_AttributeHolder, _ActionsContainer): """Object for parsing command line strings into Python objects. @@ -1665,8 +1708,11 @@ if kwargs.get('prog') is None: formatter = self._get_formatter() positionals = self._get_positional_actions() - groups = self._mutually_exclusive_groups - formatter.add_usage(self.usage, positionals, groups, '') + mutex_groups = self._mutually_exclusive_groups + mutdep_groups = self._mutually_dependence_groups + + formatter.add_usage( + self.usage, positionals, mutex_groups, mutdep_groups, '') kwargs['prog'] = formatter.format_help().strip() # create the parsers action and add it to the positionals list @@ -1982,6 +2028,31 @@ msg = _('one of the arguments %s is required') self.error(msg % ' '.join(names)) + for group in self._mutually_dependence_groups: + group_actions = group._group_actions + values = [] + names = [] + dep_names = [] + for action in group_actions: + action_name = _get_action_name(action) + dest = getattr(namespace, action.dest) + + if dest and not action.default: + names.append(action_name) + values.append(dest) + elif not action.default: + dep_names.append(action_name) + + values_length = len(values) + + if group.required and not values_length: + msg = _('the arguments %s is required') + self.error(msg % ' '.join(dep_names)) + + if values_length >= 1 and dep_names: + msg = _('%s dependence on %s') + self.error(msg % (' '.join(names), ' '.join(dep_names))) + # return the updated namespace and the extra arguments return namespace, extras @@ -2287,7 +2358,8 @@ def format_usage(self): formatter = self._get_formatter() formatter.add_usage(self.usage, self._actions, - self._mutually_exclusive_groups) + self._mutually_exclusive_groups, + self._mutually_dependence_groups) return formatter.format_help() def format_help(self): @@ -2295,7 +2367,8 @@ # usage formatter.add_usage(self.usage, self._actions, - self._mutually_exclusive_groups) + self._mutually_exclusive_groups, + self._mutually_dependence_groups) # description formatter.add_text(self.description)