blob: 54a8ba7326f3fe6ef0a1752df8fc1cbf4a4a2501 [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
Antoine Pitrou8a681222009-02-07 17:13:31 +000014from __future__ import print_function
15
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000016__copyright__ = """\
17Copyright (c), 1997-2006, Marc-Andre Lemburg (mal@lemburg.com)
18Copyright (c), 2000-2006, eGenix.com Software GmbH (info@egenix.com)
19See the documentation for further information on copyrights,
20or contact the author. All Rights Reserved.
21"""
22
23__version__ = '1.2'
24
Guido van Rossum486364b2007-06-30 05:01:58 +000025import sys, getopt, glob, os, re, traceback
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000026
27### Helpers
28
29def _getopt_flags(options):
30
31 """ Convert the option list to a getopt flag string and long opt
32 list
33
34 """
35 s = []
36 l = []
37 for o in options:
38 if o.prefix == '-':
39 # short option
40 s.append(o.name)
41 if o.takes_argument:
42 s.append(':')
43 else:
44 # long option
45 if o.takes_argument:
46 l.append(o.name+'=')
47 else:
48 l.append(o.name)
Guido van Rossum486364b2007-06-30 05:01:58 +000049 return ''.join(s), l
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000050
51def invisible_input(prompt='>>> '):
52
53 """ Get raw input from a terminal without echoing the characters to
54 the terminal, e.g. for password queries.
55
56 """
57 import getpass
58 entry = getpass.getpass(prompt)
59 if entry is None:
60 raise KeyboardInterrupt
61 return entry
62
63def fileopen(name, mode='wb', encoding=None):
64
65 """ Open a file using mode.
66
67 Default mode is 'wb' meaning to open the file for writing in
68 binary mode. If encoding is given, I/O to and from the file is
69 transparently encoded using the given encoding.
70
71 Files opened for writing are chmod()ed to 0600.
72
73 """
74 if name == 'stdout':
75 return sys.stdout
76 elif name == 'stderr':
77 return sys.stderr
78 elif name == 'stdin':
79 return sys.stdin
80 else:
81 if encoding is not None:
82 import codecs
83 f = codecs.open(name, mode, encoding)
84 else:
85 f = open(name, mode)
86 if 'w' in mode:
Guido van Rossumcd16bf62007-06-13 18:07:49 +000087 os.chmod(name, 0o600)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000088 return f
89
90def option_dict(options):
91
92 """ Return a dictionary mapping option names to Option instances.
93 """
94 d = {}
95 for option in options:
96 d[option.name] = option
97 return d
98
99# Alias
100getpasswd = invisible_input
101
R David Murray44b548d2016-09-08 13:59:53 -0400102_integerRE = re.compile(r'\s*(-?\d+)\s*$')
103_integerRangeRE = re.compile(r'\s*(-?\d+)\s*-\s*(-?\d+)\s*$')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000104
105def srange(s,
106
Guido van Rossum486364b2007-06-30 05:01:58 +0000107 integer=_integerRE,
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000108 integerRange=_integerRangeRE):
109
110 """ Converts a textual representation of integer numbers and ranges
111 to a Python list.
112
113 Supported formats: 2,3,4,2-10,-1 - -3, 5 - -2
114
115 Values are appended to the created list in the order specified
116 in the string.
117
118 """
119 l = []
120 append = l.append
Guido van Rossum486364b2007-06-30 05:01:58 +0000121 for entry in s.split(','):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000122 m = integer.match(entry)
123 if m:
124 append(int(m.groups()[0]))
125 continue
126 m = integerRange.match(entry)
127 if m:
128 start,end = map(int,m.groups())
129 l[len(l):] = range(start,end+1)
130 return l
131
132def abspath(path,
133
134 expandvars=os.path.expandvars,expanduser=os.path.expanduser,
135 join=os.path.join,getcwd=os.getcwd):
136
137 """ Return the corresponding absolute path for path.
138
139 path is expanded in the usual shell ways before
140 joining it with the current working directory.
141
142 """
143 try:
144 path = expandvars(path)
145 except AttributeError:
146 pass
147 try:
148 path = expanduser(path)
149 except AttributeError:
150 pass
151 return join(getcwd(), path)
152
153### Option classes
154
155class Option:
156
157 """ Option base class. Takes no argument.
158
159 """
160 default = None
161 helptext = ''
162 prefix = '-'
163 takes_argument = 0
164 has_default = 0
165 tab = 15
166
167 def __init__(self,name,help=None):
168
169 if not name[:1] == '-':
Guido van Rossum5b787e82007-01-13 23:54:39 +0000170 raise TypeError('option names must start with "-"')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000171 if name[1:2] == '-':
172 self.prefix = '--'
173 self.name = name[2:]
174 else:
175 self.name = name[1:]
176 if help:
177 self.help = help
178
179 def __str__(self):
180
181 o = self
182 name = o.prefix + o.name
183 if o.takes_argument:
184 name = name + ' arg'
185 if len(name) > self.tab:
186 name = name + '\n' + ' ' * (self.tab + 1 + len(o.prefix))
187 else:
188 name = '%-*s ' % (self.tab, name)
189 description = o.help
190 if o.has_default:
191 description = description + ' (%s)' % o.default
192 return '%s %s' % (name, description)
193
194class ArgumentOption(Option):
195
196 """ Option that takes an argument.
197
198 An optional default argument can be given.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000199
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000200 """
201 def __init__(self,name,help=None,default=None):
202
203 # Basemethod
204 Option.__init__(self,name,help)
205
206 if default is not None:
207 self.default = default
208 self.has_default = 1
209 self.takes_argument = 1
210
211class SwitchOption(Option):
212
213 """ Options that can be on or off. Has an optional default value.
214
215 """
216 def __init__(self,name,help=None,default=None):
217
218 # Basemethod
219 Option.__init__(self,name,help)
220
221 if default is not None:
222 self.default = default
223 self.has_default = 1
224
225### Application baseclass
226
227class Application:
228
229 """ Command line application interface with builtin argument
230 parsing.
231
232 """
233 # Options the program accepts (Option instances)
234 options = []
235
236 # Standard settings; these are appended to options in __init__
237 preset_options = [SwitchOption('-v',
238 'generate verbose output'),
239 SwitchOption('-h',
240 'show this help text'),
241 SwitchOption('--help',
242 'show this help text'),
243 SwitchOption('--debug',
244 'enable debugging'),
245 SwitchOption('--copyright',
246 'show copyright'),
247 SwitchOption('--examples',
248 'show examples of usage')]
249
250 # The help layout looks like this:
251 # [header] - defaults to ''
252 #
253 # [synopsis] - formatted as '<self.name> %s' % self.synopsis
254 #
255 # options:
256 # [options] - formatted from self.options
257 #
258 # [version] - formatted as 'Version:\n %s' % self.version, if given
259 #
260 # [about] - defaults to ''
261 #
262 # Note: all fields that do not behave as template are formatted
263 # using the instances dictionary as substitution namespace,
264 # e.g. %(name)s will be replaced by the applications name.
265 #
266
267 # Header (default to program name)
268 header = ''
269
270 # Name (defaults to program name)
271 name = ''
272
273 # Synopsis (%(name)s is replaced by the program name)
274 synopsis = '%(name)s [option] files...'
275
276 # Version (optional)
277 version = ''
278
279 # General information printed after the possible options (optional)
280 about = ''
281
282 # Examples of usage to show when the --examples option is given (optional)
283 examples = ''
284
285 # Copyright to show
286 copyright = __copyright__
287
288 # Apply file globbing ?
289 globbing = 1
290
291 # Generate debug output ?
292 debug = 0
293
294 # Generate verbose output ?
295 verbose = 0
296
297 # Internal errors to catch
Guido van Rossum486364b2007-06-30 05:01:58 +0000298 InternalError = BaseException
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000299
300 # Instance variables:
301 values = None # Dictionary of passed options (or default values)
302 # indexed by the options name, e.g. '-h'
303 files = None # List of passed filenames
Thomas Wouters477c8d52006-05-27 19:21:47 +0000304 optionlist = None # List of passed options
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000305
306 def __init__(self,argv=None):
307
308 # Setup application specs
309 if argv is None:
310 argv = sys.argv
311 self.filename = os.path.split(argv[0])[1]
312 if not self.name:
313 self.name = os.path.split(self.filename)[1]
314 else:
315 self.name = self.name
316 if not self.header:
317 self.header = self.name
318 else:
319 self.header = self.header
320
321 # Init .arguments list
322 self.arguments = argv[1:]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000323
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000324 # Setup Option mapping
325 self.option_map = option_dict(self.options)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000326
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000327 # Append preset options
328 for option in self.preset_options:
Guido van Rossum5b787e82007-01-13 23:54:39 +0000329 if not option.name in self.option_map:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000330 self.add_option(option)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000331
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000332 # Init .files list
333 self.files = []
334
335 # Start Application
Guido van Rossum5b787e82007-01-13 23:54:39 +0000336 rc = 0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000337 try:
338 # Process startup
339 rc = self.startup()
340 if rc is not None:
Guido van Rossum5b787e82007-01-13 23:54:39 +0000341 raise SystemExit(rc)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000342
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000343 # Parse command line
344 rc = self.parse()
345 if rc is not None:
Guido van Rossum5b787e82007-01-13 23:54:39 +0000346 raise SystemExit(rc)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000347
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000348 # Start application
349 rc = self.main()
350 if rc is None:
351 rc = 0
352
Guido van Rossum5b787e82007-01-13 23:54:39 +0000353 except SystemExit as rcException:
354 rc = rcException
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000355 pass
356
357 except KeyboardInterrupt:
Guido van Rossum486364b2007-06-30 05:01:58 +0000358 print()
359 print('* User Break')
360 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000361 rc = 1
362
363 except self.InternalError:
Guido van Rossum486364b2007-06-30 05:01:58 +0000364 print()
365 print('* Internal Error (use --debug to display the traceback)')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000366 if self.debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000367 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000368 traceback.print_exc(20, sys.stdout)
369 elif self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000370 print(' %s: %s' % sys.exc_info()[:2])
371 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000372 rc = 1
373
Guido van Rossum5b787e82007-01-13 23:54:39 +0000374 raise SystemExit(rc)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000375
376 def add_option(self, option):
377
378 """ Add a new Option instance to the Application dynamically.
379
380 Note that this has to be done *before* .parse() is being
381 executed.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000382
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000383 """
384 self.options.append(option)
385 self.option_map[option.name] = option
386
387 def startup(self):
388
389 """ Set user defined instance variables.
390
391 If this method returns anything other than None, the
392 process is terminated with the return value as exit code.
393
394 """
395 return None
396
397 def exit(self, rc=0):
398
399 """ Exit the program.
400
401 rc is used as exit code and passed back to the calling
402 program. It defaults to 0 which usually means: OK.
403
404 """
Guido van Rossum5b787e82007-01-13 23:54:39 +0000405 raise SystemExit(rc)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000406
407 def parse(self):
408
409 """ Parse the command line and fill in self.values and self.files.
410
411 After having parsed the options, the remaining command line
412 arguments are interpreted as files and passed to .handle_files()
413 for processing.
414
415 As final step the option handlers are called in the order
416 of the options given on the command line.
417
418 """
419 # Parse arguments
420 self.values = values = {}
421 for o in self.options:
422 if o.has_default:
423 values[o.prefix+o.name] = o.default
424 else:
425 values[o.prefix+o.name] = 0
426 flags,lflags = _getopt_flags(self.options)
427 try:
428 optlist,files = getopt.getopt(self.arguments,flags,lflags)
429 if self.globbing:
430 l = []
431 for f in files:
432 gf = glob.glob(f)
433 if not gf:
434 l.append(f)
435 else:
436 l[len(l):] = gf
437 files = l
438 self.optionlist = optlist
439 self.files = files + self.files
Guido van Rossumb940e112007-01-10 16:19:56 +0000440 except getopt.error as why:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000441 self.help(why)
442 sys.exit(1)
443
444 # Call file handler
445 rc = self.handle_files(self.files)
446 if rc is not None:
447 sys.exit(rc)
448
449 # Call option handlers
450 for optionname, value in optlist:
451
452 # Try to convert value to integer
453 try:
Guido van Rossum486364b2007-06-30 05:01:58 +0000454 value = int(value)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000455 except ValueError:
456 pass
457
458 # Find handler and call it (or count the number of option
459 # instances on the command line)
Guido van Rossum486364b2007-06-30 05:01:58 +0000460 handlername = 'handle' + optionname.replace('-', '_')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000461 try:
462 handler = getattr(self, handlername)
463 except AttributeError:
464 if value == '':
Ezio Melotti7c4a7e62013-08-26 01:32:56 +0300465 # count the number of occurrences
Guido van Rossum5b787e82007-01-13 23:54:39 +0000466 if optionname in values:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000467 values[optionname] = values[optionname] + 1
468 else:
469 values[optionname] = 1
470 else:
471 values[optionname] = value
472 else:
473 rc = handler(value)
474 if rc is not None:
Guido van Rossum5b787e82007-01-13 23:54:39 +0000475 raise SystemExit(rc)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000476
477 # Apply final file check (for backward compatibility)
478 rc = self.check_files(self.files)
479 if rc is not None:
480 sys.exit(rc)
481
482 def check_files(self,filelist):
483
484 """ Apply some user defined checks on the files given in filelist.
485
486 This may modify filelist in place. A typical application
487 is checking that at least n files are given.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000488
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000489 If this method returns anything other than None, the
490 process is terminated with the return value as exit code.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000491
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000492 """
493 return None
494
495 def help(self,note=''):
496
497 self.print_header()
498 if self.synopsis:
Guido van Rossum486364b2007-06-30 05:01:58 +0000499 print('Synopsis:')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000500 # To remain backward compatible:
501 try:
502 synopsis = self.synopsis % self.name
503 except (NameError, KeyError, TypeError):
504 synopsis = self.synopsis % self.__dict__
Guido van Rossum486364b2007-06-30 05:01:58 +0000505 print(' ' + synopsis)
506 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000507 self.print_options()
508 if self.version:
Guido van Rossum486364b2007-06-30 05:01:58 +0000509 print('Version:')
510 print(' %s' % self.version)
511 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000512 if self.about:
Guido van Rossum486364b2007-06-30 05:01:58 +0000513 about = self.about % self.__dict__
514 print(about.strip())
515 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000516 if note:
Guido van Rossum486364b2007-06-30 05:01:58 +0000517 print('-'*72)
518 print('Note:',note)
519 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000520
521 def notice(self,note):
522
Guido van Rossum486364b2007-06-30 05:01:58 +0000523 print('-'*72)
524 print('Note:',note)
525 print('-'*72)
526 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000527
528 def print_header(self):
529
Guido van Rossum486364b2007-06-30 05:01:58 +0000530 print('-'*72)
531 print(self.header % self.__dict__)
532 print('-'*72)
533 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000534
535 def print_options(self):
536
537 options = self.options
Guido van Rossum486364b2007-06-30 05:01:58 +0000538 print('Options and default settings:')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000539 if not options:
Guido van Rossum486364b2007-06-30 05:01:58 +0000540 print(' None')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000541 return
Georg Brandlbf82e372008-05-16 17:02:34 +0000542 int = [x for x in options if x.prefix == '--']
543 short = [x for x in options if x.prefix == '-']
544 items = short + int
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000545 for o in options:
Guido van Rossum486364b2007-06-30 05:01:58 +0000546 print(' ',o)
547 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000548
549 #
550 # Example handlers:
551 #
552 # If a handler returns anything other than None, processing stops
553 # and the return value is passed to sys.exit() as argument.
554 #
555
556 # File handler
557 def handle_files(self,files):
558
559 """ This may process the files list in place.
560 """
561 return None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000562
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000563 # Short option handler
564 def handle_h(self,arg):
565
566 self.help()
567 return 0
Thomas Wouters477c8d52006-05-27 19:21:47 +0000568
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000569 def handle_v(self, value):
570
571 """ Turn on verbose output.
572 """
573 self.verbose = 1
Thomas Wouters477c8d52006-05-27 19:21:47 +0000574
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000575 # Handlers for long options have two underscores in their name
576 def handle__help(self,arg):
577
578 self.help()
579 return 0
580
581 def handle__debug(self,arg):
582
583 self.debug = 1
584 # We don't want to catch internal errors:
Guido van Rossum486364b2007-06-30 05:01:58 +0000585 class NoErrorToCatch(Exception): pass
586 self.InternalError = NoErrorToCatch
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000587
588 def handle__copyright(self,arg):
589
590 self.print_header()
Guido van Rossum486364b2007-06-30 05:01:58 +0000591 copyright = self.copyright % self.__dict__
592 print(copyright.strip())
593 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000594 return 0
595
596 def handle__examples(self,arg):
597
598 self.print_header()
599 if self.examples:
Guido van Rossum486364b2007-06-30 05:01:58 +0000600 print('Examples:')
601 print()
602 examples = self.examples % self.__dict__
603 print(examples.strip())
604 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000605 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000606 print('No examples available.')
607 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000608 return 0
609
610 def main(self):
611
612 """ Override this method as program entry point.
613
614 The return value is passed to sys.exit() as argument. If
615 it is None, 0 is assumed (meaning OK). Unhandled
616 exceptions are reported with exit status code 1 (see
617 __init__ for further details).
Thomas Wouters477c8d52006-05-27 19:21:47 +0000618
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000619 """
620 return None
621
622# Alias
623CommandLine = Application
624
625def _test():
626
627 class MyApplication(Application):
628 header = 'Test Application'
629 version = __version__
630 options = [Option('-v','verbose')]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000631
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000632 def handle_v(self,arg):
Guido van Rossum486364b2007-06-30 05:01:58 +0000633 print('VERBOSE, Yeah !')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000634
635 cmd = MyApplication()
636 if not cmd.values['-h']:
637 cmd.help()
Guido van Rossum486364b2007-06-30 05:01:58 +0000638 print('files:',cmd.files)
639 print('Bye...')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000640
641if __name__ == '__main__':
642 _test()