Update mach from gecko tree

This commit is contained in:
James Graham 2015-07-01 10:53:23 +01:00
parent 9897125b34
commit f1641fde8f
18 changed files with 785 additions and 520 deletions

View file

@ -2,7 +2,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import unicode_literals
from __future__ import absolute_import, unicode_literals
class CommandContext(object):
@ -44,67 +44,3 @@ class UnrecognizedArgumentError(MachError):
self.command = command
self.arguments = arguments
class MethodHandler(object):
"""Describes a Python method that implements a mach command.
Instances of these are produced by mach when it processes classes
defining mach commands.
"""
__slots__ = (
# The Python class providing the command. This is the class type not
# an instance of the class. Mach will instantiate a new instance of
# the class if the command is executed.
'cls',
# Whether the __init__ method of the class should receive a mach
# context instance. This should only affect the mach driver and how
# it instantiates classes.
'pass_context',
# The name of the method providing the command. In other words, this
# is the str name of the attribute on the class type corresponding to
# the name of the function.
'method',
# The name of the command.
'name',
# String category this command belongs to.
'category',
# Description of the purpose of this command.
'description',
# Functions used to 'skip' commands if they don't meet the conditions
# in a given context.
'conditions',
# argparse.ArgumentParser instance to use as the basis for command
# arguments.
'parser',
# 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,
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.conditions = conditions or []
self.parser = parser
self.arguments = arguments or []
self.argument_group_names = argument_group_names or []
self.pass_context = pass_context

View file

@ -2,11 +2,12 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, # You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import print_function, unicode_literals
from __future__ import absolute_import, print_function, unicode_literals
from mach.decorators import (
CommandProvider,
Command,
CommandArgument,
)
@ -22,11 +23,16 @@ class BuiltinCommands(object):
@Command('mach-debug-commands', category='misc',
description='Show info about available mach commands.')
def debug_commands(self):
@CommandArgument('match', metavar='MATCH', default=None, nargs='?',
help='Only display commands containing given substring.')
def debug_commands(self, match=None):
import inspect
handlers = self.context.commands.command_handlers
for command in sorted(handlers.keys()):
if match and match not in command:
continue
handler = handlers[command]
cls = handler.cls
method = getattr(cls, getattr(handler, 'method'))

View file

@ -2,11 +2,14 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import print_function, unicode_literals
from __future__ import absolute_import, print_function, unicode_literals
from textwrap import TextWrapper
from mach.decorators import Command
from mach.decorators import (
CommandProvider,
Command,
)
#@CommandProvider

View file

@ -25,7 +25,7 @@ msgfmt binary to perform this conversion. Generation of the original .po file
can be done via the write_pot() of ConfigSettings.
"""
from __future__ import unicode_literals
from __future__ import absolute_import, unicode_literals
import collections
import gettext

View file

@ -2,22 +2,97 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import unicode_literals
from __future__ import absolute_import, unicode_literals
import argparse
import collections
import inspect
import types
from .base import (
MachError,
MethodHandler
)
from .base import MachError
from .config import ConfigProvider
from .registrar import Registrar
class _MachCommand(object):
"""Container for mach command metadata.
Mach commands contain lots of attributes. This class exists to capture them
in a sane way so tuples, etc aren't used instead.
"""
__slots__ = (
# Content from decorator arguments to define the command.
'name',
'subcommand',
'category',
'description',
'conditions',
'_parser',
'arguments',
'argument_group_names',
# Describes how dispatch is performed.
# The Python class providing the command. This is the class type not
# an instance of the class. Mach will instantiate a new instance of
# the class if the command is executed.
'cls',
# Whether the __init__ method of the class should receive a mach
# context instance. This should only affect the mach driver and how
# it instantiates classes.
'pass_context',
# The name of the method providing the command. In other words, this
# is the str name of the attribute on the class type corresponding to
# the name of the function.
'method',
# Dict of string to _MachCommand defining sub-commands for this
# command.
'subcommand_handlers',
)
def __init__(self, name=None, subcommand=None, category=None,
description=None, conditions=None, parser=None):
self.name = name
self.subcommand = subcommand
self.category = category
self.description = description
self.conditions = conditions or []
self._parser = parser
self.arguments = []
self.argument_group_names = []
self.cls = None
self.pass_context = None
self.method = None
self.subcommand_handlers = {}
@property
def parser(self):
# Creating CLI parsers at command dispatch time can be expensive. Make
# it possible to lazy load them by using functions.
if callable(self._parser):
self._parser = self._parser()
return self._parser
@property
def docstring(self):
return self.cls.__dict__[self.method].__doc__
def __ior__(self, other):
if not isinstance(other, _MachCommand):
raise ValueError('can only operate on _MachCommand instances')
for a in self.__slots__:
if not getattr(self, a):
setattr(self, a, getattr(other, a))
return self
def CommandProvider(cls):
"""Class decorator to denote that it provides subcommands for Mach.
@ -47,6 +122,8 @@ def CommandProvider(cls):
if len(spec.args) == 2:
pass_context = True
seen_commands = set()
# We scan __dict__ because we only care about the classes own attributes,
# not inherited ones. If we did inherited attributes, we could potentially
# define commands multiple times. We also sort keys so commands defined in
@ -57,45 +134,80 @@ def CommandProvider(cls):
if not isinstance(value, types.FunctionType):
continue
command_name, category, description, conditions, parser = getattr(
value, '_mach_command', (None, None, None, None, None))
if command_name is None:
command = getattr(value, '_mach_command', None)
if not command:
continue
if conditions is None and Registrar.require_conditions:
# Ignore subcommands for now: we handle them later.
if command.subcommand:
continue
seen_commands.add(command.name)
if not command.conditions and Registrar.require_conditions:
continue
msg = 'Mach command \'%s\' implemented incorrectly. ' + \
'Conditions argument must take a list ' + \
'of functions. Found %s instead.'
conditions = conditions or []
if not isinstance(conditions, collections.Iterable):
msg = msg % (command_name, type(conditions))
if not isinstance(command.conditions, collections.Iterable):
msg = msg % (command.name, type(command.conditions))
raise MachError(msg)
for c in conditions:
for c in command.conditions:
if not hasattr(c, '__call__'):
msg = msg % (command_name, type(c))
msg = msg % (command.name, type(c))
raise MachError(msg)
arguments = getattr(value, '_mach_command_args', None)
command.cls = cls
command.method = attr
command.pass_context = pass_context
argument_group_names = getattr(value, '_mach_command_arg_group_names', None)
Registrar.register_command_handler(command)
handler = MethodHandler(cls, attr, command_name, category=category,
description=description, conditions=conditions, parser=parser,
arguments=arguments, argument_group_names=argument_group_names,
pass_context=pass_context)
# Now do another pass to get sub-commands. We do this in two passes so
# we can check the parent command existence without having to hold
# state and reconcile after traversal.
for attr in sorted(cls.__dict__.keys()):
value = cls.__dict__[attr]
Registrar.register_command_handler(handler)
if not isinstance(value, types.FunctionType):
continue
command = getattr(value, '_mach_command', None)
if not command:
continue
# It is a regular command.
if not command.subcommand:
continue
if command.name not in seen_commands:
raise MachError('Command referenced by sub-command does not '
'exist: %s' % command.name)
if command.name not in Registrar.command_handlers:
continue
command.cls = cls
command.method = attr
command.pass_context = pass_context
parent = Registrar.command_handlers[command.name]
if parent._parser:
raise MachError('cannot declare sub commands against a command '
'that has a parser installed: %s' % command)
if command.subcommand in parent.subcommand_handlers:
raise MachError('sub-command already defined: %s' % command.subcommand)
parent.subcommand_handlers[command.subcommand] = command
return cls
class Command(object):
"""Decorator for functions or methods that provide a mach subcommand.
"""Decorator for functions or methods that provide a mach command.
The decorator accepts arguments that define basic attributes of the
command. The following arguments are recognized:
@ -105,8 +217,9 @@ class Command(object):
description -- A brief description of what the command does.
parser -- an optional argparse.ArgumentParser instance to use as
the basis for the command arguments.
parser -- an optional argparse.ArgumentParser instance or callable
that returns an argparse.ArgumentParser instance to use as the
basis for the command arguments.
For example:
@ -114,20 +227,45 @@ class Command(object):
def foo(self):
pass
"""
def __init__(self, name, category=None, description=None, conditions=None,
parser=None):
self._name = name
self._category = category
self._description = description
self._conditions = conditions
self._parser = parser
def __init__(self, name, **kwargs):
self._mach_command = _MachCommand(name=name, **kwargs)
def __call__(self, func):
func._mach_command = (self._name, self._category, self._description,
self._conditions, self._parser)
if not hasattr(func, '_mach_command'):
func._mach_command = _MachCommand()
func._mach_command |= self._mach_command
return func
class SubCommand(object):
"""Decorator for functions or methods that provide a sub-command.
Mach commands can have sub-commands. e.g. ``mach command foo`` or
``mach command bar``. Each sub-command has its own parser and is
effectively its own mach command.
The decorator accepts arguments that define basic attributes of the
sub command:
command -- The string of the command this sub command should be
attached to.
subcommand -- The string name of the sub command to register.
description -- A textual description for this sub command.
"""
def __init__(self, command, subcommand, description=None):
self._mach_command = _MachCommand(name=command, subcommand=subcommand,
description=description)
def __call__(self, func):
if not hasattr(func, '_mach_command'):
func._mach_command = _MachCommand()
func._mach_command |= self._mach_command
return func
class CommandArgument(object):
"""Decorator for additional arguments to mach subcommands.
@ -152,17 +290,16 @@ class CommandArgument(object):
self._command_args = (args, kwargs)
def __call__(self, func):
command_args = getattr(func, '_mach_command_args', [])
if not hasattr(func, '_mach_command'):
func._mach_command = _MachCommand()
command_args.insert(0, self._command_args)
func._mach_command_args = command_args
func._mach_command.arguments.insert(0, self._command_args)
return func
class CommandArgumentGroup(object):
"""Decorator for additional argument groups to mach subcommands.
"""Decorator for additional argument groups to mach commands.
This decorator should be used to add arguments groups to mach commands.
Arguments to the decorator are proxied to
@ -185,11 +322,10 @@ class CommandArgumentGroup(object):
self._group_name = group_name
def __call__(self, func):
command_arg_group_names = getattr(func, '_mach_command_arg_group_names', [])
if not hasattr(func, '_mach_command'):
func._mach_command = _MachCommand()
command_arg_group_names.insert(0, self._group_name)
func._mach_command_arg_group_names = command_arg_group_names
func._mach_command.argument_group_names.insert(0, self._group_name)
return func

View file

@ -2,7 +2,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import unicode_literals
from __future__ import absolute_import, unicode_literals
import argparse
import difflib
@ -11,6 +11,7 @@ import sys
from operator import itemgetter
from .base import (
MachError,
NoCommandError,
UnknownCommandError,
UnrecognizedArgumentError,
@ -95,37 +96,71 @@ class CommandAction(argparse.Action):
if command == 'help':
if args and args[0] not in ['-h', '--help']:
# Make sure args[0] is indeed a command.
self._handle_subcommand_help(parser, args[0])
self._handle_command_help(parser, args[0])
else:
self._handle_main_help(parser, namespace.verbose)
sys.exit(0)
elif '-h' in args or '--help' in args:
# -h or --help is in the command arguments.
self._handle_subcommand_help(parser, command)
self._handle_command_help(parser, command)
sys.exit(0)
else:
raise NoCommandError()
# Command suggestion
if command not in self._mach_registrar.command_handlers:
# Make sure we don't suggest any deprecated commands.
names = [h.name for h in self._mach_registrar.command_handlers.values()
if h.cls.__name__ == 'DeprecatedCommands']
# We first try to look for a valid command that is very similar to the given command.
suggested_commands = difflib.get_close_matches(command, self._mach_registrar.command_handlers.keys(), cutoff=0.8)
suggested_commands = difflib.get_close_matches(command, names, cutoff=0.8)
# If we find more than one matching command, or no command at all, we give command suggestions instead
# (with a lower matching threshold). All commands that start with the given command (for instance: 'mochitest-plain',
# 'mochitest-chrome', etc. for 'mochitest-') are also included.
if len(suggested_commands) != 1:
suggested_commands = set(difflib.get_close_matches(command, self._mach_registrar.command_handlers.keys(), cutoff=0.5))
suggested_commands |= {cmd for cmd in self._mach_registrar.command_handlers if cmd.startswith(command)}
suggested_commands = set(difflib.get_close_matches(command, names, cutoff=0.5))
suggested_commands |= {cmd for cmd in names if cmd.startswith(command)}
raise UnknownCommandError(command, 'run', suggested_commands)
sys.stderr.write("We're assuming the '%s' command is '%s' and we're executing it for you.\n\n" % (command, suggested_commands[0]))
command = suggested_commands[0]
handler = self._mach_registrar.command_handlers.get(command)
# FUTURE
# If we wanted to conditionally enable commands based on whether
# it's possible to run them given the current state of system, here
# would be a good place to hook that up.
usage = '%(prog)s [global arguments] ' + command + \
' [command arguments]'
subcommand = None
# If there are sub-commands, parse the intent out immediately.
if handler.subcommand_handlers:
if not args:
self._handle_subcommand_main_help(parser, handler)
sys.exit(0)
elif len(args) == 1 and args[0] in ('help', '--help'):
self._handle_subcommand_main_help(parser, handler)
sys.exit(0)
# mach <command> help <subcommand>
elif len(args) == 2 and args[0] == 'help':
subcommand = args[1]
subhandler = handler.subcommand_handlers[subcommand]
self._handle_subcommand_help(parser, command, subcommand, subhandler)
sys.exit(0)
# We are running a sub command.
else:
subcommand = args[0]
if subcommand[0] == '-':
raise MachError('%s invoked improperly. A sub-command name '
'must be the first argument after the command name.' %
command)
if subcommand not in handler.subcommand_handlers:
raise UnknownCommandError(subcommand, 'run',
handler.subcommand_handlers.keys())
handler = handler.subcommand_handlers[subcommand]
usage = '%(prog)s [global arguments] ' + command + ' ' + \
subcommand + ' [command arguments]'
args.pop(0)
# We create a new parser, populate it with the command's arguments,
# then feed all remaining arguments to it, merging the results
@ -134,12 +169,12 @@ class CommandAction(argparse.Action):
parser_args = {
'add_help': False,
'usage': '%(prog)s [global arguments] ' + command +
' [command arguments]',
'usage': usage,
}
if handler.parser:
subparser = handler.parser
subparser.context = self._context
else:
subparser = argparse.ArgumentParser(**parser_args)
@ -166,6 +201,7 @@ class CommandAction(argparse.Action):
# not interfere with arguments passed to the command.
setattr(namespace, 'mach_handler', handler)
setattr(namespace, 'command', command)
setattr(namespace, 'subcommand', subcommand)
command_namespace, extra = subparser.parse_known_args(args)
setattr(namespace, 'command_args', command_namespace)
@ -251,12 +287,31 @@ class CommandAction(argparse.Action):
parser.print_help()
def _handle_subcommand_help(self, parser, command):
def _populate_command_group(self, parser, handler, group):
extra_groups = {}
for group_name in handler.argument_group_names:
group_full_name = 'Command Arguments for ' + group_name
extra_groups[group_name] = \
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])
def _handle_command_help(self, parser, command):
handler = self._mach_registrar.command_handlers.get(command)
if not handler:
raise UnknownCommandError(command, 'query')
if handler.subcommand_handlers:
self._handle_subcommand_main_help(parser, handler)
return
# This code is worth explaining. Because we are doing funky things with
# argument registration to allow the same option in both global and
# command arguments, we can't simply put all arguments on the same
@ -274,6 +329,7 @@ class CommandAction(argparse.Action):
if handler.parser:
c_parser = handler.parser
c_parser.context = self._context
c_parser.formatter_class = NoUsageFormatter
# Accessing _action_groups is a bit shady. We are highly dependent
# on the argparse implementation not changing. We fail fast to
@ -293,31 +349,84 @@ 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)
self._populate_command_group(c_parser, handler, group)
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.
description = handler.description
if description:
parser.description = description
# Set the long help of the command to the docstring (if present) or
# the command decorator description argument (if present).
if handler.docstring:
parser.description = format_docstring(handler.docstring)
elif handler.description:
parser.description = handler.description
parser.usage = '%(prog)s [global arguments] ' + command + \
' [command arguments]'
# This is needed to preserve line endings in the description field,
# which may be populated from a docstring.
parser.formatter_class = argparse.RawDescriptionHelpFormatter
parser.print_help()
print('')
c_parser.print_help()
def _handle_subcommand_main_help(self, parser, handler):
parser.usage = '%(prog)s [global arguments] ' + handler.name + \
' subcommand [subcommand arguments]'
group = parser.add_argument_group('Sub Commands')
for subcommand, subhandler in sorted(handler.subcommand_handlers.iteritems()):
group.add_argument(subcommand, help=subhandler.description,
action='store_true')
if handler.docstring:
parser.description = format_docstring(handler.docstring)
parser.formatter_class = argparse.RawDescriptionHelpFormatter
parser.print_help()
def _handle_subcommand_help(self, parser, command, subcommand, handler):
parser.usage = '%(prog)s [global arguments] ' + command + \
' ' + subcommand + ' [command arguments]'
c_parser = argparse.ArgumentParser(add_help=False,
formatter_class=CommandFormatter)
group = c_parser.add_argument_group('Sub Command Arguments')
self._populate_command_group(c_parser, handler, group)
if handler.docstring:
parser.description = format_docstring(handler.docstring)
parser.formatter_class = argparse.RawDescriptionHelpFormatter
parser.print_help()
print('')
c_parser.print_help()
class NoUsageFormatter(argparse.HelpFormatter):
def _format_usage(self, *args, **kwargs):
return ""
def format_docstring(docstring):
"""Format a raw docstring into something suitable for presentation.
This function is based on the example function in PEP-0257.
"""
if not docstring:
return ''
lines = docstring.expandtabs().splitlines()
indent = sys.maxint
for line in lines[1:]:
stripped = line.lstrip()
if stripped:
indent = min(indent, len(line) - len(stripped))
trimmed = [lines[0].strip()]
if indent < sys.maxint:
for line in lines[1:]:
trimmed.append(line[indent:].rstrip())
while trimmed and not trimmed[-1]:
trimmed.pop()
while trimmed and not trimmed[0]:
trimmed.pop(0)
return '\n'.join(trimmed)

