summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAvery Pennarun <apenwarr@gmail.com>2010-09-03 22:58:44 -0700
committerAvery Pennarun <apenwarr@gmail.com>2010-09-03 23:00:26 -0700
commit5bf8687ce37dcd5bddd50fb38daaeae8c5215a9a (patch)
tree88ed195dfdf0bbbcbe7f3a2a32f8d6476d74f55e
parent6bdb9517fd8667f4d6385d0cf6ffe4d0d2c198c7 (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-xmain.py8
-rw-r--r--options.py105
2 files changed, 81 insertions, 32 deletions
diff --git a/main.py b/main.py
index d92d3bd..6d6b528 100755
--- a/main.py
+++ b/main.py
@@ -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:])
diff --git a/options.py b/options.py
index b40d6b4..cc185e2 100644
--- a/options.py
+++ b/options.py
@@ -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)