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 AND CONTRIBUTORS ``AS
20 # IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
23 # <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24 # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28 # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30 # OF THE POSSIBILITY OF SUCH DAMAGE.
32 """Command-line options parser.
33 With the help of an options spec string, easily parse command-line options.
35 An options spec is made up of two parts, separated by a line with two dashes.
36 The first part is the synopsis of the command and the second one specifies
37 options, one per line.
39 Each non-empty line in the synopsis gives a set of options that can be used
42 Option flags must be at the begining of the line and multiple flags are
43 separated by commas. Usually, options have a short, one character flag, and a
44 longer one, but the short one can be omitted.
46 Long option flags are used as the option's key for the OptDict produced when
49 When the flag definition is ended with an equal sign, the option takes
50 one string as an argument, and that string will be converted to an
51 integer when possible. Otherwise, the option does not take an argument
52 and corresponds to a boolean flag that is true when the option is
53 given on the command line.
55 The option's description is found at the right of its flags definition, after
56 one or more spaces. The description ends at the end of the line. If the
57 description contains text enclosed in square brackets, the enclosed text will
58 be used as the option's default value.
60 Options can be put in different groups. Options in the same group must be on
61 consecutive lines. Groups are formed by inserting a line that begins with a
62 space. The text on that line will be output after an empty line.
65 from __future__ import absolute_import
66 import sys, os, textwrap, getopt, re, struct
79 def _invert(v, invert):
85 def _remove_negative_kv(k, v):
86 if k.startswith('no-') or k.startswith('no_'):
91 class OptDict(object):
92 """Dictionary that exposes keys as attributes.
94 Keys can be set or accessed with a "no-" or "no_" prefix to negate the
97 def __init__(self, aliases):
99 self._aliases = aliases
101 def _unalias(self, k):
102 k, reinvert = _remove_negative_kv(k, False)
103 k, invert = self._aliases[k]
104 return k, invert ^ reinvert
106 def __setitem__(self, k, v):
107 k, invert = self._unalias(k)
108 self._opts[k] = _invert(v, invert)
110 def __getitem__(self, k):
111 k, invert = self._unalias(k)
112 return _invert(self._opts[k], invert)
114 def __getattr__(self, k):
118 def _default_onabort(msg):
132 if not fcntl and termios:
137 forced = os.environ.get('BUP_TTY_WIDTH', None)
140 s = struct.pack("HHHH", 0, 0, 0, 0)
142 s = fcntl.ioctl(sys.stderr.fileno(), termios.TIOCGWINSZ, s)
145 ysize, xsize, ypix, xpix = struct.unpack('HHHH', s)
151 When constructed, a string called an option spec must be given. It
152 specifies the synopsis and option flags and their description. For more
153 information about option specs, see the docstring at the top of this file.
155 Two optional arguments specify an alternative parsing function and an
156 alternative behaviour on abort (after having output the usage string).
158 By default, the parser function is getopt.gnu_getopt, and the abort
159 behaviour is to exit the program.
161 def __init__(self, optspec, optfunc=getopt.gnu_getopt,
162 onabort=_default_onabort):
163 self.optspec = optspec
164 self._onabort = onabort
165 self.optfunc = optfunc
167 self._shortopts = 'h?'
168 self._longopts = ['help', 'usage']
171 self._usagestr = self._gen_usage() # this also parses the optspec
173 def _gen_usage(self):
175 lines = self.optspec.strip().split('\n')
181 out.append('%s: %s\n' % (first_syn and 'usage' or ' or', l))
184 last_was_option = False
187 if l.startswith(' '):
188 out.append('%s%s\n' % (last_was_option and '\n' or '',
190 last_was_option = False
192 (flags,extra) = (l + ' ').split(' ', 1)
193 extra = extra.strip()
194 if flags.endswith('='):
199 g = re.search(r'\[([^\]]*)\]$', extra)
201 defval = _intify(g.group(1))
204 flagl = flags.split(',')
206 flag_main, invert_main = _remove_negative_kv(flagl[0], False)
207 self._defaults[flag_main] = _invert(defval, invert_main)
209 f,invert = _remove_negative_kv(_f, 0)
210 self._aliases[f] = (flag_main, invert_main ^ invert)
211 self._hasparms[f] = has_parm
213 self._shortopts += '0123456789'
214 flagl_nice.append('-#')
216 self._shortopts += f + (has_parm and ':' or '')
217 flagl_nice.append('-' + f)
219 f_nice = re.sub(r'\W', '_', f)
220 self._aliases[f_nice] = (flag_main,
221 invert_main ^ invert)
222 self._longopts.append(f + (has_parm and '=' or ''))
223 self._longopts.append('no-' + f)
224 flagl_nice.append('--' + _f)
225 flags_nice = ', '.join(flagl_nice)
228 prefix = ' %-20s ' % flags_nice
229 argtext = '\n'.join(textwrap.wrap(extra, width=_tty_width(),
230 initial_indent=prefix,
231 subsequent_indent=' '*28))
232 out.append(argtext + '\n')
233 last_was_option = True
236 last_was_option = False
237 return ''.join(out).rstrip() + '\n'
239 def usage(self, msg=""):
240 """Print usage string to stderr and abort."""
241 sys.stderr.write(self._usagestr)
243 sys.stderr.write(msg)
244 e = self._onabort and self._onabort(msg) or None
248 def fatal(self, msg):
249 """Print an error message to stderr and abort with usage string."""
250 msg = '\nerror: %s\n' % msg
251 return self.usage(msg)
253 def parse(self, args):
254 """Parse a list of arguments and return (options, flags, extra).
256 In the returned tuple, "options" is an OptDict with known options,
257 "flags" is a list of option flags that were used on the command-line,
258 and "extra" is a list of positional arguments.
261 (flags,extra) = self.optfunc(args, self._shortopts, self._longopts)
262 except getopt.GetoptError as e:
265 opt = OptDict(aliases=self._aliases)
267 for k,v in self._defaults.items():
272 if k in ('h', '?', 'help', 'usage'):
274 if (self._aliases.get('#') and
275 k in ('0','1','2','3','4','5','6','7','8','9')):
276 v = int(k) # guaranteed to be exactly one digit
277 k, invert = self._aliases['#']
280 k, invert = opt._unalias(k)
281 if not self._hasparms[k]:
283 v = (opt._opts.get(k) or 0) + 1
286 opt[k] = _invert(v, invert)
287 return (opt,flags,extra)
289 def parse_bytes(self, args):
290 if sys.version_info[0] > 2:
291 args = [x.decode(errors='surrogateescape') for x in args]
292 return self.parse(args)