|
1 """ CommandLine - Get and parse command line options |
|
2 |
|
3 NOTE: This still is very much work in progress !!! |
|
4 |
|
5 Different version are likely to be incompatible. |
|
6 |
|
7 TODO: |
|
8 |
|
9 * Incorporate the changes made by (see Inbox) |
|
10 * Add number range option using srange() |
|
11 |
|
12 """ |
|
13 |
|
14 __copyright__ = """\ |
|
15 Copyright (c), 1997-2006, Marc-Andre Lemburg (mal@lemburg.com) |
|
16 Copyright (c), 2000-2006, eGenix.com Software GmbH (info@egenix.com) |
|
17 See the documentation for further information on copyrights, |
|
18 or contact the author. All Rights Reserved. |
|
19 """ |
|
20 |
|
21 __version__ = '1.2' |
|
22 |
|
23 import sys, getopt, string, glob, os, re, exceptions, traceback |
|
24 |
|
25 ### Helpers |
|
26 |
|
27 def _getopt_flags(options): |
|
28 |
|
29 """ Convert the option list to a getopt flag string and long opt |
|
30 list |
|
31 |
|
32 """ |
|
33 s = [] |
|
34 l = [] |
|
35 for o in options: |
|
36 if o.prefix == '-': |
|
37 # short option |
|
38 s.append(o.name) |
|
39 if o.takes_argument: |
|
40 s.append(':') |
|
41 else: |
|
42 # long option |
|
43 if o.takes_argument: |
|
44 l.append(o.name+'=') |
|
45 else: |
|
46 l.append(o.name) |
|
47 return string.join(s,''),l |
|
48 |
|
49 def invisible_input(prompt='>>> '): |
|
50 |
|
51 """ Get raw input from a terminal without echoing the characters to |
|
52 the terminal, e.g. for password queries. |
|
53 |
|
54 """ |
|
55 import getpass |
|
56 entry = getpass.getpass(prompt) |
|
57 if entry is None: |
|
58 raise KeyboardInterrupt |
|
59 return entry |
|
60 |
|
61 def fileopen(name, mode='wb', encoding=None): |
|
62 |
|
63 """ Open a file using mode. |
|
64 |
|
65 Default mode is 'wb' meaning to open the file for writing in |
|
66 binary mode. If encoding is given, I/O to and from the file is |
|
67 transparently encoded using the given encoding. |
|
68 |
|
69 Files opened for writing are chmod()ed to 0600. |
|
70 |
|
71 """ |
|
72 if name == 'stdout': |
|
73 return sys.stdout |
|
74 elif name == 'stderr': |
|
75 return sys.stderr |
|
76 elif name == 'stdin': |
|
77 return sys.stdin |
|
78 else: |
|
79 if encoding is not None: |
|
80 import codecs |
|
81 f = codecs.open(name, mode, encoding) |
|
82 else: |
|
83 f = open(name, mode) |
|
84 if 'w' in mode: |
|
85 os.chmod(name, 0600) |
|
86 return f |
|
87 |
|
88 def option_dict(options): |
|
89 |
|
90 """ Return a dictionary mapping option names to Option instances. |
|
91 """ |
|
92 d = {} |
|
93 for option in options: |
|
94 d[option.name] = option |
|
95 return d |
|
96 |
|
97 # Alias |
|
98 getpasswd = invisible_input |
|
99 |
|
100 _integerRE = re.compile('\s*(-?\d+)\s*$') |
|
101 _integerRangeRE = re.compile('\s*(-?\d+)\s*-\s*(-?\d+)\s*$') |
|
102 |
|
103 def srange(s, |
|
104 |
|
105 split=string.split,integer=_integerRE, |
|
106 integerRange=_integerRangeRE): |
|
107 |
|
108 """ Converts a textual representation of integer numbers and ranges |
|
109 to a Python list. |
|
110 |
|
111 Supported formats: 2,3,4,2-10,-1 - -3, 5 - -2 |
|
112 |
|
113 Values are appended to the created list in the order specified |
|
114 in the string. |
|
115 |
|
116 """ |
|
117 l = [] |
|
118 append = l.append |
|
119 for entry in split(s,','): |
|
120 m = integer.match(entry) |
|
121 if m: |
|
122 append(int(m.groups()[0])) |
|
123 continue |
|
124 m = integerRange.match(entry) |
|
125 if m: |
|
126 start,end = map(int,m.groups()) |
|
127 l[len(l):] = range(start,end+1) |
|
128 return l |
|
129 |
|
130 def abspath(path, |
|
131 |
|
132 expandvars=os.path.expandvars,expanduser=os.path.expanduser, |
|
133 join=os.path.join,getcwd=os.getcwd): |
|
134 |
|
135 """ Return the corresponding absolute path for path. |
|
136 |
|
137 path is expanded in the usual shell ways before |
|
138 joining it with the current working directory. |
|
139 |
|
140 """ |
|
141 try: |
|
142 path = expandvars(path) |
|
143 except AttributeError: |
|
144 pass |
|
145 try: |
|
146 path = expanduser(path) |
|
147 except AttributeError: |
|
148 pass |
|
149 return join(getcwd(), path) |
|
150 |
|
151 ### Option classes |
|
152 |
|
153 class Option: |
|
154 |
|
155 """ Option base class. Takes no argument. |
|
156 |
|
157 """ |
|
158 default = None |
|
159 helptext = '' |
|
160 prefix = '-' |
|
161 takes_argument = 0 |
|
162 has_default = 0 |
|
163 tab = 15 |
|
164 |
|
165 def __init__(self,name,help=None): |
|
166 |
|
167 if not name[:1] == '-': |
|
168 raise TypeError,'option names must start with "-"' |
|
169 if name[1:2] == '-': |
|
170 self.prefix = '--' |
|
171 self.name = name[2:] |
|
172 else: |
|
173 self.name = name[1:] |
|
174 if help: |
|
175 self.help = help |
|
176 |
|
177 def __str__(self): |
|
178 |
|
179 o = self |
|
180 name = o.prefix + o.name |
|
181 if o.takes_argument: |
|
182 name = name + ' arg' |
|
183 if len(name) > self.tab: |
|
184 name = name + '\n' + ' ' * (self.tab + 1 + len(o.prefix)) |
|
185 else: |
|
186 name = '%-*s ' % (self.tab, name) |
|
187 description = o.help |
|
188 if o.has_default: |
|
189 description = description + ' (%s)' % o.default |
|
190 return '%s %s' % (name, description) |
|
191 |
|
192 class ArgumentOption(Option): |
|
193 |
|
194 """ Option that takes an argument. |
|
195 |
|
196 An optional default argument can be given. |
|
197 |
|
198 """ |
|
199 def __init__(self,name,help=None,default=None): |
|
200 |
|
201 # Basemethod |
|
202 Option.__init__(self,name,help) |
|
203 |
|
204 if default is not None: |
|
205 self.default = default |
|
206 self.has_default = 1 |
|
207 self.takes_argument = 1 |
|
208 |
|
209 class SwitchOption(Option): |
|
210 |
|
211 """ Options that can be on or off. Has an optional default value. |
|
212 |
|
213 """ |
|
214 def __init__(self,name,help=None,default=None): |
|
215 |
|
216 # Basemethod |
|
217 Option.__init__(self,name,help) |
|
218 |
|
219 if default is not None: |
|
220 self.default = default |
|
221 self.has_default = 1 |
|
222 |
|
223 ### Application baseclass |
|
224 |
|
225 class Application: |
|
226 |
|
227 """ Command line application interface with builtin argument |
|
228 parsing. |
|
229 |
|
230 """ |
|
231 # Options the program accepts (Option instances) |
|
232 options = [] |
|
233 |
|
234 # Standard settings; these are appended to options in __init__ |
|
235 preset_options = [SwitchOption('-v', |
|
236 'generate verbose output'), |
|
237 SwitchOption('-h', |
|
238 'show this help text'), |
|
239 SwitchOption('--help', |
|
240 'show this help text'), |
|
241 SwitchOption('--debug', |
|
242 'enable debugging'), |
|
243 SwitchOption('--copyright', |
|
244 'show copyright'), |
|
245 SwitchOption('--examples', |
|
246 'show examples of usage')] |
|
247 |
|
248 # The help layout looks like this: |
|
249 # [header] - defaults to '' |
|
250 # |
|
251 # [synopsis] - formatted as '<self.name> %s' % self.synopsis |
|
252 # |
|
253 # options: |
|
254 # [options] - formatted from self.options |
|
255 # |
|
256 # [version] - formatted as 'Version:\n %s' % self.version, if given |
|
257 # |
|
258 # [about] - defaults to '' |
|
259 # |
|
260 # Note: all fields that do not behave as template are formatted |
|
261 # using the instances dictionary as substitution namespace, |
|
262 # e.g. %(name)s will be replaced by the applications name. |
|
263 # |
|
264 |
|
265 # Header (default to program name) |
|
266 header = '' |
|
267 |
|
268 # Name (defaults to program name) |
|
269 name = '' |
|
270 |
|
271 # Synopsis (%(name)s is replaced by the program name) |
|
272 synopsis = '%(name)s [option] files...' |
|
273 |
|
274 # Version (optional) |
|
275 version = '' |
|
276 |
|
277 # General information printed after the possible options (optional) |
|
278 about = '' |
|
279 |
|
280 # Examples of usage to show when the --examples option is given (optional) |
|
281 examples = '' |
|
282 |
|
283 # Copyright to show |
|
284 copyright = __copyright__ |
|
285 |
|
286 # Apply file globbing ? |
|
287 globbing = 1 |
|
288 |
|
289 # Generate debug output ? |
|
290 debug = 0 |
|
291 |
|
292 # Generate verbose output ? |
|
293 verbose = 0 |
|
294 |
|
295 # Internal errors to catch |
|
296 InternalError = exceptions.Exception |
|
297 |
|
298 # Instance variables: |
|
299 values = None # Dictionary of passed options (or default values) |
|
300 # indexed by the options name, e.g. '-h' |
|
301 files = None # List of passed filenames |
|
302 optionlist = None # List of passed options |
|
303 |
|
304 def __init__(self,argv=None): |
|
305 |
|
306 # Setup application specs |
|
307 if argv is None: |
|
308 argv = sys.argv |
|
309 self.filename = os.path.split(argv[0])[1] |
|
310 if not self.name: |
|
311 self.name = os.path.split(self.filename)[1] |
|
312 else: |
|
313 self.name = self.name |
|
314 if not self.header: |
|
315 self.header = self.name |
|
316 else: |
|
317 self.header = self.header |
|
318 |
|
319 # Init .arguments list |
|
320 self.arguments = argv[1:] |
|
321 |
|
322 # Setup Option mapping |
|
323 self.option_map = option_dict(self.options) |
|
324 |
|
325 # Append preset options |
|
326 for option in self.preset_options: |
|
327 if not self.option_map.has_key(option.name): |
|
328 self.add_option(option) |
|
329 |
|
330 # Init .files list |
|
331 self.files = [] |
|
332 |
|
333 # Start Application |
|
334 try: |
|
335 # Process startup |
|
336 rc = self.startup() |
|
337 if rc is not None: |
|
338 raise SystemExit,rc |
|
339 |
|
340 # Parse command line |
|
341 rc = self.parse() |
|
342 if rc is not None: |
|
343 raise SystemExit,rc |
|
344 |
|
345 # Start application |
|
346 rc = self.main() |
|
347 if rc is None: |
|
348 rc = 0 |
|
349 |
|
350 except SystemExit,rc: |
|
351 pass |
|
352 |
|
353 except KeyboardInterrupt: |
|
354 print |
|
355 print '* User Break' |
|
356 print |
|
357 rc = 1 |
|
358 |
|
359 except self.InternalError: |
|
360 print |
|
361 print '* Internal Error (use --debug to display the traceback)' |
|
362 if self.debug: |
|
363 print |
|
364 traceback.print_exc(20, sys.stdout) |
|
365 elif self.verbose: |
|
366 print ' %s: %s' % sys.exc_info()[:2] |
|
367 print |
|
368 rc = 1 |
|
369 |
|
370 raise SystemExit,rc |
|
371 |
|
372 def add_option(self, option): |
|
373 |
|
374 """ Add a new Option instance to the Application dynamically. |
|
375 |
|
376 Note that this has to be done *before* .parse() is being |
|
377 executed. |
|
378 |
|
379 """ |
|
380 self.options.append(option) |
|
381 self.option_map[option.name] = option |
|
382 |
|
383 def startup(self): |
|
384 |
|
385 """ Set user defined instance variables. |
|
386 |
|
387 If this method returns anything other than None, the |
|
388 process is terminated with the return value as exit code. |
|
389 |
|
390 """ |
|
391 return None |
|
392 |
|
393 def exit(self, rc=0): |
|
394 |
|
395 """ Exit the program. |
|
396 |
|
397 rc is used as exit code and passed back to the calling |
|
398 program. It defaults to 0 which usually means: OK. |
|
399 |
|
400 """ |
|
401 raise SystemExit, rc |
|
402 |
|
403 def parse(self): |
|
404 |
|
405 """ Parse the command line and fill in self.values and self.files. |
|
406 |
|
407 After having parsed the options, the remaining command line |
|
408 arguments are interpreted as files and passed to .handle_files() |
|
409 for processing. |
|
410 |
|
411 As final step the option handlers are called in the order |
|
412 of the options given on the command line. |
|
413 |
|
414 """ |
|
415 # Parse arguments |
|
416 self.values = values = {} |
|
417 for o in self.options: |
|
418 if o.has_default: |
|
419 values[o.prefix+o.name] = o.default |
|
420 else: |
|
421 values[o.prefix+o.name] = 0 |
|
422 flags,lflags = _getopt_flags(self.options) |
|
423 try: |
|
424 optlist,files = getopt.getopt(self.arguments,flags,lflags) |
|
425 if self.globbing: |
|
426 l = [] |
|
427 for f in files: |
|
428 gf = glob.glob(f) |
|
429 if not gf: |
|
430 l.append(f) |
|
431 else: |
|
432 l[len(l):] = gf |
|
433 files = l |
|
434 self.optionlist = optlist |
|
435 self.files = files + self.files |
|
436 except getopt.error,why: |
|
437 self.help(why) |
|
438 sys.exit(1) |
|
439 |
|
440 # Call file handler |
|
441 rc = self.handle_files(self.files) |
|
442 if rc is not None: |
|
443 sys.exit(rc) |
|
444 |
|
445 # Call option handlers |
|
446 for optionname, value in optlist: |
|
447 |
|
448 # Try to convert value to integer |
|
449 try: |
|
450 value = string.atoi(value) |
|
451 except ValueError: |
|
452 pass |
|
453 |
|
454 # Find handler and call it (or count the number of option |
|
455 # instances on the command line) |
|
456 handlername = 'handle' + string.replace(optionname, '-', '_') |
|
457 try: |
|
458 handler = getattr(self, handlername) |
|
459 except AttributeError: |
|
460 if value == '': |
|
461 # count the number of occurances |
|
462 if values.has_key(optionname): |
|
463 values[optionname] = values[optionname] + 1 |
|
464 else: |
|
465 values[optionname] = 1 |
|
466 else: |
|
467 values[optionname] = value |
|
468 else: |
|
469 rc = handler(value) |
|
470 if rc is not None: |
|
471 raise SystemExit, rc |
|
472 |
|
473 # Apply final file check (for backward compatibility) |
|
474 rc = self.check_files(self.files) |
|
475 if rc is not None: |
|
476 sys.exit(rc) |
|
477 |
|
478 def check_files(self,filelist): |
|
479 |
|
480 """ Apply some user defined checks on the files given in filelist. |
|
481 |
|
482 This may modify filelist in place. A typical application |
|
483 is checking that at least n files are given. |
|
484 |
|
485 If this method returns anything other than None, the |
|
486 process is terminated with the return value as exit code. |
|
487 |
|
488 """ |
|
489 return None |
|
490 |
|
491 def help(self,note=''): |
|
492 |
|
493 self.print_header() |
|
494 if self.synopsis: |
|
495 print 'Synopsis:' |
|
496 # To remain backward compatible: |
|
497 try: |
|
498 synopsis = self.synopsis % self.name |
|
499 except (NameError, KeyError, TypeError): |
|
500 synopsis = self.synopsis % self.__dict__ |
|
501 print ' ' + synopsis |
|
502 print |
|
503 self.print_options() |
|
504 if self.version: |
|
505 print 'Version:' |
|
506 print ' %s' % self.version |
|
507 print |
|
508 if self.about: |
|
509 print string.strip(self.about % self.__dict__) |
|
510 print |
|
511 if note: |
|
512 print '-'*72 |
|
513 print 'Note:',note |
|
514 print |
|
515 |
|
516 def notice(self,note): |
|
517 |
|
518 print '-'*72 |
|
519 print 'Note:',note |
|
520 print '-'*72 |
|
521 print |
|
522 |
|
523 def print_header(self): |
|
524 |
|
525 print '-'*72 |
|
526 print self.header % self.__dict__ |
|
527 print '-'*72 |
|
528 print |
|
529 |
|
530 def print_options(self): |
|
531 |
|
532 options = self.options |
|
533 print 'Options and default settings:' |
|
534 if not options: |
|
535 print ' None' |
|
536 return |
|
537 long = filter(lambda x: x.prefix == '--', options) |
|
538 short = filter(lambda x: x.prefix == '-', options) |
|
539 items = short + long |
|
540 for o in options: |
|
541 print ' ',o |
|
542 print |
|
543 |
|
544 # |
|
545 # Example handlers: |
|
546 # |
|
547 # If a handler returns anything other than None, processing stops |
|
548 # and the return value is passed to sys.exit() as argument. |
|
549 # |
|
550 |
|
551 # File handler |
|
552 def handle_files(self,files): |
|
553 |
|
554 """ This may process the files list in place. |
|
555 """ |
|
556 return None |
|
557 |
|
558 # Short option handler |
|
559 def handle_h(self,arg): |
|
560 |
|
561 self.help() |
|
562 return 0 |
|
563 |
|
564 def handle_v(self, value): |
|
565 |
|
566 """ Turn on verbose output. |
|
567 """ |
|
568 self.verbose = 1 |
|
569 |
|
570 # Handlers for long options have two underscores in their name |
|
571 def handle__help(self,arg): |
|
572 |
|
573 self.help() |
|
574 return 0 |
|
575 |
|
576 def handle__debug(self,arg): |
|
577 |
|
578 self.debug = 1 |
|
579 # We don't want to catch internal errors: |
|
580 self.InternalError = None |
|
581 |
|
582 def handle__copyright(self,arg): |
|
583 |
|
584 self.print_header() |
|
585 print string.strip(self.copyright % self.__dict__) |
|
586 print |
|
587 return 0 |
|
588 |
|
589 def handle__examples(self,arg): |
|
590 |
|
591 self.print_header() |
|
592 if self.examples: |
|
593 print 'Examples:' |
|
594 print |
|
595 print string.strip(self.examples % self.__dict__) |
|
596 print |
|
597 else: |
|
598 print 'No examples available.' |
|
599 print |
|
600 return 0 |
|
601 |
|
602 def main(self): |
|
603 |
|
604 """ Override this method as program entry point. |
|
605 |
|
606 The return value is passed to sys.exit() as argument. If |
|
607 it is None, 0 is assumed (meaning OK). Unhandled |
|
608 exceptions are reported with exit status code 1 (see |
|
609 __init__ for further details). |
|
610 |
|
611 """ |
|
612 return None |
|
613 |
|
614 # Alias |
|
615 CommandLine = Application |
|
616 |
|
617 def _test(): |
|
618 |
|
619 class MyApplication(Application): |
|
620 header = 'Test Application' |
|
621 version = __version__ |
|
622 options = [Option('-v','verbose')] |
|
623 |
|
624 def handle_v(self,arg): |
|
625 print 'VERBOSE, Yeah !' |
|
626 |
|
627 cmd = MyApplication() |
|
628 if not cmd.values['-h']: |
|
629 cmd.help() |
|
630 print 'files:',cmd.files |
|
631 print 'Bye...' |
|
632 |
|
633 if __name__ == '__main__': |
|
634 _test() |