diff options
author | Matthew Rasmus <mattr@zzntd.com> | 2014-11-23 16:29:29 -0800 |
---|---|---|
committer | Matthew Rasmus <mattr@zzntd.com> | 2014-11-23 16:29:29 -0800 |
commit | 850da4984682380bc204eae675749df76e50db54 (patch) | |
tree | e5369ee6d501748bee4e6e549abc0b5c22dd120e /python | |
parent | a1a268ce1d00c865f14ae76c1d767f905cfc3a92 (diff) | |
download | servo-850da4984682380bc204eae675749df76e50db54.tar.gz servo-850da4984682380bc204eae675749df76e50db54.zip |
Update mach to latest changes from mozilla-central
Updates the way mach mixes unrecognized arguments and predefined
arguments (see [mozilla bug
1076649](https://bugzilla.mozilla.org/show_bug.cgi?id=1076649) for
details on this change).
Diffstat (limited to 'python')
-rw-r--r-- | python/mach/mach/base.py | 12 | ||||
-rw-r--r-- | python/mach/mach/decorators.py | 59 | ||||
-rw-r--r-- | python/mach/mach/dispatcher.py | 64 |
3 files changed, 109 insertions, 26 deletions
diff --git a/python/mach/mach/base.py b/python/mach/mach/base.py index 3e8e6477357..8989ac0e6b3 100644 --- a/python/mach/mach/base.py +++ b/python/mach/mach/base.py @@ -77,9 +77,6 @@ class MethodHandler(object): # Description of the purpose of this command. 'description', - # Whether to allow all arguments from the parser. - 'allow_all_arguments', - # Functions used to 'skip' commands if they don't meet the conditions # in a given context. 'conditions', @@ -91,20 +88,23 @@ class MethodHandler(object): # Arguments added to this command's parser. This is a 2-tuple of # positional and named arguments, respectively. 'arguments', + + # Argument groups added to this command's parser. + 'argument_group_names', ) def __init__(self, cls, method, name, category=None, description=None, - allow_all_arguments=False, conditions=None, parser=None, arguments=None, - pass_context=False): + conditions=None, parser=None, arguments=None, + argument_group_names=None, pass_context=False): self.cls = cls self.method = method self.name = name self.category = category self.description = description - self.allow_all_arguments = allow_all_arguments self.conditions = conditions or [] self.parser = parser self.arguments = arguments or [] + self.argument_group_names = argument_group_names or [] self.pass_context = pass_context diff --git a/python/mach/mach/decorators.py b/python/mach/mach/decorators.py index cb9a67428e7..aeb10575abb 100644 --- a/python/mach/mach/decorators.py +++ b/python/mach/mach/decorators.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals +import argparse import collections import inspect import types @@ -56,8 +57,8 @@ def CommandProvider(cls): if not isinstance(value, types.FunctionType): continue - command_name, category, description, allow_all, conditions, parser = getattr( - value, '_mach_command', (None, None, None, None, None, None)) + command_name, category, description, conditions, parser = getattr( + value, '_mach_command', (None, None, None, None, None)) if command_name is None: continue @@ -81,9 +82,11 @@ def CommandProvider(cls): arguments = getattr(value, '_mach_command_args', None) + argument_group_names = getattr(value, '_mach_command_arg_group_names', None) + handler = MethodHandler(cls, attr, command_name, category=category, - description=description, allow_all_arguments=allow_all, - conditions=conditions, parser=parser, arguments=arguments, + description=description, conditions=conditions, parser=parser, + arguments=arguments, argument_group_names=argument_group_names, pass_context=pass_context) Registrar.register_command_handler(handler) @@ -102,9 +105,6 @@ class Command(object): description -- A brief description of what the command does. - allow_all_args -- Bool indicating whether to allow unknown arguments - through to the command. - parser -- an optional argparse.ArgumentParser instance to use as the basis for the command arguments. @@ -114,18 +114,17 @@ class Command(object): def foo(self): pass """ - def __init__(self, name, category=None, description=None, - allow_all_args=False, conditions=None, parser=None): + def __init__(self, name, category=None, description=None, conditions=None, + parser=None): self._name = name self._category = category self._description = description - self._allow_all_args = allow_all_args self._conditions = conditions self._parser = parser def __call__(self, func): func._mach_command = (self._name, self._category, self._description, - self._allow_all_args, self._conditions, self._parser) + self._conditions, self._parser) return func @@ -145,6 +144,11 @@ class CommandArgument(object): pass """ def __init__(self, *args, **kwargs): + if kwargs.get('nargs') == argparse.REMAINDER: + # These are the assertions we make in dispatcher.py about + # those types of CommandArguments. + assert len(args) == 1 + assert all(k in ('default', 'nargs', 'help', 'group') for k in kwargs) self._command_args = (args, kwargs) def __call__(self, func): @@ -157,6 +161,39 @@ class CommandArgument(object): return func +class CommandArgumentGroup(object): + """Decorator for additional argument groups to mach subcommands. + + This decorator should be used to add arguments groups to mach commands. + Arguments to the decorator are proxied to + ArgumentParser.add_argument_group(). + + For example: + + @Command('foo', helps='Run the foo action') + @CommandArgumentGroup('group1') + @CommandArgument('-b', '--bar', group='group1', action='store_true', + default=False, help='Enable bar mode.') + def foo(self): + pass + + The name should be chosen so that it makes sense as part of the phrase + 'Command Arguments for <name>' because that's how it will be shown in the + help message. + """ + def __init__(self, group_name): + self._group_name = group_name + + def __call__(self, func): + command_arg_group_names = getattr(func, '_mach_command_arg_group_names', []) + + command_arg_group_names.insert(0, self._group_name) + + func._mach_command_arg_group_names = command_arg_group_names + + return func + + def SettingsProvider(cls): """Class decorator to denote that this class provides Mach settings. diff --git a/python/mach/mach/dispatcher.py b/python/mach/mach/dispatcher.py index 11e58cb9e2f..594cc25e7d7 100644 --- a/python/mach/mach/dispatcher.py +++ b/python/mach/mach/dispatcher.py @@ -135,19 +135,32 @@ class CommandAction(argparse.Action): parser_args = { 'add_help': False, 'usage': '%(prog)s [global arguments] ' + command + - ' command arguments]', + ' [command arguments]', } - if handler.allow_all_arguments: - parser_args['prefix_chars'] = '+' - if handler.parser: subparser = handler.parser else: subparser = argparse.ArgumentParser(**parser_args) + remainder = None + for arg in handler.arguments: - subparser.add_argument(*arg[0], **arg[1]) + # Remove our group keyword; it's not needed here. + group_name = arg[1].get('group') + if group_name: + del arg[1]['group'] + + if arg[1].get('nargs') == argparse.REMAINDER: + # parse_known_args expects all argparse.REMAINDER ('...') + # arguments to be all stuck together. Instead, we want them to + # pick any extra argument, wherever they are. + # Assume a limited CommandArgument for those arguments. + assert len(arg[0]) == 1 + assert all(k in ('default', 'nargs', 'help') for k in arg[1]) + remainder = arg + else: + subparser.add_argument(*arg[0], **arg[1]) # We define the command information on the main parser result so as to # not interfere with arguments passed to the command. @@ -156,7 +169,32 @@ class CommandAction(argparse.Action): command_namespace, extra = subparser.parse_known_args(args) setattr(namespace, 'command_args', command_namespace) - if extra: + if remainder: + (name,), options = remainder + # parse_known_args usefully puts all arguments after '--' in + # extra, but also puts '--' there. We don't want to pass it down + # to the command handler. Note that if multiple '--' are on the + # command line, only the first one is removed, so that subsequent + # ones are passed down. + if '--' in extra: + extra.remove('--') + + # Commands with argparse.REMAINDER arguments used to force the + # other arguments to be '+' prefixed. If a user now passes such + # an argument, if will silently end up in extra. So, check if any + # of the allowed arguments appear in a '+' prefixed form, and error + # out if that's the case. + for args, _ in handler.arguments: + for arg in args: + arg = arg.replace('-', '+', 1) + if arg in extra: + raise UnrecognizedArgumentError(command, [arg]) + + if extra: + setattr(command_namespace, name, extra) + else: + setattr(command_namespace, name, options.get('default', [])) + elif extra: raise UnrecognizedArgumentError(command, extra) def _handle_main_help(self, parser, verbose): @@ -234,9 +272,6 @@ class CommandAction(argparse.Action): 'add_help': False, } - if handler.allow_all_arguments: - parser_args['prefix_chars'] = '+' - if handler.parser: c_parser = handler.parser c_parser.formatter_class = NoUsageFormatter @@ -258,7 +293,18 @@ class CommandAction(argparse.Action): c_parser = argparse.ArgumentParser(**parser_args) group = c_parser.add_argument_group('Command Arguments') + extra_groups = {} + for group_name in handler.argument_group_names: + group_full_name = 'Command Arguments for ' + group_name + extra_groups[group_name] = \ + c_parser.add_argument_group(group_full_name) + for arg in handler.arguments: + # Apply our group keyword. + group_name = arg[1].get('group') + if group_name: + del arg[1]['group'] + group = extra_groups[group_name] group.add_argument(*arg[0], **arg[1]) # This will print the description of the command below the usage. |