aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--python/mach/mach/base.py12
-rw-r--r--python/mach/mach/decorators.py59
-rw-r--r--python/mach/mach/dispatcher.py64
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.