View file

@ -25,7 +25,11 @@ from .base import (
UnrecognizedArgumentError,
)
from .decorators import CommandProvider
from .decorators import (
CommandArgument,
CommandProvider,
Command,
)
from .config import ConfigSettings
from .dispatcher import CommandAction
@ -87,13 +91,6 @@ It looks like you passed an unrecognized argument into mach.
The %s command does not accept the arguments: %s
'''.lstrip()
INVALID_COMMAND_CONTEXT = r'''
It looks like you tried to run a mach command from an invalid context. The %s
command failed to meet the following conditions: %s
Run |mach help| to show a list of all commands available to the current context.
'''.lstrip()
INVALID_ENTRY_POINT = r'''
Entry points should return a list of command providers or directories
containing command providers. The following entry point is invalid:
@ -153,7 +150,7 @@ class ContextWrapper(object):
except AttributeError as e:
try:
ret = object.__getattribute__(self, '_handler')(self, key)
except AttributeError, TypeError:
except (AttributeError, TypeError):
# TypeError is in case the handler comes from old code not
# taking a key argument.
raise e
@ -421,41 +418,17 @@ To see more help for a specific command, run:
raise MachError('ArgumentParser result missing mach handler info.')
handler = getattr(args, 'mach_handler')
cls = handler.cls
if handler.pass_context:
instance = cls(context)
else:
instance = cls()
if handler.conditions:
fail_conditions = []
for c in handler.conditions:
if not c(instance):
fail_conditions.append(c)
if fail_conditions:
print(self._condition_failed_message(handler.name, fail_conditions))
return 1
fn = getattr(instance, handler.method)
try:
result = fn(**vars(args.command_args))
if not result:
result = 0
assert isinstance(result, (int, long))
return result
return Registrar._run_command_handler(handler, context=context,
debug_command=args.debug_command, **vars(args.command_args))
except KeyboardInterrupt as ki:
raise ki
except Exception as e:
exc_type, exc_value, exc_tb = sys.exc_info()
# The first frame is us and is never used.
stack = traceback.extract_tb(exc_tb)[1:]
# The first two frames are us and are never used.
stack = traceback.extract_tb(exc_tb)[2:]
# If we have nothing on the stack, the exception was raised as part
# of calling the @Command method itself. This likely means a
@ -502,16 +475,6 @@ To see more help for a specific command, run:
self.logger.log(level, format_str,
extra={'action': action, 'params': params})
@classmethod
def _condition_failed_message(cls, name, conditions):
msg = ['\n']
for c in conditions:
part = [' %s' % c.__name__]
if c.__doc__ is not None:
part.append(c.__doc__)
msg.append(' - '.join(part))
return INVALID_COMMAND_CONTEXT % (name, '\n'.join(msg))
def _print_error_header(self, argv, fh):
fh.write('Error running mach:\n\n')
fh.write(' ')
@ -598,6 +561,8 @@ To see more help for a specific command, run:
global_group.add_argument('-h', '--help', dest='help',
action='store_true', default=False,
help='Show this help message.')
global_group.add_argument('--debug-command', action='store_true',
help='Start a Python debugger when command is dispatched.')
for args, kwargs in self.global_arguments:
global_group.add_argument(*args, **kwargs)

