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