]> arthur.barton.de Git - bup.git/blob - lib/bup/options.py
lib/bup/options: Add docstrings
[bup.git] / lib / bup / options.py
1 """Command-line options parser.
2 With the help of an options spec string, easily parse command-line options.
3 """
4 import sys
5 import textwrap
6 import getopt
7 import re
8
9 class OptDict:
10     def __init__(self):
11         self._opts = {}
12
13     def __setitem__(self, k, v):
14         self._opts[k] = v
15
16     def __getitem__(self, k):
17         return self._opts[k]
18
19     def __getattr__(self, k):
20         return self[k]
21
22
23 def _default_onabort(msg):
24     sys.exit(97)
25
26
27 def _intify(v):
28     try:
29         vv = int(v or '')
30         if str(vv) == v:
31             return vv
32     except ValueError:
33         pass
34     return v
35
36
37 class Options:
38     """Option parser.
39     When constructed, two strings are mandatory. The first one is the command
40     name showed before error messages. The second one is a string called an
41     optspec that specifies the synopsis and option flags and their description.
42     For more information about optspecs, consult the bup-options(1) man page.
43
44     Two optional arguments specify an alternative parsing function and an
45     alternative behaviour on abort (after having output the usage string).
46
47     By default, the parser function is getopt.gnu_getopt, and the abort
48     behaviour is to exit the program.
49     """
50     def __init__(self, exe, optspec, optfunc=getopt.gnu_getopt,
51                  onabort=_default_onabort):
52         self.exe = exe
53         self.optspec = optspec
54         self._onabort = onabort
55         self.optfunc = optfunc
56         self._aliases = {}
57         self._shortopts = 'h?'
58         self._longopts = ['help']
59         self._hasparms = {}
60         self._defaults = {}
61         self._usagestr = self._gen_usage()
62
63     def _gen_usage(self):
64         out = []
65         lines = self.optspec.strip().split('\n')
66         lines.reverse()
67         first_syn = True
68         while lines:
69             l = lines.pop()
70             if l == '--': break
71             out.append('%s: %s\n' % (first_syn and 'usage' or '   or', l))
72             first_syn = False
73         out.append('\n')
74         while lines:
75             l = lines.pop()
76             if l.startswith(' '):
77                 out.append('\n%s\n' % l.lstrip())
78             elif l:
79                 (flags, extra) = l.split(' ', 1)
80                 extra = extra.strip()
81                 if flags.endswith('='):
82                     flags = flags[:-1]
83                     has_parm = 1
84                 else:
85                     has_parm = 0
86                 g = re.search(r'\[([^\]]*)\]', extra)
87                 if g:
88                     defval = g.group(1)
89                 else:
90                     defval = None
91                 flagl = flags.split(',')
92                 flagl_nice = []
93                 for f in flagl:
94                     self._aliases[f] = flagl[0]
95                     self._hasparms[f] = has_parm
96                     self._defaults[f] = _intify(defval)
97                     if len(f) == 1:
98                         self._shortopts += f + (has_parm and ':' or '')
99                         flagl_nice.append('-' + f)
100                     else:
101                         f_nice = re.sub(r'\W', '_', f)
102                         self._aliases[f_nice] = flagl[0]
103                         assert(not f.startswith('no-')) # supported implicitly
104                         self._longopts.append(f + (has_parm and '=' or ''))
105                         self._longopts.append('no-' + f)
106                         flagl_nice.append('--' + f)
107                 flags_nice = ', '.join(flagl_nice)
108                 if has_parm:
109                     flags_nice += ' ...'
110                 prefix = '    %-20s  ' % flags_nice
111                 argtext = '\n'.join(textwrap.wrap(extra, width=70,
112                                                 initial_indent=prefix,
113                                                 subsequent_indent=' '*28))
114                 out.append(argtext + '\n')
115             else:
116                 out.append('\n')
117         return ''.join(out).rstrip() + '\n'
118
119     def usage(self, msg=""):
120         """Print usage string to stderr and abort."""
121         sys.stderr.write(self._usagestr)
122         e = self._onabort and self._onabort(msg) or None
123         if e:
124             raise e
125
126     def fatal(self, s):
127         """Print an error message to stderr and abort with usage string."""
128         msg = 'error: %s\n' % s
129         sys.stderr.write(msg)
130         return self.usage(msg)
131
132     def parse(self, args):
133         """Parse a list of arguments and return (options, flags, extra).
134
135         In the returned tuple, "options" is an OptDict with known options,
136         "flags" is a list of option flags that were used on the command-line,
137         and "extra" is a list of positional arguments.
138         """
139         try:
140             (flags,extra) = self.optfunc(args, self._shortopts, self._longopts)
141         except getopt.GetoptError, e:
142             self.fatal(e)
143
144         opt = OptDict()
145
146         for k,v in self._defaults.iteritems():
147             k = self._aliases[k]
148             opt[k] = v
149
150         for (k,v) in flags:
151             k = k.lstrip('-')
152             if k in ('h', '?', 'help'):
153                 self.usage()
154             if k.startswith('no-'):
155                 k = self._aliases[k[3:]]
156                 v = 0
157             else:
158                 k = self._aliases[k]
159                 if not self._hasparms[k]:
160                     assert(v == '')
161                     v = (opt._opts.get(k) or 0) + 1
162                 else:
163                     v = _intify(v)
164             opt[k] = v
165         for (f1,f2) in self._aliases.iteritems():
166             opt[f1] = opt._opts.get(f2)
167         return (opt,flags,extra)