View file

@ -2,10 +2,17 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import unicode_literals
from __future__ import absolute_import, unicode_literals
from .base import MachError
INVALID_COMMAND_CONTEXT = r'''
It looks like you tried to run a mach command from an invalid context. The %s
command failed to meet the following conditions: %s
Run |mach help| to show a list of all commands available to the current context.
'''.lstrip()
class MachRegistrar(object):
"""Container for mach command and config providers."""
@ -38,15 +45,17 @@ class MachRegistrar(object):
self.categories[name] = (title, description, priority)
self.commands_by_category[name] = set()
def dispatch(self, name, context=None, **args):
"""Dispatch/run a command.
@classmethod
def _condition_failed_message(cls, name, conditions):
msg = ['\n']
for c in conditions:
part = [' %s' % c.__name__]
if c.__doc__ is not None:
part.append(c.__doc__)
msg.append(' - '.join(part))
return INVALID_COMMAND_CONTEXT % (name, '\n'.join(msg))
Commands can use this to call other commands.
"""
# TODO The logic in this function overlaps with code in
# mach.main.Main._run() and should be consolidated.
handler = self.command_handlers[name]
def _run_command_handler(self, handler, context=None, debug_command=False, **kwargs):
cls = handler.cls
if handler.pass_context and not context:
@ -57,9 +66,49 @@ class MachRegistrar(object):
else:
instance = cls()
if handler.conditions:
fail_conditions = []
for c in handler.conditions:
if not c(instance):
fail_conditions.append(c)
if fail_conditions:
print(self._condition_failed_message(handler.name, fail_conditions))
return 1
fn = getattr(instance, handler.method)
return fn(**args) or 0
if debug_command:
import pdb
result = pdb.runcall(fn, **kwargs)
else:
result = fn(**kwargs)
result = result or 0
assert isinstance(result, (int, long))
return result
def dispatch(self, name, context=None, argv=None, **kwargs):
"""Dispatch/run a command.
Commands can use this to call other commands.
"""
# TODO handler.subcommand_handlers are ignored
handler = self.command_handlers[name]
if handler.parser:
parser = handler.parser
# save and restore existing defaults so **kwargs don't persist across
# subsequent invocations of Registrar.dispatch()
old_defaults = parser._defaults.copy()
parser.set_defaults(**kwargs)
kwargs, _ = parser.parse_known_args(argv or [])
kwargs = vars(kwargs)
parser._defaults = old_defaults
return self._run_command_handler(handler, context=context, **kwargs)
Registrar = MachRegistrar()

