blob: d0142f82a5c92f679f121d9b486071351a5ca3f1 [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
Guido van Rossum486364b2007-06-30 05:01:58 +000023import sys, getopt, glob, os, re, traceback
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000024
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)
Guido van Rossum486364b2007-06-30 05:01:58 +000047 return ''.join(s), l
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000048
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:
Guido van Rossumcd16bf62007-06-13 18:07:49 +000085 os.chmod(name, 0o600)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000086 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
Guido van Rossum486364b2007-06-30 05:01:58 +0000105 integer=_integerRE,
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000106 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
Guido van Rossum486364b2007-06-30 05:01:58 +0000119 for entry in s.split(','):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000120 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
Guido van Rossum486364b2007-06-30 05:01:58 +0000296 InternalError = BaseException
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000297
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:
Guido van Rossum486364b2007-06-30 05:01:58 +0000356 print()
357 print('* User Break')
358 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000359 rc = 1
360
361 except self.InternalError:
Guido van Rossum486364b2007-06-30 05:01:58 +0000362 print()
363 print('* Internal Error (use --debug to display the traceback)')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000364 if self.debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000365 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000366 traceback.print_exc(20, sys.stdout)
367 elif self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000368 print(' %s: %s' % sys.exc_info()[:2])
369 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000370 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:
Guido van Rossum486364b2007-06-30 05:01:58 +0000452 value = int(value)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000453 except ValueError:
454 pass
455
456 # Find handler and call it (or count the number of option
457 # instances on the command line)
Guido van Rossum486364b2007-06-30 05:01:58 +0000458 handlername = 'handle' + optionname.replace('-', '_')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000459 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:
Guido van Rossum486364b2007-06-30 05:01:58 +0000497 print('Synopsis:')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000498 # To remain backward compatible:
499 try:
500 synopsis = self.synopsis % self.name
501 except (NameError, KeyError, TypeError):
502 synopsis = self.synopsis % self.__dict__
Guido van Rossum486364b2007-06-30 05:01:58 +0000503 print(' ' + synopsis)
504 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000505 self.print_options()
506 if self.version:
Guido van Rossum486364b2007-06-30 05:01:58 +0000507 print('Version:')
508 print(' %s' % self.version)
509 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000510 if self.about:
Guido van Rossum486364b2007-06-30 05:01:58 +0000511 about = self.about % self.__dict__
512 print(about.strip())
513 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000514 if note:
Guido van Rossum486364b2007-06-30 05:01:58 +0000515 print('-'*72)
516 print('Note:',note)
517 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000518
519 def notice(self,note):
520
Guido van Rossum486364b2007-06-30 05:01:58 +0000521 print('-'*72)
522 print('Note:',note)
523 print('-'*72)
524 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000525
526 def print_header(self):
527
Guido van Rossum486364b2007-06-30 05:01:58 +0000528 print('-'*72)
529 print(self.header % self.__dict__)
530 print('-'*72)
531 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000532
533 def print_options(self):
534
535 options = self.options
Guido van Rossum486364b2007-06-30 05:01:58 +0000536 print('Options and default settings:')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000537 if not options:
Guido van Rossum486364b2007-06-30 05:01:58 +0000538 print(' None')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000539 return
540 long = filter(lambda x: x.prefix == '--', options)
541 short = filter(lambda x: x.prefix == '-', options)
542 items = short + long
543 for o in options:
Guido van Rossum486364b2007-06-30 05:01:58 +0000544 print(' ',o)
545 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000546
547 #
548 # Example handlers:
549 #
550 # If a handler returns anything other than None, processing stops
551 # and the return value is passed to sys.exit() as argument.
552 #
553
554 # File handler
555 def handle_files(self,files):
556
557 """ This may process the files list in place.
558 """
559 return None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000560
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000561 # Short option handler
562 def handle_h(self,arg):
563
564 self.help()
565 return 0
Thomas Wouters477c8d52006-05-27 19:21:47 +0000566
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000567 def handle_v(self, value):
568
569 """ Turn on verbose output.
570 """
571 self.verbose = 1
Thomas Wouters477c8d52006-05-27 19:21:47 +0000572
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000573 # Handlers for long options have two underscores in their name
574 def handle__help(self,arg):
575
576 self.help()
577 return 0
578
579 def handle__debug(self,arg):
580
581 self.debug = 1
582 # We don't want to catch internal errors:
Guido van Rossum486364b2007-06-30 05:01:58 +0000583 class NoErrorToCatch(Exception): pass
584 self.InternalError = NoErrorToCatch
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000585
586 def handle__copyright(self,arg):
587
588 self.print_header()
Guido van Rossum486364b2007-06-30 05:01:58 +0000589 copyright = self.copyright % self.__dict__
590 print(copyright.strip())
591 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000592 return 0
593
594 def handle__examples(self,arg):
595
596 self.print_header()
597 if self.examples:
Guido van Rossum486364b2007-06-30 05:01:58 +0000598 print('Examples:')
599 print()
600 examples = self.examples % self.__dict__
601 print(examples.strip())
602 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000603 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000604 print('No examples available.')
605 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000606 return 0
607
608 def main(self):
609
610 """ Override this method as program entry point.
611
612 The return value is passed to sys.exit() as argument. If
613 it is None, 0 is assumed (meaning OK). Unhandled
614 exceptions are reported with exit status code 1 (see
615 __init__ for further details).
Thomas Wouters477c8d52006-05-27 19:21:47 +0000616
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000617 """
618 return None
619
620# Alias
621CommandLine = Application
622
623def _test():
624
625 class MyApplication(Application):
626 header = 'Test Application'
627 version = __version__
628 options = [Option('-v','verbose')]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000629
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000630 def handle_v(self,arg):
Guido van Rossum486364b2007-06-30 05:01:58 +0000631 print('VERBOSE, Yeah !')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000632
633 cmd = MyApplication()
634 if not cmd.values['-h']:
635 cmd.help()
Guido van Rossum486364b2007-06-30 05:01:58 +0000636 print('files:',cmd.files)
637 print('Bye...')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000638
639if __name__ == '__main__':
640 _test()