Split generic subcommand code off its own module.
Use the code in git_cl.py, since it was the more evolved. Add documentation
and clean up the structure along the way.
This makes it possible to easily reuse the generic subcommand handling code.
As a first step, only git_cl.py is using it. Eventually, gclient and gcl could
be switch over.
R=iannucci@chromium.org
BUG=
Review URL: https://chromiumcodereview.appspot.com/23250002
git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@218072 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/git_cl.py b/git_cl.py
index d833650..eb6594d 100755
--- a/git_cl.py
+++ b/git_cl.py
@@ -7,7 +7,6 @@
"""A git-command for integrating reviews on Rietveld."""
-import difflib
from distutils.version import LooseVersion
import json
import logging
@@ -36,9 +35,11 @@
import presubmit_support
import rietveld
import scm
+import subcommand
import subprocess2
import watchlists
+__version__ = '1.0'
DEFAULT_SERVER = 'https://codereview.appspot.com'
POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s'
@@ -98,13 +99,6 @@
LooseVersion(version[len(prefix):]) >= LooseVersion(min_version))
-def usage(more):
- def hook(fn):
- fn.usage_more = more
- return fn
- return hook
-
-
def ask_for_data(prompt):
try:
return raw_input(prompt)
@@ -1031,7 +1025,7 @@
DieWithError('\nFailed to download hooks from %s' % src)
-@usage('[repo root containing codereview.settings]')
+@subcommand.usage('[repo root containing codereview.settings]')
def CMDconfig(parser, args):
"""Edits configuration for this tree."""
@@ -1189,7 +1183,7 @@
return 0
-@usage('[issue_number]')
+@subcommand.usage('[issue_number]')
def CMDissue(parser, args):
"""Sets or displays the current code review issue number.
@@ -1448,7 +1442,7 @@
return sorted(filter(None, stripped_items))
-@usage('[args to "git diff"]')
+@subcommand.usage('[args to "git diff"]')
def CMDupload(parser, args):
"""Uploads the current changelist to codereview."""
parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks',
@@ -1772,7 +1766,7 @@
return 0
-@usage('[upstream branch to apply against]')
+@subcommand.usage('[upstream branch to apply against]')
def CMDdcommit(parser, args):
"""Commits the current changelist via git-svn."""
if not settings.GetIsGitSvn():
@@ -1788,7 +1782,7 @@
return SendUpstream(parser, args, 'dcommit')
-@usage('[upstream branch to apply against]')
+@subcommand.usage('[upstream branch to apply against]')
def CMDpush(parser, args):
"""Commits the current changelist via git."""
if settings.GetIsGitSvn():
@@ -1798,7 +1792,7 @@
return SendUpstream(parser, args, 'push')
-@usage('<patch url or issue id>')
+@subcommand.usage('<patch url or issue id>')
def CMDpatch(parser, args):
"""Patchs in a code review."""
parser.add_option('-b', dest='newbranch',
@@ -2045,7 +2039,7 @@
return 0
-@usage('[new upstream branch]')
+@subcommand.usage('[new upstream branch]')
def CMDupstream(parser, args):
"""Prints or sets the name of the upstream branch, if any."""
_, args = parser.parse_args(args)
@@ -2147,72 +2141,11 @@
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):
- """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):
- """Prints list of commands or help for a specific command."""
- _, args = parser.parse_args(args)
- if len(args) == 1:
- return main(args + ['--help'])
- parser.print_help()
- return 0
-
-
-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>'
- else:
- parser.description = obj.__doc__
- parser.set_usage('usage: %%prog %s [options] %s' % (command, more))
-
-
class OptionParser(optparse.OptionParser):
"""Creates the option parse and add --verbose support."""
def __init__(self, *args, **kwargs):
- optparse.OptionParser.__init__(self, *args, **kwargs)
+ optparse.OptionParser.__init__(
+ self, *args, prog='git cl', version=__version__, **kwargs)
self.add_option(
'-v', '--verbose', action='count', default=0,
help='Use 2 times for more debugging info')
@@ -2232,8 +2165,6 @@
def main(argv):
- """Doesn't parse the arguments here, just find the right subcommand to
- execute."""
if sys.hexversion < 0x02060000:
print >> sys.stderr, (
'\nYour python version %s is unsupported, please upgrade.\n' %
@@ -2244,39 +2175,15 @@
global settings
settings = Settings()
- # Do it late so all commands are listed.
- commands = Commands()
- length = max(len(c) for c in commands)
-
- def gen_summary(x):
- """Creates a oneline summary from the docstring."""
- line = x.split('\n', 1)[0].rstrip('.')
- return line[0].lower() + line[1:]
-
- docs = sorted(
- (name, gen_summary(handler.__doc__).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))
-
- parser = OptionParser()
- if argv:
- command = Command(argv[0])
- if command:
- # "fix" the usage and the description now that we know the subcommand.
- GenUsage(parser, argv[0])
- try:
- return command(parser, argv[1:])
- except urllib2.HTTPError, e:
- if e.code != 500:
- raise
- DieWithError(
- ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith '
- 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e)))
-
- # Not a known command. Default to help.
- GenUsage(parser, 'help')
- return CMDhelp(parser, argv)
+ dispatcher = subcommand.CommandDispatcher(__name__)
+ try:
+ return dispatcher.execute(OptionParser(), argv)
+ except urllib2.HTTPError, e:
+ if e.code != 500:
+ raise
+ DieWithError(
+ ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith '
+ 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e)))
if __name__ == '__main__':