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