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

Side by Side Diff: swarm_client/third_party/depot_tools/subcommand.py

Issue 69143004: Delete swarm_client. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/
Patch Set: Created 7 years, 1 month 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 # Copyright 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """Manages subcommands in a script.
6
7 Each subcommand should look like this:
8 @usage('[pet name]')
9 def CMDpet(parser, args):
10 '''Prints a pet.
11
12 Many people likes pet. This command prints a pet for your pleasure.
13 '''
14 parser.add_option('--color', help='color of your pet')
15 options, args = parser.parse_args(args)
16 if len(args) != 1:
17 parser.error('A pet name is required')
18 pet = args[0]
19 if options.color:
20 print('Nice %s %d' % (options.color, pet))
21 else:
22 print('Nice %s' % pet)
23 return 0
24
25 Explanation:
26 - usage decorator alters the 'usage: %prog' line in the command's help.
27 - docstring is used to both short help line and long help line.
28 - parser can be augmented with arguments.
29 - return the exit code.
30 - Every function in the specified module with a name starting with 'CMD' will
31 be a subcommand.
32 - The module's docstring will be used in the default 'help' page.
33 - If a command has no docstring, it will not be listed in the 'help' page.
34 Useful to keep compatibility commands around or aliases.
35 - If a command is an alias to another one, it won't be documented. E.g.:
36 CMDoldname = CMDnewcmd
37 will result in oldname not being documented but supported and redirecting to
38 newcmd. Make it a real function that calls the old function if you want it
39 to be documented.
40 """
41
42 import difflib
43 import sys
44 import textwrap
45
46
47 def usage(more):
48 """Adds a 'usage_more' property to a CMD function."""
49 def hook(fn):
50 fn.usage_more = more
51 return fn
52 return hook
53
54
55 def epilog(text):
56 """Adds an 'epilog' property to a CMD function.
57
58 It will be shown in the epilog. Usually useful for examples.
59 """
60 def hook(fn):
61 fn.epilog = text
62 return fn
63 return hook
64
65
66 def CMDhelp(parser, args):
67 """Prints list of commands or help for a specific command."""
68 # This is the default help implementation. It can be disabled or overriden if
69 # wanted.
70 if not any(i in ('-h', '--help') for i in args):
71 args = args + ['--help']
72 _, args = parser.parse_args(args)
73 # Never gets there.
74 assert False
75
76
77 def _get_color_module():
78 """Returns the colorama module if available.
79
80 If so, assumes colors are supported and return the module handle.
81 """
82 return sys.modules.get('colorama') or sys.modules.get('third_party.colorama')
83
84
85 class CommandDispatcher(object):
86 def __init__(self, module):
87 """module is the name of the main python module where to look for commands.
88
89 The python builtin variable __name__ MUST be used for |module|. If the
90 script is executed in the form 'python script.py', __name__ == '__main__'
91 and sys.modules['script'] doesn't exist. On the other hand if it is unit
92 tested, __main__ will be the unit test's module so it has to reference to
93 itself with 'script'. __name__ always match the right value.
94 """
95 self.module = sys.modules[module]
96
97 def enumerate_commands(self):
98 """Returns a dict of command and their handling function.
99
100 The commands must be in the '__main__' modules. To import a command from a
101 submodule, use:
102 from mysubcommand import CMDfoo
103
104 Automatically adds 'help' if not already defined.
105
106 A command can be effectively disabled by defining a global variable to None,
107 e.g.:
108 CMDhelp = None
109 """
110 cmds = dict(
111 (fn[3:], getattr(self.module, fn))
112 for fn in dir(self.module) if fn.startswith('CMD'))
113 cmds.setdefault('help', CMDhelp)
114 return cmds
115
116 def find_nearest_command(self, name):
117 """Retrieves the function to handle a command.
118
119 It automatically tries to guess the intended command by handling typos or
120 incomplete names.
121 """
122 commands = self.enumerate_commands()
123 if name in commands:
124 return commands[name]
125
126 # An exact match was not found. Try to be smart and look if there's
127 # something similar.
128 commands_with_prefix = [c for c in commands if c.startswith(name)]
129 if len(commands_with_prefix) == 1:
130 return commands[commands_with_prefix[0]]
131
132 # A #closeenough approximation of levenshtein distance.
133 def close_enough(a, b):
134 return difflib.SequenceMatcher(a=a, b=b).ratio()
135
136 hamming_commands = sorted(
137 ((close_enough(c, name), c) for c in commands),
138 reverse=True)
139 if (hamming_commands[0][0] - hamming_commands[1][0]) < 0.3:
140 # Too ambiguous.
141 return
142
143 if hamming_commands[0][0] < 0.8:
144 # Not similar enough. Don't be a fool and run a random command.
145 return
146
147 return commands[hamming_commands[0][1]]
148
149 def _gen_commands_list(self):
150 """Generates the short list of supported commands."""
151 commands = self.enumerate_commands()
152 docs = sorted(
153 (name, self._create_command_summary(name, handler))
154 for name, handler in commands.iteritems())
155 # Skip commands without a docstring.
156 docs = [i for i in docs if i[1]]
157 # Then calculate maximum length for alignment:
158 length = max(len(c) for c in commands)
159
160 # Look if color is supported.
161 colors = _get_color_module()
162 green = reset = ''
163 if colors:
164 green = colors.Fore.GREEN
165 reset = colors.Fore.RESET
166 return (
167 'Commands are:\n' +
168 ''.join(
169 ' %s%-*s%s %s\n' % (green, length, name, reset, doc)
170 for name, doc in docs))
171
172 def _add_command_usage(self, parser, command):
173 """Modifies an OptionParser object with the function's documentation."""
174 name = command.__name__[3:]
175 if name == 'help':
176 name = '<command>'
177 # Use the module's docstring as the description for the 'help' command if
178 # available.
179 parser.description = (self.module.__doc__ or '').rstrip()
180 if parser.description:
181 parser.description += '\n\n'
182 parser.description += self._gen_commands_list()
183 # Do not touch epilog.
184 else:
185 # Use the command's docstring if available. For commands, unlike module
186 # docstring, realign.
187 lines = (command.__doc__ or '').rstrip().splitlines()
188 if lines[:1]:
189 rest = textwrap.dedent('\n'.join(lines[1:]))
190 parser.description = '\n'.join((lines[0], rest))
191 else:
192 parser.description = lines[0]
193 if parser.description:
194 parser.description += '\n'
195 parser.epilog = getattr(command, 'epilog', None)
196 if parser.epilog:
197 parser.epilog = '\n' + parser.epilog.strip() + '\n'
198
199 more = getattr(command, 'usage_more', '')
200 parser.set_usage(
201 'usage: %%prog %s [options]%s' % (name, '' if not more else ' ' + more))
202
203 @staticmethod
204 def _create_command_summary(name, command):
205 """Creates a oneline summary from the command's docstring."""
206 if name != command.__name__[3:]:
207 # Skip aliases.
208 return ''
209 doc = command.__doc__ or ''
210 line = doc.split('\n', 1)[0].rstrip('.')
211 if not line:
212 return line
213 return (line[0].lower() + line[1:]).strip()
214
215 def execute(self, parser, args):
216 """Dispatches execution to the right command.
217
218 Fallbacks to 'help' if not disabled.
219 """
220 # Unconditionally disable format_description() and format_epilog().
221 # Technically, a formatter should be used but it's not worth (yet) the
222 # trouble.
223 parser.format_description = lambda _: parser.description or ''
224 parser.format_epilog = lambda _: parser.epilog or ''
225
226 if args:
227 if args[0] in ('-h', '--help') and len(args) > 1:
228 # Inverse the argument order so 'tool --help cmd' is rewritten to
229 # 'tool cmd --help'.
230 args = [args[1], args[0]] + args[2:]
231 command = self.find_nearest_command(args[0])
232 if command:
233 if command.__name__ == 'CMDhelp' and len(args) > 1:
234 # Inverse the arguments order so 'tool help cmd' is rewritten to
235 # 'tool cmd --help'. Do it here since we want 'tool hel cmd' to work
236 # too.
237 args = [args[1], '--help'] + args[2:]
238 command = self.find_nearest_command(args[0]) or command
239
240 # "fix" the usage and the description now that we know the subcommand.
241 self._add_command_usage(parser, command)
242 return command(parser, args[1:])
243
244 cmdhelp = self.enumerate_commands().get('help')
245 if cmdhelp:
246 # Not a known command. Default to help.
247 self._add_command_usage(parser, cmdhelp)
248 return cmdhelp(parser, args)
249
250 # Nothing can be done.
251 return 2
OLDNEW
« no previous file with comments | « swarm_client/third_party/depot_tools/fix_encoding.py ('k') | swarm_client/third_party/requests/HISTORY.rst » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698