]> arthur.barton.de Git - bup.git/blob - lib/bup/options.py
650e10af38a34f87dabd883f6e354fb7cba50e7a
[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         last_was_option = False
89         while lines:
90             l = lines.pop()
91             if l.startswith(' '):
92                 out.append('%s%s\n' % (last_was_option and '\n' or '',
93                                        l.lstrip()))
94                 last_was_option = False
95             elif l:
96                 (flags, extra) = l.split(' ', 1)
97                 extra = extra.strip()
98                 if flags.endswith('='):
99                     flags = flags[:-1]
100                     has_parm = 1
101                 else:
102                     has_parm = 0
103                 g = re.search(r'\[([^\]]*)\]$', extra)
104                 if g:
105                     defval = g.group(1)
106                 else:
107                     defval = None
108                 flagl = flags.split(',')
109                 flagl_nice = []
110                 for f in flagl:
111                     f,dvi = _remove_negative_kv(f, _intify(defval))
112                     self._aliases[f] = _remove_negative_k(flagl[0])
113                     self._hasparms[f] = has_parm
114                     self._defaults[f] = dvi
115                     if len(f) == 1:
116                         self._shortopts += f + (has_parm and ':' or '')
117                         flagl_nice.append('-' + f)
118                     else:
119                         f_nice = re.sub(r'\W', '_', f)
120                         self._aliases[f_nice] = _remove_negative_k(flagl[0])
121                         self._longopts.append(f + (has_parm and '=' or ''))
122                         self._longopts.append('no-' + f)
123                         flagl_nice.append('--' + f)
124                 flags_nice = ', '.join(flagl_nice)
125                 if has_parm:
126                     flags_nice += ' ...'
127                 prefix = '    %-20s  ' % flags_nice
128                 argtext = '\n'.join(textwrap.wrap(extra, width=70,
129                                                 initial_indent=prefix,
130                                                 subsequent_indent=' '*28))
131                 out.append(argtext + '\n')
132                 last_was_option = True
133             else:
134                 out.append('\n')
135                 last_was_option = False
136         return ''.join(out).rstrip() + '\n'
137
138     def usage(self, msg=""):
139         """Print usage string to stderr and abort."""
140         sys.stderr.write(self._usagestr)
141         e = self._onabort and self._onabort(msg) or None
142         if e:
143             raise e
144
145     def fatal(self, s):
146         """Print an error message to stderr and abort with usage string."""
147         msg = 'error: %s\n' % s
148         sys.stderr.write(msg)
149         return self.usage(msg)
150
151     def parse(self, args):
152         """Parse a list of arguments and return (options, flags, extra).
153
154         In the returned tuple, "options" is an OptDict with known options,
155         "flags" is a list of option flags that were used on the command-line,
156         and "extra" is a list of positional arguments.
157         """
158         try:
159             (flags,extra) = self.optfunc(args, self._shortopts, self._longopts)
160         except getopt.GetoptError, e:
161             self.fatal(e)
162
163         opt = OptDict()
164
165         for k,v in self._defaults.iteritems():
166             k = self._aliases[k]
167             opt[k] = v
168
169         for (k,v) in flags:
170             k = k.lstrip('-')
171             if k in ('h', '?', 'help'):
172                 self.usage()
173             if k.startswith('no-'):
174                 k = self._aliases[k[3:]]
175                 v = 0
176             else:
177                 k = self._aliases[k]
178                 if not self._hasparms[k]:
179                     assert(v == '')
180                     v = (opt._opts.get(k) or 0) + 1
181                 else:
182                     v = _intify(v)
183             opt[k] = v
184         for (f1,f2) in self._aliases.iteritems():
185             opt[f1] = opt._opts.get(f2)
186         return (opt,flags,extra)