From 85ea4a47c623931805547140f3b22624133abf1c Mon Sep 17 00:00:00 2001 From: Ahmed Kachkach Date: Tue, 26 Aug 2014 16:17:00 +0200 Subject: [PATCH] Bug 1057694 - Command suggestions in mach. r=gps --- python/mach/mach/base.py | 3 ++- python/mach/mach/dispatcher.py | 20 +++++++++++++++----- python/mach/mach/main.py | 9 +++++++-- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/python/mach/mach/base.py b/python/mach/mach/base.py index 076f1b23f865c..3e8e647735771 100644 --- a/python/mach/mach/base.py +++ b/python/mach/mach/base.py @@ -29,11 +29,12 @@ class NoCommandError(MachError): class UnknownCommandError(MachError): """Raised when we attempted to execute an unknown command.""" - def __init__(self, command, verb): + def __init__(self, command, verb, suggested_commands=None): MachError.__init__(self) self.command = command self.verb = verb + self.suggested_commands = suggested_commands or [] class UnrecognizedArgumentError(MachError): """Raised when an unknown argument is passed to mach.""" diff --git a/python/mach/mach/dispatcher.py b/python/mach/mach/dispatcher.py index da9662259fbc0..11e58cb9e2f78 100644 --- a/python/mach/mach/dispatcher.py +++ b/python/mach/mach/dispatcher.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import argparse +import difflib import sys from operator import itemgetter @@ -105,12 +106,21 @@ def __call__(self, parser, namespace, values, option_string=None): else: raise NoCommandError() - handler = self._mach_registrar.command_handlers.get(command) + # Command suggestion + if command not in self._mach_registrar.command_handlers: + # 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) + # 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)} + 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] - # FUTURE consider looking for commands with similar names and - # suggest or run them. - if not handler: - raise UnknownCommandError(command, 'run') + handler = self._mach_registrar.command_handlers.get(command) # FUTURE # If we wanted to conditionally enable commands based on whether diff --git a/python/mach/mach/main.py b/python/mach/mach/main.py index 97ebdeb313791..3c74bd513335c 100644 --- a/python/mach/mach/main.py +++ b/python/mach/mach/main.py @@ -78,10 +78,14 @@ UNKNOWN_COMMAND_ERROR = r''' It looks like you are trying to %s an unknown mach command: %s - +%s Run |mach help| to show a list of commands. '''.lstrip() +SUGGESTED_COMMANDS_MESSAGE = r''' +Did you want to %s any of these commands instead: %s? +''' + UNRECOGNIZED_ARGUMENT_ERROR = r''' It looks like you passed an unrecognized argument into mach. @@ -388,7 +392,8 @@ def _run(self, argv): print(NO_COMMAND_ERROR) return 1 except UnknownCommandError as e: - print(UNKNOWN_COMMAND_ERROR % (e.verb, e.command)) + suggestion_message = SUGGESTED_COMMANDS_MESSAGE % (e.verb, ', '.join(e.suggested_commands)) if e.suggested_commands else '' + print(UNKNOWN_COMMAND_ERROR % (e.verb, e.command, suggestion_message)) return 1 except UnrecognizedArgumentError as e: print(UNRECOGNIZED_ARGUMENT_ERROR % (e.command,