Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1)

Unified Diff: third_party/depot_tools/subcommand.py

Issue 22902007: Switch trace_inputs.py and isolate.py to subcommand.py. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/swarm_client
Patch Set: Now works Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/depot_tools/subcommand.py
diff --git a/third_party/depot_tools/subcommand.py b/third_party/depot_tools/subcommand.py
new file mode 100644
index 0000000000000000000000000000000000000000..6afe3dcda1d31b458174f97c491dcf4ab0fb68d7
--- /dev/null
+++ b/third_party/depot_tools/subcommand.py
@@ -0,0 +1,251 @@
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Manages subcommands in a script.
+
+Each subcommand should look like this:
+ @usage('[pet name]')
+ def CMDpet(parser, args):
+ '''Prints a pet.
+
+ Many people likes pet. This command prints a pet for your pleasure.
+ '''
+ parser.add_option('--color', help='color of your pet')
+ options, args = parser.parse_args(args)
+ if len(args) != 1:
+ parser.error('A pet name is required')
+ pet = args[0]
+ if options.color:
+ print('Nice %s %d' % (options.color, pet))
+ else:
+ print('Nice %s' % pet)
+ return 0
+
+Explanation:
+ - usage decorator alters the 'usage: %prog' line in the command's help.
+ - docstring is used to both short help line and long help line.
+ - parser can be augmented with arguments.
+ - return the exit code.
+ - Every function in the specified module with a name starting with 'CMD' will
+ be a subcommand.
+ - The module's docstring will be used in the default 'help' page.
+ - If a command has no docstring, it will not be listed in the 'help' page.
+ Useful to keep compatibility commands around or aliases.
+ - If a command is an alias to another one, it won't be documented. E.g.:
+ CMDoldname = CMDnewcmd
+ will result in oldname not being documented but supported and redirecting to
+ newcmd. Make it a real function that calls the old function if you want it
+ to be documented.
+"""
+
+import difflib
+import sys
+import textwrap
+
+
+def usage(more):
+ """Adds a 'usage_more' property to a CMD function."""
+ def hook(fn):
+ fn.usage_more = more
+ return fn
+ return hook
+
+
+def epilog(text):
+ """Adds an 'epilog' property to a CMD function.
+
+ It will be shown in the epilog. Usually useful for examples.
+ """
+ def hook(fn):
+ fn.epilog = text
+ return fn
+ return hook
+
+
+def CMDhelp(parser, args):
+ """Prints list of commands or help for a specific command."""
+ # This is the default help implementation. It can be disabled or overriden if
+ # wanted.
+ if not any(i in ('-h', '--help') for i in args):
+ args = args + ['--help']
+ _, args = parser.parse_args(args)
+ # Never gets there.
+ assert False
+
+
+def _get_color_module():
+ """Returns the colorama module if available.
+
+ If so, assumes colors are supported and return the module handle.
+ """
+ return sys.modules.get('colorama') or sys.modules.get('third_party.colorama')
+
+
+class CommandDispatcher(object):
+ def __init__(self, module):
+ """module is the name of the main python module where to look for commands.
+
+ The python builtin variable __name__ MUST be used for |module|. If the
+ script is executed in the form 'python script.py', __name__ == '__main__'
+ and sys.modules['script'] doesn't exist. On the other hand if it is unit
+ tested, __main__ will be the unit test's module so it has to reference to
+ itself with 'script'. __name__ always match the right value.
+ """
+ self.module = sys.modules[module]
+
+ def enumerate_commands(self):
+ """Returns a dict of command and their handling function.
+
+ The commands must be in the '__main__' modules. To import a command from a
+ submodule, use:
+ from mysubcommand import CMDfoo
+
+ Automatically adds 'help' if not already defined.
+
+ A command can be effectively disabled by defining a global variable to None,
+ e.g.:
+ CMDhelp = None
+ """
+ cmds = dict(
+ (fn[3:], getattr(self.module, fn))
+ for fn in dir(self.module) if fn.startswith('CMD'))
+ cmds.setdefault('help', CMDhelp)
+ return cmds
+
+ def find_nearest_command(self, name):
+ """Retrieves the function to handle a command.
+
+ It automatically tries to guess the intended command by handling typos or
+ incomplete names.
+ """
+ commands = self.enumerate_commands()
+ if name in commands:
+ return commands[name]
+
+ # An exact match was not found. 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 _gen_commands_list(self):
+ """Generates the short list of supported commands."""
+ commands = self.enumerate_commands()
+ docs = sorted(
+ (name, self._create_command_summary(name, handler))
+ for name, handler in commands.iteritems())
+ # Skip commands without a docstring.
+ docs = [i for i in docs if i[1]]
+ # Then calculate maximum length for alignment:
+ length = max(len(c) for c in commands)
+
+ # Look if color is supported.
+ colors = _get_color_module()
+ green = reset = ''
+ if colors:
+ green = colors.Fore.GREEN
+ reset = colors.Fore.RESET
+ return (
+ 'Commands are:\n' +
+ ''.join(
+ ' %s%-*s%s %s\n' % (green, length, name, reset, doc)
+ for name, doc in docs))
+
+ def _add_command_usage(self, parser, command):
+ """Modifies an OptionParser object with the function's documentation."""
+ name = command.__name__[3:]
+ if name == 'help':
+ name = '<command>'
+ # Use the module's docstring as the description for the 'help' command if
+ # available.
+ parser.description = (self.module.__doc__ or '').rstrip()
+ if parser.description:
+ parser.description += '\n\n'
+ parser.description += self._gen_commands_list()
+ # Do not touch epilog.
+ else:
+ # Use the command's docstring if available. For commands, unlike module
+ # docstring, realign.
+ lines = (command.__doc__ or '').rstrip().splitlines()
+ if lines[:1]:
+ rest = textwrap.dedent('\n'.join(lines[1:]))
+ parser.description = '\n'.join((lines[0], rest))
+ else:
+ parser.description = lines[0]
+ if parser.description:
+ parser.description += '\n'
+ parser.epilog = getattr(command, 'epilog', None)
+ if parser.epilog:
+ parser.epilog = '\n' + parser.epilog.strip() + '\n'
+
+ more = getattr(command, 'usage_more', '')
+ parser.set_usage(
+ 'usage: %%prog %s [options]%s' % (name, '' if not more else ' ' + more))
+
+ @staticmethod
+ def _create_command_summary(name, command):
+ """Creates a oneline summary from the command's docstring."""
+ if name != command.__name__[3:]:
+ # Skip aliases.
+ return ''
+ doc = command.__doc__ or ''
+ line = doc.split('\n', 1)[0].rstrip('.')
+ if not line:
+ return line
+ return (line[0].lower() + line[1:]).strip()
+
+ def execute(self, parser, args):
+ """Dispatches execution to the right command.
+
+ Fallbacks to 'help' if not disabled.
+ """
+ # Unconditionally disable format_description() and format_epilog().
+ # Technically, a formatter should be used but it's not worth (yet) the
+ # trouble.
+ parser.format_description = lambda _: parser.description or ''
+ parser.format_epilog = lambda _: parser.epilog or ''
+
+ if args:
+ if args[0] in ('-h', '--help') and len(args) > 1:
+ # Inverse the argument order so 'tool --help cmd' is rewritten to
+ # 'tool cmd --help'.
+ args = [args[1], args[0]] + args[2:]
+ command = self.find_nearest_command(args[0])
+ if command:
+ if command.__name__ == 'CMDhelp' and len(args) > 1:
+ # Inverse the arguments order so 'tool help cmd' is rewritten to
+ # 'tool cmd --help'. Do it here since we want 'tool hel cmd' to work
+ # too.
+ args = [args[1], '--help'] + args[2:]
+ command = self.find_nearest_command(args[0]) or command
+
+ # "fix" the usage and the description now that we know the subcommand.
+ self._add_command_usage(parser, command)
+ return command(parser, args[1:])
+
+ cmdhelp = self.enumerate_commands().get('help')
+ if cmdhelp:
+ # Not a known command. Default to help.
+ self._add_command_usage(parser, cmdhelp)
+ return cmdhelp(parser, args)
+
+ # Nothing can be done.
+ return 2

Powered by Google App Engine
This is Rietveld 408576698