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

Side by Side Diff: third_party/logilab/common/clcommands.py

Issue 10447014: Add pylint to depot_tools. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Fix unittests. Created 8 years, 6 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 unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « third_party/logilab/common/changelog.py ('k') | third_party/logilab/common/cli.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
3 #
4 # This file is part of logilab-common.
5 #
6 # logilab-common is free software: you can redistribute it and/or modify it unde r
7 # the terms of the GNU Lesser General Public License as published by the Free
8 # Software Foundation, either version 2.1 of the License, or (at your option) an y
9 # later version.
10 #
11 # logilab-common is distributed in the hope that it will be useful, but WITHOUT
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
14 # details.
15 #
16 # You should have received a copy of the GNU Lesser General Public License along
17 # with logilab-common. If not, see <http://www.gnu.org/licenses/>.
18 """Helper functions to support command line tools providing more than
19 one command.
20
21 e.g called as "tool command [options] args..." where <options> and <args> are
22 command'specific
23 """
24
25 __docformat__ = "restructuredtext en"
26
27 import sys
28 import logging
29 from os.path import basename
30
31 from logilab.common.configuration import Configuration
32 from logilab.common.logging_ext import init_log, get_threshold
33 from logilab.common.deprecation import deprecated
34
35
36 class BadCommandUsage(Exception):
37 """Raised when an unknown command is used or when a command is not
38 correctly used (bad options, too much / missing arguments...).
39
40 Trigger display of command usage.
41 """
42
43 class CommandError(Exception):
44 """Raised when a command can't be processed and we want to display it and
45 exit, without traceback nor usage displayed.
46 """
47
48
49 # command line access point ####################################################
50
51 class CommandLine(dict):
52 """Usage:
53
54 >>> LDI = cli.CommandLine('ldi', doc='Logilab debian installer',
55 version=version, rcfile=RCFILE)
56 >>> LDI.register(MyCommandClass)
57 >>> LDI.register(MyOtherCommandClass)
58 >>> LDI.run(sys.argv[1:])
59
60 Arguments:
61
62 * `pgm`, the program name, default to `basename(sys.argv[0])`
63
64 * `doc`, a short description of the command line tool
65
66 * `copyright`, additional doc string that will be appended to the generated
67 doc
68
69 * `version`, version number of string of the tool. If specified, global
70 --version option will be available.
71
72 * `rcfile`, path to a configuration file. If specified, global --C/--rc-file
73 option will be available? self.rcfile = rcfile
74
75 * `logger`, logger to propagate to commands, default to
76 `logging.getLogger(self.pgm))`
77 """
78 def __init__(self, pgm=None, doc=None, copyright=None, version=None,
79 rcfile=None, logthreshold=logging.ERROR,
80 check_duplicated_command=True):
81 if pgm is None:
82 pgm = basename(sys.argv[0])
83 self.pgm = pgm
84 self.doc = doc
85 self.copyright = copyright
86 self.version = version
87 self.rcfile = rcfile
88 self.logger = None
89 self.logthreshold = logthreshold
90 self.check_duplicated_command = check_duplicated_command
91
92 def register(self, cls, force=False):
93 """register the given :class:`Command` subclass"""
94 assert not self.check_duplicated_command or force or not cls.name in sel f, \
95 'a command %s is already defined' % cls.name
96 self[cls.name] = cls
97 return cls
98
99 def run(self, args):
100 """main command line access point:
101 * init logging
102 * handle global options (-h/--help, --version, -C/--rc-file)
103 * check command
104 * run command
105
106 Terminate by :exc:`SystemExit`
107 """
108 init_log(debug=True, # so that we use StreamHandler
109 logthreshold=self.logthreshold,
110 logformat='%(levelname)s: %(message)s')
111 try:
112 arg = args.pop(0)
113 except IndexError:
114 self.usage_and_exit(1)
115 if arg in ('-h', '--help'):
116 self.usage_and_exit(0)
117 if self.version is not None and arg in ('--version'):
118 print self.version
119 sys.exit(0)
120 rcfile = self.rcfile
121 if rcfile is not None and arg in ('-C', '--rc-file'):
122 try:
123 rcfile = args.pop(0)
124 arg = args.pop(0)
125 except IndexError:
126 self.usage_and_exit(1)
127 try:
128 command = self.get_command(arg)
129 except KeyError:
130 print 'ERROR: no %s command' % arg
131 print
132 self.usage_and_exit(1)
133 try:
134 sys.exit(command.main_run(args, rcfile))
135 except KeyboardInterrupt, exc:
136 print 'Interrupted',
137 if str(exc):
138 print ': %s' % exc,
139 print
140 sys.exit(4)
141 except BadCommandUsage, err:
142 print 'ERROR:', err
143 print
144 print command.help()
145 sys.exit(1)
146
147 def create_logger(self, handler, logthreshold=None):
148 logger = logging.Logger(self.pgm)
149 logger.handlers = [handler]
150 if logthreshold is None:
151 logthreshold = get_threshold(self.logthreshold)
152 logger.setLevel(logthreshold)
153 return logger
154
155 def get_command(self, cmd, logger=None):
156 if logger is None:
157 logger = self.logger
158 if logger is None:
159 logger = self.logger = logging.getLogger(self.pgm)
160 logger.setLevel(get_threshold(self.logthreshold))
161 return self[cmd](logger)
162
163 def usage(self):
164 """display usage for the main program (i.e. when no command supplied)
165 and exit
166 """
167 print 'usage:', self.pgm,
168 if self.rcfile:
169 print '[--rc-file=<configuration file>]',
170 print '<command> [options] <command argument>...'
171 if self.doc:
172 print '\n%s' % self.doc
173 print '''
174 Type "%(pgm)s <command> --help" for more information about a specific
175 command. Available commands are :\n''' % self.__dict__
176 max_len = max([len(cmd) for cmd in self])
177 padding = ' ' * max_len
178 for cmdname, cmd in sorted(self.items()):
179 if not cmd.hidden:
180 print ' ', (cmdname + padding)[:max_len], cmd.short_description( )
181 if self.rcfile:
182 print '''
183 Use --rc-file=<configuration file> / -C <configuration file> before the command
184 to specify a configuration file. Default to %s.
185 ''' % self.rcfile
186 print '''%(pgm)s -h/--help
187 display this usage information and exit''' % self.__dict__
188 if self.version:
189 print '''%(pgm)s -v/--version
190 display version configuration and exit''' % self.__dict__
191 if self.copyright:
192 print '\n', self.copyright
193
194 def usage_and_exit(self, status):
195 self.usage()
196 sys.exit(status)
197
198
199 # base command classes #########################################################
200
201 class Command(Configuration):
202 """Base class for command line commands.
203
204 Class attributes:
205
206 * `name`, the name of the command
207
208 * `min_args`, minimum number of arguments, None if unspecified
209
210 * `max_args`, maximum number of arguments, None if unspecified
211
212 * `arguments`, string describing arguments, used in command usage
213
214 * `hidden`, boolean flag telling if the command should be hidden, e.g. does
215 not appear in help's commands list
216
217 * `options`, options list, as allowed by :mod:configuration
218 """
219
220 arguments = ''
221 name = ''
222 # hidden from help ?
223 hidden = False
224 # max/min args, None meaning unspecified
225 min_args = None
226 max_args = None
227
228 @classmethod
229 def description(cls):
230 return cls.__doc__.replace(' ', '')
231
232 @classmethod
233 def short_description(cls):
234 return cls.description().split('.')[0]
235
236 def __init__(self, logger):
237 usage = '%%prog %s %s\n\n%s' % (self.name, self.arguments,
238 self.description())
239 Configuration.__init__(self, usage=usage)
240 self.logger = logger
241
242 def check_args(self, args):
243 """check command's arguments are provided"""
244 if self.min_args is not None and len(args) < self.min_args:
245 raise BadCommandUsage('missing argument')
246 if self.max_args is not None and len(args) > self.max_args:
247 raise BadCommandUsage('too many arguments')
248
249 def main_run(self, args, rcfile=None):
250 """Run the command and return status 0 if everything went fine.
251
252 If :exc:`CommandError` is raised by the underlying command, simply log
253 the error and return status 2.
254
255 Any other exceptions, including :exc:`BadCommandUsage` will be
256 propagated.
257 """
258 if rcfile:
259 self.load_file_configuration(rcfile)
260 args = self.load_command_line_configuration(args)
261 try:
262 self.check_args(args)
263 self.run(args)
264 except CommandError, err:
265 self.logger.error(err)
266 return 2
267 return 0
268
269 def run(self, args):
270 """run the command with its specific arguments"""
271 raise NotImplementedError()
272
273
274 class ListCommandsCommand(Command):
275 """list available commands, useful for bash completion."""
276 name = 'listcommands'
277 arguments = '[command]'
278 hidden = True
279
280 def run(self, args):
281 """run the command with its specific arguments"""
282 if args:
283 command = args.pop()
284 cmd = _COMMANDS[command]
285 for optname, optdict in cmd.options:
286 print '--help'
287 print '--' + optname
288 else:
289 commands = sorted(_COMMANDS.keys())
290 for command in commands:
291 cmd = _COMMANDS[command]
292 if not cmd.hidden:
293 print command
294
295
296 # deprecated stuff #############################################################
297
298 _COMMANDS = CommandLine()
299
300 DEFAULT_COPYRIGHT = '''\
301 Copyright (c) 2004-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
302 http://www.logilab.fr/ -- mailto:contact@logilab.fr'''
303
304 @deprecated('use cls.register(cli)')
305 def register_commands(commands):
306 """register existing commands"""
307 for command_klass in commands:
308 _COMMANDS.register(command_klass)
309
310 @deprecated('use args.pop(0)')
311 def main_run(args, doc=None, copyright=None, version=None):
312 """command line tool: run command specified by argument list (without the
313 program name). Raise SystemExit with status 0 if everything went fine.
314
315 >>> main_run(sys.argv[1:])
316 """
317 _COMMANDS.doc = doc
318 _COMMANDS.copyright = copyright
319 _COMMANDS.version = version
320 _COMMANDS.run(args)
321
322 @deprecated('use args.pop(0)')
323 def pop_arg(args_list, expected_size_after=None, msg="Missing argument"):
324 """helper function to get and check command line arguments"""
325 try:
326 value = args_list.pop(0)
327 except IndexError:
328 raise BadCommandUsage(msg)
329 if expected_size_after is not None and len(args_list) > expected_size_after:
330 raise BadCommandUsage('too many arguments')
331 return value
332
OLDNEW
« no previous file with comments | « third_party/logilab/common/changelog.py ('k') | third_party/logilab/common/cli.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698