| Index: git_cl.py
|
| diff --git a/git_cl.py b/git_cl.py
|
| index 7a43959726d8d4dbe6922787f07fbdbe6bede381..7e9180e8a963c15d7a013406830091089f3b150f 100755
|
| --- a/git_cl.py
|
| +++ b/git_cl.py
|
| @@ -7,6 +7,7 @@
|
|
|
| """A git-command for integrating reviews on Rietveld."""
|
|
|
| +import difflib
|
| import json
|
| import logging
|
| import optparse
|
| @@ -15,8 +16,8 @@ import re
|
| import stat
|
| import sys
|
| import textwrap
|
| -import urlparse
|
| import urllib2
|
| +import urlparse
|
|
|
| try:
|
| import readline # pylint: disable=F0401,W0611
|
| @@ -2019,8 +2020,43 @@ def CMDformat(parser, args):
|
| return 0
|
|
|
|
|
| +### Glue code for subcommand handling.
|
| +
|
| +
|
| +def Commands():
|
| + """Returns a dict of command and their handling function."""
|
| + module = sys.modules[__name__]
|
| + cmds = (fn[3:] for fn in dir(module) if fn.startswith('CMD'))
|
| + return dict((cmd, getattr(module, 'CMD' + cmd)) for cmd in cmds)
|
| +
|
| +
|
| def Command(name):
|
| - return getattr(sys.modules[__name__], 'CMD' + name, None)
|
| + """Retrieves the function to handle a command."""
|
| + commands = Commands()
|
| + if name in commands:
|
| + return commands[name]
|
| +
|
| + # Try to be smart and look if there's something similar.
|
| + commands_with_prefix = [c for c in commands if c.startswith(name)]
|
| + if len(commands_with_prefix) == 1:
|
| + return commands[commands_with_prefix[0]]
|
| +
|
| + # A #closeenough approximation of levenshtein distance.
|
| + def close_enough(a, b):
|
| + return difflib.SequenceMatcher(a=a, b=b).ratio()
|
| +
|
| + hamming_commands = sorted(
|
| + ((close_enough(c, name), c) for c in commands),
|
| + reverse=True)
|
| + if (hamming_commands[0][0] - hamming_commands[1][0]) < 0.3:
|
| + # Too ambiguous.
|
| + return
|
| +
|
| + if hamming_commands[0][0] < 0.8:
|
| + # Not similar enough. Don't be a fool and run a random command.
|
| + return
|
| +
|
| + return commands[hamming_commands[0][1]]
|
|
|
|
|
| def CMDhelp(parser, args):
|
| @@ -2035,6 +2071,9 @@ def CMDhelp(parser, args):
|
| def GenUsage(parser, command):
|
| """Modify an OptParse object with the function's documentation."""
|
| obj = Command(command)
|
| + # Get back the real command name in case Command() guess the actual command
|
| + # name.
|
| + command = obj.__name__[3:]
|
| more = getattr(obj, 'usage_more', '')
|
| if command == 'help':
|
| command = '<command>'
|
| @@ -2058,9 +2097,13 @@ def main(argv):
|
| settings = Settings()
|
|
|
| # Do it late so all commands are listed.
|
| - CMDhelp.usage_more = ('\n\nCommands are:\n' + '\n'.join([
|
| - ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
|
| - for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
|
| + commands = Commands()
|
| + length = max(len(c) for c in commands)
|
| + docs = sorted(
|
| + (name, handler.__doc__.split('\n')[0].strip())
|
| + for name, handler in commands.iteritems())
|
| + CMDhelp.usage_more = ('\n\nCommands are:\n' + '\n'.join(
|
| + ' %-*s %s' % (length, name, doc) for name, doc in docs))
|
|
|
| # Create the option parse and add --verbose support.
|
| parser = optparse.OptionParser()
|
|
|