View file

@ -8,7 +8,7 @@ All the terminal interaction code is consolidated so the complexity can be in
one place, away from code that is commonly looked at.
"""
from __future__ import print_function, unicode_literals
from __future__ import absolute_import, print_function, unicode_literals
import logging
import sys

View file

@ -9,6 +9,7 @@ import os
import unittest
from mach.main import Mach
from mach.base import CommandContext
here = os.path.abspath(os.path.dirname(__file__))

View file

@ -4,6 +4,8 @@
from __future__ import unicode_literals
import time
from mach.decorators import (
CommandArgument,
CommandProvider,

View file

@ -8,6 +8,7 @@ import os
from mach.base import MachError
from mach.main import Mach
from mach.registrar import Registrar
from mach.test.common import TestBase
from mozunit import main
@ -48,14 +49,14 @@ class TestConditions(TestBase):
result, stdout, stderr = self._run_mach([name])
self.assertEquals(1, result)
fail_msg = Mach._condition_failed_message(name, fail_conditions)
fail_msg = Registrar._condition_failed_message(name, fail_conditions)
self.assertEquals(fail_msg.rstrip(), stdout.rstrip())
for name in ('cmd_bar_ctx', 'cmd_foobar_ctx'):
result, stdout, stderr = self._run_mach([name], _populate_context)
self.assertEquals(1, result)
fail_msg = Mach._condition_failed_message(name, fail_conditions)
fail_msg = Registrar._condition_failed_message(name, fail_conditions)
self.assertEquals(fail_msg.rstrip(), stdout.rstrip())
def test_invalid_type(self):

View file

@ -11,6 +11,8 @@ from mach.base import MachError
from mach.test.common import TestBase
from mock import patch
from mozunit import main
here = os.path.abspath(os.path.dirname(__file__))