diff options
author | Avery Pennarun <apenwarr@gmail.com> | 2010-09-03 22:58:44 -0700 |
---|---|---|
committer | Avery Pennarun <apenwarr@gmail.com> | 2010-09-03 23:00:26 -0700 |
commit | 5bf8687ce37dcd5bddd50fb38daaeae8c5215a9a (patch) | |
tree | 88ed195dfdf0bbbcbe7f3a2a32f8d6476d74f55e | |
parent | 6bdb9517fd8667f4d6385d0cf6ffe4d0d2c198c7 (diff) |
Import latest options.py from bup-0.17.
This has new support for default values in square brackets, so let's use
that.
-rwxr-xr-x | main.py | 8 | ||||
-rw-r--r-- | options.py | 105 |
2 files changed, 81 insertions, 32 deletions
@@ -49,7 +49,7 @@ sshuttle [-l [ip:]port] [-r [username@]sshserver[:port]] <subnets...> sshuttle --firewall <port> <subnets...> sshuttle --server -- -l,listen= transproxy to this ip address and port number [default=0] +l,listen= transproxy to this ip address and port number [0.0.0.0:0] H,auto-hosts scan for remote hostnames and update local /etc/hosts N,auto-nets automatically determine subnets to route r,remote= ssh hostname (and optional username) of remote sshuttle server @@ -57,9 +57,9 @@ x,exclude= exclude this subnet (can be used more than once) v,verbose increase debug message verbosity seed-hosts= with -H, use these hostnames for initial scan (comma-separated) noserver don't use a separate server process (mostly for debugging) -server [internal use only] -firewall [internal use only] -hostwatch [internal use only] +server (internal use only) +firewall (internal use only) +hostwatch (internal use only) """ o = options.Options('sshuttle', optspec) (opt, flags, extra) = o.parse(sys.argv[1:]) @@ -1,4 +1,10 @@ -import sys, textwrap, getopt, re +"""Command-line options parser. +With the help of an options spec string, easily parse command-line options. +""" +import sys +import textwrap +import getopt +import re class OptDict: def __init__(self): @@ -6,7 +12,7 @@ class OptDict: def __setitem__(self, k, v): self._opts[k] = v - + def __getitem__(self, k): return self._opts[k] @@ -14,17 +20,46 @@ class OptDict: return self[k] +def _default_onabort(msg): + sys.exit(97) + + +def _intify(v): + try: + vv = int(v or '') + if str(vv) == v: + return vv + except ValueError: + pass + return v + + class Options: - def __init__(self, exe, optspec, optfunc=getopt.gnu_getopt): + """Option parser. + When constructed, two strings are mandatory. The first one is the command + name showed before error messages. The second one is a string called an + optspec that specifies the synopsis and option flags and their description. + For more information about optspecs, consult the bup-options(1) man page. + + Two optional arguments specify an alternative parsing function and an + alternative behaviour on abort (after having output the usage string). + + By default, the parser function is getopt.gnu_getopt, and the abort + behaviour is to exit the program. + """ + def __init__(self, exe, optspec, optfunc=getopt.gnu_getopt, + onabort=_default_onabort): self.exe = exe self.optspec = optspec + self._onabort = onabort self.optfunc = optfunc self._aliases = {} self._shortopts = 'h?' self._longopts = ['help'] self._hasparms = {} + self._defaults = {} self._usagestr = self._gen_usage() - + def _gen_usage(self): out = [] lines = self.optspec.strip().split('\n') @@ -48,17 +83,23 @@ class Options: has_parm = 1 else: has_parm = 0 + g = re.search(r'\[([^\]]*)\]', extra) + if g: + defval = g.group(1) + else: + defval = None flagl = flags.split(',') flagl_nice = [] for f in flagl: - f_nice = re.sub(r'\W', '_', f) self._aliases[f] = flagl[0] - self._aliases[f_nice] = flagl[0] self._hasparms[f] = has_parm + self._defaults[f] = _intify(defval) if len(f) == 1: self._shortopts += f + (has_parm and ':' or '') flagl_nice.append('-' + f) else: + f_nice = re.sub(r'\W', '_', f) + self._aliases[f_nice] = flagl[0] assert(not f.startswith('no-')) # supported implicitly self._longopts.append(f + (has_parm and '=' or '')) self._longopts.append('no-' + f) @@ -74,45 +115,53 @@ class Options: else: out.append('\n') return ''.join(out).rstrip() + '\n' - - def usage(self): + + def usage(self, msg=""): + """Print usage string to stderr and abort.""" sys.stderr.write(self._usagestr) - sys.exit(97) + e = self._onabort and self._onabort(msg) or None + if e: + raise e def fatal(self, s): - sys.stderr.write('error: %s\n' % s) - return self.usage() - + """Print an error message to stderr and abort with usage string.""" + msg = 'error: %s\n' % s + sys.stderr.write(msg) + return self.usage(msg) + def parse(self, args): + """Parse a list of arguments and return (options, flags, extra). + + In the returned tuple, "options" is an OptDict with known options, + "flags" is a list of option flags that were used on the command-line, + and "extra" is a list of positional arguments. + """ try: (flags,extra) = self.optfunc(args, self._shortopts, self._longopts) except getopt.GetoptError, e: self.fatal(e) opt = OptDict() - for f in self._aliases.values(): - opt[f] = None + + for k,v in self._defaults.iteritems(): + k = self._aliases[k] + opt[k] = v + for (k,v) in flags: - while k.startswith('-'): - k = k[1:] - if k in ['h', '?', 'help']: + k = k.lstrip('-') + if k in ('h', '?', 'help'): self.usage() if k.startswith('no-'): k = self._aliases[k[3:]] - opt[k] = None + v = 0 else: k = self._aliases[k] if not self._hasparms[k]: assert(v == '') - opt[k] = (opt._opts.get(k) or 0) + 1 + v = (opt._opts.get(k) or 0) + 1 else: - try: - vv = int(v) - if str(vv) == v: - v = vv - except ValueError: - pass - opt[k] = v - for (f1,f2) in self._aliases.items(): - opt[f1] = opt[f2] + v = _intify(v) + opt[k] = v + for (f1,f2) in self._aliases.iteritems(): + opt[f1] = opt._opts.get(f2) return (opt,flags,extra) |