blob: 909ef02db98e8d6a0f10ee6fb01e3b8938a96e3e [file] [log] [blame]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +00001""" 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)
Thomas Wouters477c8d52006-05-27 19:21:47 +000010 * Add number range option using srange()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000011
12"""
13
14__copyright__ = """\
15Copyright (c), 1997-2006, Marc-Andre Lemburg (mal@lemburg.com)
16Copyright (c), 2000-2006, eGenix.com Software GmbH (info@egenix.com)
17See the documentation for further information on copyrights,
18or contact the author. All Rights Reserved.
19"""
20
21__version__ = '1.2'
22
23import sys, getopt, string, glob, os, re, exceptions, traceback
24
25### Helpers
26
27def _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
49def 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
61def 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
88def 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
98getpasswd = invisible_input
99
100_integerRE = re.compile('\s*(-?\d+)\s*$')
101_integerRangeRE = re.compile('\s*(-?\d+)\s*-\s*(-?\d+)\s*$')
102
103def 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
130def 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
153class 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] == '-':
Guido van Rossum5b787e82007-01-13 23:54:39 +0000168 raise TypeError('option names must start with "-"')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000169 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
192class ArgumentOption(Option):
193
194 """ Option that takes an argument.
195
196 An optional default argument can be given.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000197
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000198 """
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
209class 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
225class 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
Thomas Wouters477c8d52006-05-27 19:21:47 +0000302 optionlist = None # List of passed options
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000303
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:]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000321
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000322 # Setup Option mapping
323 self.option_map = option_dict(self.options)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000324
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000325 # Append preset options
326 for option in self.preset_options:
Guido van Rossum5b787e82007-01-13 23:54:39 +0000327 if not option.name in self.option_map:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000328 self.add_option(option)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000329
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000330 # Init .files list
331 self.files = []
332
333 # Start Application
Guido van Rossum5b787e82007-01-13 23:54:39 +0000334 rc = 0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000335 try:
336 # Process startup
337 rc = self.startup()
338 if rc is not None:
Guido van Rossum5b787e82007-01-13 23:54:39 +0000339 raise SystemExit(rc)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000340
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000341 # Parse command line
342 rc = self.parse()
343 if rc is not None:
Guido van Rossum5b787e82007-01-13 23:54:39 +0000344 raise SystemExit(rc)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000345
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000346 # Start application
347 rc = self.main()
348 if rc is None:
349 rc = 0
350
Guido van Rossum5b787e82007-01-13 23:54:39 +0000351 except SystemExit as rcException:
352 rc = rcException
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000353 pass
354
355 except KeyboardInterrupt:
356 print
357 print '* User Break'
358 print
359 rc = 1
360
361 except self.InternalError:
362 print
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000363 print '* Internal Error (use --debug to display the traceback)'
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000364 if self.debug:
365 print
366 traceback.print_exc(20, sys.stdout)
367 elif self.verbose:
368 print ' %s: %s' % sys.exc_info()[:2]
369 print
370 rc = 1
371
Guido van Rossum5b787e82007-01-13 23:54:39 +0000372 raise SystemExit(rc)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000373
374 def add_option(self, option):
375
376 """ Add a new Option instance to the Application dynamically.
377
378 Note that this has to be done *before* .parse() is being
379 executed.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000380
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000381 """
382 self.options.append(option)
383 self.option_map[option.name] = option
384
385 def startup(self):
386
387 """ Set user defined instance variables.
388
389 If this method returns anything other than None, the
390 process is terminated with the return value as exit code.
391
392 """
393 return None
394
395 def exit(self, rc=0):
396
397 """ Exit the program.
398
399 rc is used as exit code and passed back to the calling
400 program. It defaults to 0 which usually means: OK.
401
402 """
Guido van Rossum5b787e82007-01-13 23:54:39 +0000403 raise SystemExit(rc)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000404
405 def parse(self):
406
407 """ Parse the command line and fill in self.values and self.files.
408
409 After having parsed the options, the remaining command line
410 arguments are interpreted as files and passed to .handle_files()
411 for processing.
412
413 As final step the option handlers are called in the order
414 of the options given on the command line.
415
416 """
417 # Parse arguments
418 self.values = values = {}
419 for o in self.options:
420 if o.has_default:
421 values[o.prefix+o.name] = o.default
422 else:
423 values[o.prefix+o.name] = 0
424 flags,lflags = _getopt_flags(self.options)
425 try:
426 optlist,files = getopt.getopt(self.arguments,flags,lflags)
427 if self.globbing:
428 l = []
429 for f in files:
430 gf = glob.glob(f)
431 if not gf:
432 l.append(f)
433 else:
434 l[len(l):] = gf
435 files = l
436 self.optionlist = optlist
437 self.files = files + self.files
Guido van Rossumb940e112007-01-10 16:19:56 +0000438 except getopt.error as why:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000439 self.help(why)
440 sys.exit(1)
441
442 # Call file handler
443 rc = self.handle_files(self.files)
444 if rc is not None:
445 sys.exit(rc)
446
447 # Call option handlers
448 for optionname, value in optlist:
449
450 # Try to convert value to integer
451 try:
452 value = string.atoi(value)
453 except ValueError:
454 pass
455
456 # Find handler and call it (or count the number of option
457 # instances on the command line)
458 handlername = 'handle' + string.replace(optionname, '-', '_')
459 try:
460 handler = getattr(self, handlername)
461 except AttributeError:
462 if value == '':
463 # count the number of occurances
Guido van Rossum5b787e82007-01-13 23:54:39 +0000464 if optionname in values:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000465 values[optionname] = values[optionname] + 1
466 else:
467 values[optionname] = 1
468 else:
469 values[optionname] = value
470 else:
471 rc = handler(value)
472 if rc is not None:
Guido van Rossum5b787e82007-01-13 23:54:39 +0000473 raise SystemExit(rc)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000474
475 # Apply final file check (for backward compatibility)
476 rc = self.check_files(self.files)
477 if rc is not None:
478 sys.exit(rc)
479
480 def check_files(self,filelist):
481
482 """ Apply some user defined checks on the files given in filelist.
483
484 This may modify filelist in place. A typical application
485 is checking that at least n files are given.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000486
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000487 If this method returns anything other than None, the
488 process is terminated with the return value as exit code.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000489
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000490 """
491 return None
492
493 def help(self,note=''):
494
495 self.print_header()
496 if self.synopsis:
497 print 'Synopsis:'
498 # To remain backward compatible:
499 try:
500 synopsis = self.synopsis % self.name
501 except (NameError, KeyError, TypeError):
502 synopsis = self.synopsis % self.__dict__
503 print ' ' + synopsis
504 print
505 self.print_options()
506 if self.version:
507 print 'Version:'
508 print ' %s' % self.version
509 print
510 if self.about:
511 print string.strip(self.about % self.__dict__)
512 print
513 if note:
514 print '-'*72
515 print 'Note:',note
516 print
517
518 def notice(self,note):
519
520 print '-'*72
521 print 'Note:',note
522 print '-'*72
523 print
524
525 def print_header(self):
526
527 print '-'*72
528 print self.header % self.__dict__
529 print '-'*72
530 print
531
532 def print_options(self):
533
534 options = self.options
535 print 'Options and default settings:'
536 if not options:
537 print ' None'
538 return
539 long = filter(lambda x: x.prefix == '--', options)
540 short = filter(lambda x: x.prefix == '-', options)
541 items = short + long
542 for o in options:
543 print ' ',o
544 print
545
546 #
547 # Example handlers:
548 #
549 # If a handler returns anything other than None, processing stops
550 # and the return value is passed to sys.exit() as argument.
551 #
552
553 # File handler
554 def handle_files(self,files):
555
556 """ This may process the files list in place.
557 """
558 return None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000559
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000560 # Short option handler
561 def handle_h(self,arg):
562
563 self.help()
564 return 0
Thomas Wouters477c8d52006-05-27 19:21:47 +0000565
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000566 def handle_v(self, value):
567
568 """ Turn on verbose output.
569 """
570 self.verbose = 1
Thomas Wouters477c8d52006-05-27 19:21:47 +0000571
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000572 # Handlers for long options have two underscores in their name
573 def handle__help(self,arg):
574
575 self.help()
576 return 0
577
578 def handle__debug(self,arg):
579
580 self.debug = 1
581 # We don't want to catch internal errors:
582 self.InternalError = None
583
584 def handle__copyright(self,arg):
585
586 self.print_header()
587 print string.strip(self.copyright % self.__dict__)
588 print
589 return 0
590
591 def handle__examples(self,arg):
592
593 self.print_header()
594 if self.examples:
595 print 'Examples:'
596 print
597 print string.strip(self.examples % self.__dict__)
598 print
599 else:
600 print 'No examples available.'
601 print
602 return 0
603
604 def main(self):
605
606 """ Override this method as program entry point.
607
608 The return value is passed to sys.exit() as argument. If
609 it is None, 0 is assumed (meaning OK). Unhandled
610 exceptions are reported with exit status code 1 (see
611 __init__ for further details).
Thomas Wouters477c8d52006-05-27 19:21:47 +0000612
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000613 """
614 return None
615
616# Alias
617CommandLine = Application
618
619def _test():
620
621 class MyApplication(Application):
622 header = 'Test Application'
623 version = __version__
624 options = [Option('-v','verbose')]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000625
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000626 def handle_v(self,arg):
627 print 'VERBOSE, Yeah !'
628
629 cmd = MyApplication()
630 if not cmd.values['-h']:
631 cmd.help()
632 print 'files:',cmd.files
633 print 'Bye...'
634
635if __name__ == '__main__':
636 _test()