1 # Copyright 2010-2012 Avery Pennarun and options.py contributors.
4 # (This license applies to this file but not necessarily the other files in
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are
11 # 1. Redistributions of source code must retain the above copyright
12 # notice, this list of conditions and the following disclaimer.
14 # 2. Redistributions in binary form must reproduce the above copyright
15 # notice, this list of conditions and the following disclaimer in
16 # the documentation and/or other materials provided with the
19 # THIS SOFTWARE IS PROVIDED BY AVERY PENNARUN ``AS IS'' AND ANY
20 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR
23 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 """Command-line options parser.
32 With the help of an options spec string, easily parse command-line options.
34 An options spec is made up of two parts, separated by a line with two dashes.
35 The first part is the synopsis of the command and the second one specifies
36 options, one per line.
38 Each non-empty line in the synopsis gives a set of options that can be used
41 Option flags must be at the begining of the line and multiple flags are
42 separated by commas. Usually, options have a short, one character flag, and a
43 longer one, but the short one can be omitted.
45 Long option flags are used as the option's key for the OptDict produced when
48 When the flag definition is ended with an equal sign, the option takes
49 one string as an argument, and that string will be converted to an
50 integer when possible. Otherwise, the option does not take an argument
51 and corresponds to a boolean flag that is true when the option is
52 given on the command line.
54 The option's description is found at the right of its flags definition, after
55 one or more spaces. The description ends at the end of the line. If the
56 description contains text enclosed in square brackets, the enclosed text will
57 be used as the option's default value.
59 Options can be put in different groups. Options in the same group must be on
60 consecutive lines. Groups are formed by inserting a line that begins with a
61 space. The text on that line will be output after an empty line.
64 from __future__ import absolute_import
65 import sys, os, textwrap, getopt, re, struct
78 def _invert(v, invert):
84 def _remove_negative_kv(k, v):
85 if k.startswith('no-') or k.startswith('no_'):
90 class OptDict(object):
91 """Dictionary that exposes keys as attributes.
93 Keys can be set or accessed with a "no-" or "no_" prefix to negate the
96 def __init__(self, aliases):
98 self._aliases = aliases
100 def _unalias(self, k):
101 k, reinvert = _remove_negative_kv(k, False)
102 k, invert = self._aliases[k]
103 return k, invert ^ reinvert
105 def __setitem__(self, k, v):
106 k, invert = self._unalias(k)
107 self._opts[k] = _invert(v, invert)
109 def __getitem__(self, k):
110 k, invert = self._unalias(k)
111 return _invert(self._opts[k], invert)
113 def __getattr__(self, k):
117 def _default_onabort(msg):
138 if not fcntl and termios:
143 s = struct.pack("HHHH", 0, 0, 0, 0)
145 s = fcntl.ioctl(sys.stderr.fileno(), termios.TIOCGWINSZ, s)
148 ysize, xsize, ypix, xpix = struct.unpack('HHHH', s)
154 When constructed, a string called an option spec must be given. It
155 specifies the synopsis and option flags and their description. For more
156 information about option specs, see the docstring at the top of this file.
158 Two optional arguments specify an alternative parsing function and an
159 alternative behaviour on abort (after having output the usage string).
161 By default, the parser function is getopt.gnu_getopt, and the abort
162 behaviour is to exit the program.
164 def __init__(self, optspec, optfunc=getopt.gnu_getopt,
165 onabort=_default_onabort):
166 self.optspec = optspec
167 self._onabort = onabort
168 self.optfunc = optfunc
170 self._shortopts = 'h?'
171 self._longopts = ['help', 'usage']
174 self._usagestr = self._gen_usage() # this also parses the optspec
176 def _gen_usage(self):
178 lines = self.optspec.strip().split('\n')
184 out.append('%s: %s\n' % (first_syn and 'usage' or ' or', l))
187 last_was_option = False
190 if l.startswith(' '):
191 out.append('%s%s\n' % (last_was_option and '\n' or '',
193 last_was_option = False
195 (flags,extra) = (l + ' ').split(' ', 1)
196 extra = extra.strip()
197 if flags.endswith('='):
202 g = re.search(r'\[([^\]]*)\]$', extra)
204 defval = _intify(g.group(1))
207 flagl = flags.split(',')
209 flag_main, invert_main = _remove_negative_kv(flagl[0], False)
210 self._defaults[flag_main] = _invert(defval, invert_main)
212 f,invert = _remove_negative_kv(_f, 0)
213 self._aliases[f] = (flag_main, invert_main ^ invert)
214 self._hasparms[f] = has_parm
216 self._shortopts += '0123456789'
217 flagl_nice.append('-#')
219 self._shortopts += f + (has_parm and ':' or '')
220 flagl_nice.append('-' + f)
222 f_nice = re.sub(r'\W', '_', f)
223 self._aliases[f_nice] = (flag_main,
224 invert_main ^ invert)
225 self._longopts.append(f + (has_parm and '=' or ''))
226 self._longopts.append('no-' + f)
227 flagl_nice.append('--' + _f)
228 flags_nice = ', '.join(flagl_nice)
231 prefix = ' %-20s ' % flags_nice
232 argtext = '\n'.join(textwrap.wrap(extra, width=_tty_width(),
233 initial_indent=prefix,
234 subsequent_indent=' '*28))
235 out.append(argtext + '\n')
236 last_was_option = True
239 last_was_option = False
240 return ''.join(out).rstrip() + '\n'
242 def usage(self, msg=""):
243 """Print usage string to stderr and abort."""
244 sys.stderr.write(self._usagestr)
246 sys.stderr.write(msg)
247 e = self._onabort and self._onabort(msg) or None
251 def fatal(self, msg):
252 """Print an error message to stderr and abort with usage string."""
253 msg = '\nerror: %s\n' % msg
254 return self.usage(msg)
256 def parse(self, args):
257 """Parse a list of arguments and return (options, flags, extra).
259 In the returned tuple, "options" is an OptDict with known options,
260 "flags" is a list of option flags that were used on the command-line,
261 and "extra" is a list of positional arguments.
264 (flags,extra) = self.optfunc(args, self._shortopts, self._longopts)
265 except getopt.GetoptError as e:
268 opt = OptDict(aliases=self._aliases)
270 for k,v in self._defaults.items():
275 if k in ('h', '?', 'help', 'usage'):
277 if (self._aliases.get('#') and
278 k in ('0','1','2','3','4','5','6','7','8','9')):
279 v = int(k) # guaranteed to be exactly one digit
280 k, invert = self._aliases['#']
283 k, invert = opt._unalias(k)
284 if not self._hasparms[k]:
286 v = (opt._opts.get(k) or 0) + 1
289 opt[k] = _invert(v, invert)
290 return (opt,flags,extra)