blob: 6601be5fb5186f6619e9c97450328528c9108b86 [file] [log] [blame]
Marc-André Lemburgc311f642006-04-19 15:27:33 +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)
Tim Petersf9cc5942006-04-21 16:34:54 +000010 * Add number range option using srange()
Marc-André Lemburgc311f642006-04-19 15:27:33 +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] == '-':
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
192class ArgumentOption(Option):
193
194 """ Option that takes an argument.
195
196 An optional default argument can be given.
Tim Petersf9cc5942006-04-21 16:34:54 +0000197
Marc-André Lemburgc311f642006-04-19 15:27:33 +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
Tim Petersf9cc5942006-04-21 16:34:54 +0000302 optionlist = None # List of passed options
Marc-André Lemburgc311f642006-04-19 15:27:33 +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:]
Tim Petersf9cc5942006-04-21 16:34:54 +0000321
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000322 # Setup Option mapping
323 self.option_map = option_dict(self.options)
Tim Petersf9cc5942006-04-21 16:34:54 +0000324
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000325 # 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)
Tim Petersf9cc5942006-04-21 16:34:54 +0000329
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000330 # 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
Tim Petersf9cc5942006-04-21 16:34:54 +0000339
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000340 # Parse command line
341 rc = self.parse()
342 if rc is not None:
343 raise SystemExit,rc
Tim Petersf9cc5942006-04-21 16:34:54 +0000344
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000345 # 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
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000361 print '* Internal Error (use --debug to display the traceback)'
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000362 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.
Tim Petersf9cc5942006-04-21 16:34:54 +0000378
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000379 """
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.
Tim Petersf9cc5942006-04-21 16:34:54 +0000484
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000485 If this method returns anything other than None, the
486 process is terminated with the return value as exit code.
Tim Petersf9cc5942006-04-21 16:34:54 +0000487
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000488 """
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
Tim Petersf9cc5942006-04-21 16:34:54 +0000557
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000558 # Short option handler
559 def handle_h(self,arg):
560
561 self.help()
562 return 0
Tim Petersf9cc5942006-04-21 16:34:54 +0000563
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000564 def handle_v(self, value):
565
566 """ Turn on verbose output.
567 """
568 self.verbose = 1
Tim Petersf9cc5942006-04-21 16:34:54 +0000569
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000570 # 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).
Tim Petersf9cc5942006-04-21 16:34:54 +0000610
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000611 """
612 return None
613
614# Alias
615CommandLine = Application
616
617def _test():
618
619 class MyApplication(Application):
620 header = 'Test Application'
621 version = __version__
622 options = [Option('-v','verbose')]
Tim Petersf9cc5942006-04-21 16:34:54 +0000623
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000624 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
633if __name__ == '__main__':
634 _test()