731 lines
26 KiB
Python
731 lines
26 KiB
Python
|
"""optik.option_parser
|
||
|
|
||
|
Provides the OptionParser and Values classes.
|
||
|
"""
|
||
|
|
||
|
__revision__ = "/home/scons/scons/branch.0/baseline/src/engine/SCons/Optik/option_parser.py 0.96.1.D001 2004/08/23 09:55:29 knight"
|
||
|
|
||
|
# Original Optik revision this is based on:
|
||
|
__Optik_revision__ = "option_parser.py,v 1.38.2.1 2002/07/23 01:51:14 gward Exp"
|
||
|
|
||
|
# Copyright (c) 2001 Gregory P. Ward. All rights reserved.
|
||
|
# See the README.txt distributed with Optik for licensing terms.
|
||
|
|
||
|
# created 2001/10/17, GPW (from optik.py)
|
||
|
|
||
|
import sys, os
|
||
|
import string
|
||
|
import types
|
||
|
from SCons.Optik.option import Option, NO_DEFAULT
|
||
|
from SCons.Optik.errors import OptionConflictError, OptionValueError, BadOptionError
|
||
|
|
||
|
def get_prog_name ():
|
||
|
return os.path.basename(sys.argv[0])
|
||
|
|
||
|
|
||
|
SUPPRESS_HELP = "SUPPRESS"+"HELP"
|
||
|
SUPPRESS_USAGE = "SUPPRESS"+"USAGE"
|
||
|
|
||
|
class Values:
|
||
|
|
||
|
def __init__ (self, defaults=None):
|
||
|
if defaults:
|
||
|
for (attr, val) in defaults.items():
|
||
|
setattr(self, attr, val)
|
||
|
|
||
|
|
||
|
def _update_careful (self, dict):
|
||
|
"""
|
||
|
Update the option values from an arbitrary dictionary, but only
|
||
|
use keys from dict that already have a corresponding attribute
|
||
|
in self. Any keys in dict without a corresponding attribute
|
||
|
are silently ignored.
|
||
|
"""
|
||
|
for attr in dir(self):
|
||
|
if dict.has_key(attr):
|
||
|
dval = dict[attr]
|
||
|
if dval is not None:
|
||
|
setattr(self, attr, dval)
|
||
|
|
||
|
def _update_loose (self, dict):
|
||
|
"""
|
||
|
Update the option values from an arbitrary dictionary,
|
||
|
using all keys from the dictionary regardless of whether
|
||
|
they have a corresponding attribute in self or not.
|
||
|
"""
|
||
|
self.__dict__.update(dict)
|
||
|
|
||
|
def _update (self, dict, mode):
|
||
|
if mode == "careful":
|
||
|
self._update_careful(dict)
|
||
|
elif mode == "loose":
|
||
|
self._update_loose(dict)
|
||
|
else:
|
||
|
raise ValueError, "invalid update mode: %s" % (repr(mode),)
|
||
|
|
||
|
def read_module (self, modname, mode="careful"):
|
||
|
__import__(modname)
|
||
|
mod = sys.modules[modname]
|
||
|
self._update(vars(mod), mode)
|
||
|
|
||
|
def read_file (self, filename, mode="careful"):
|
||
|
vars = {}
|
||
|
execfile(filename, vars)
|
||
|
self._update(vars, mode)
|
||
|
|
||
|
def ensure_value (self, attr, value):
|
||
|
if not hasattr(self, attr) or getattr(self, attr) is None:
|
||
|
setattr(self, attr, value)
|
||
|
return getattr(self, attr)
|
||
|
|
||
|
|
||
|
class OptionParser:
|
||
|
"""
|
||
|
Class attributes:
|
||
|
standard_option_list : [Option]
|
||
|
list of standard options that will be accepted by all instances
|
||
|
of this parser class (intended to be overridden by subclasses).
|
||
|
|
||
|
Instance attributes:
|
||
|
usage : string
|
||
|
a usage string for your program. Before it is displayed
|
||
|
to the user, "%prog" will be expanded to the name of
|
||
|
your program (os.path.basename(sys.argv[0])).
|
||
|
option_list : [Option]
|
||
|
the list of all options accepted on the command-line of
|
||
|
this program
|
||
|
_short_opt : { string : Option }
|
||
|
dictionary mapping short option strings, eg. "-f" or "-X",
|
||
|
to the Option instances that implement them. If an Option
|
||
|
has multiple short option strings, it will appears in this
|
||
|
dictionary multiple times.
|
||
|
_long_opt : { string : Option }
|
||
|
dictionary mapping long option strings, eg. "--file" or
|
||
|
"--exclude", to the Option instances that implement them.
|
||
|
Again, a given Option can occur multiple times in this
|
||
|
dictionary.
|
||
|
defaults : { string : any }
|
||
|
dictionary mapping option destination names to default
|
||
|
values for each destination.
|
||
|
|
||
|
allow_interspersed_args : boolean = true
|
||
|
if true, positional arguments may be interspersed with options.
|
||
|
Assuming -a and -b each take a single argument, the command-line
|
||
|
-ablah foo bar -bboo baz
|
||
|
will be interpreted the same as
|
||
|
-ablah -bboo -- foo bar baz
|
||
|
If this flag were false, that command line would be interpreted as
|
||
|
-ablah -- foo bar -bboo baz
|
||
|
-- ie. we stop processing options as soon as we see the first
|
||
|
non-option argument. (This is the tradition followed by
|
||
|
Python's getopt module, Perl's Getopt::Std, and other argument-
|
||
|
parsing libraries, but it is generally annoying to users.)
|
||
|
|
||
|
rargs : [string]
|
||
|
the argument list currently being parsed. Only set when
|
||
|
parse_args() is active, and continually trimmed down as
|
||
|
we consume arguments. Mainly there for the benefit of
|
||
|
callback options.
|
||
|
largs : [string]
|
||
|
the list of leftover arguments that we have skipped while
|
||
|
parsing options. If allow_interspersed_args is false, this
|
||
|
list is always empty.
|
||
|
values : Values
|
||
|
the set of option values currently being accumulated. Only
|
||
|
set when parse_args() is active. Also mainly for callbacks.
|
||
|
|
||
|
Because of the 'rargs', 'largs', and 'values' attributes,
|
||
|
OptionParser is not thread-safe. If, for some perverse reason, you
|
||
|
need to parse command-line arguments simultaneously in different
|
||
|
threads, use different OptionParser instances.
|
||
|
|
||
|
"""
|
||
|
|
||
|
standard_option_list = []
|
||
|
|
||
|
|
||
|
def __init__ (self,
|
||
|
usage=None,
|
||
|
option_list=None,
|
||
|
option_class=Option,
|
||
|
version=None,
|
||
|
conflict_handler="error"):
|
||
|
self.set_usage(usage)
|
||
|
self.option_class = option_class
|
||
|
self.version = version
|
||
|
self.set_conflict_handler(conflict_handler)
|
||
|
self.allow_interspersed_args = 1
|
||
|
|
||
|
# Create the various lists and dicts that constitute the
|
||
|
# "option list". See class docstring for details about
|
||
|
# each attribute.
|
||
|
self._create_option_list()
|
||
|
|
||
|
# Populate the option list; initial sources are the
|
||
|
# standard_option_list class attribute, the 'option_list'
|
||
|
# argument, and the STD_VERSION_OPTION global (if 'version'
|
||
|
# supplied).
|
||
|
self._populate_option_list(option_list)
|
||
|
|
||
|
self._init_parsing_state()
|
||
|
|
||
|
# -- Private methods -----------------------------------------------
|
||
|
# (used by the constructor)
|
||
|
|
||
|
def _create_option_list (self):
|
||
|
self.option_list = []
|
||
|
self._short_opt = {} # single letter -> Option instance
|
||
|
self._long_opt = {} # long option -> Option instance
|
||
|
self.defaults = {} # maps option dest -> default value
|
||
|
|
||
|
def _populate_option_list (self, option_list):
|
||
|
if self.standard_option_list:
|
||
|
self.add_options(self.standard_option_list)
|
||
|
if option_list:
|
||
|
self.add_options(option_list)
|
||
|
|
||
|
def _init_parsing_state (self):
|
||
|
# These are set in parse_args() for the convenience of callbacks.
|
||
|
self.rargs = None
|
||
|
self.largs = None
|
||
|
self.values = None
|
||
|
|
||
|
|
||
|
# -- Simple modifier methods ---------------------------------------
|
||
|
|
||
|
def set_usage (self, usage):
|
||
|
if usage is None:
|
||
|
self.usage = "usage: %prog [options]"
|
||
|
elif usage is SUPPRESS_USAGE:
|
||
|
self.usage = None
|
||
|
else:
|
||
|
self.usage = usage
|
||
|
|
||
|
def enable_interspersed_args (self):
|
||
|
self.allow_interspersed_args = 1
|
||
|
|
||
|
def disable_interspersed_args (self):
|
||
|
self.allow_interspersed_args = 0
|
||
|
|
||
|
def set_conflict_handler (self, handler):
|
||
|
if handler not in ("ignore", "error", "resolve"):
|
||
|
raise ValueError, "invalid conflict_resolution value %s" % (repr(handler),)
|
||
|
self.conflict_handler = handler
|
||
|
|
||
|
def set_default (self, dest, value):
|
||
|
self.defaults[dest] = value
|
||
|
|
||
|
def set_defaults (self, **kwargs):
|
||
|
self.defaults.update(kwargs)
|
||
|
|
||
|
def get_default_values(self):
|
||
|
return Values(self.defaults)
|
||
|
|
||
|
|
||
|
# -- Option-adding methods -----------------------------------------
|
||
|
|
||
|
def _check_conflict (self, option):
|
||
|
conflict_opts = []
|
||
|
for opt in option._short_opts:
|
||
|
if self._short_opt.has_key(opt):
|
||
|
conflict_opts.append((opt, self._short_opt[opt]))
|
||
|
for opt in option._long_opts:
|
||
|
if self._long_opt.has_key(opt):
|
||
|
conflict_opts.append((opt, self._long_opt[opt]))
|
||
|
|
||
|
if conflict_opts:
|
||
|
handler = self.conflict_handler
|
||
|
if handler == "ignore": # behaviour for Optik 1.0, 1.1
|
||
|
pass
|
||
|
elif handler == "error": # new in 1.2
|
||
|
raise OptionConflictError(
|
||
|
"conflicting option string(s): %s"
|
||
|
% string.join( map( lambda x: x[0], conflict_opts),", "),
|
||
|
option)
|
||
|
elif handler == "resolve": # new in 1.2
|
||
|
for (opt, c_option) in conflict_opts:
|
||
|
if len(opt)>2 and opt[:2]=="--":
|
||
|
c_option._long_opts.remove(opt)
|
||
|
del self._long_opt[opt]
|
||
|
else:
|
||
|
c_option._short_opts.remove(opt)
|
||
|
del self._short_opt[opt]
|
||
|
if not (c_option._short_opts or c_option._long_opts):
|
||
|
self.option_list.remove(c_option)
|
||
|
|
||
|
|
||
|
def add_option (self, *args, **kwargs):
|
||
|
"""add_option(Option)
|
||
|
add_option(opt_str, ..., kwarg=val, ...)
|
||
|
"""
|
||
|
if type(args[0]) is types.StringType:
|
||
|
option = apply(self.option_class,args, kwargs)
|
||
|
elif len(args) == 1 and not kwargs:
|
||
|
option = args[0]
|
||
|
if not isinstance(option, Option):
|
||
|
raise TypeError, "not an Option instance: %s" % (repr(option),)
|
||
|
else:
|
||
|
raise TypeError, "invalid arguments"
|
||
|
|
||
|
self._check_conflict(option)
|
||
|
|
||
|
self.option_list.append(option)
|
||
|
for opt in option._short_opts:
|
||
|
self._short_opt[opt] = option
|
||
|
for opt in option._long_opts:
|
||
|
self._long_opt[opt] = option
|
||
|
|
||
|
if option.dest is not None: # option has a dest, we need a default
|
||
|
if option.default is not NO_DEFAULT:
|
||
|
self.defaults[option.dest] = option.default
|
||
|
elif not self.defaults.has_key(option.dest):
|
||
|
self.defaults[option.dest] = None
|
||
|
|
||
|
def add_options (self, option_list):
|
||
|
for option in option_list:
|
||
|
self.add_option(option)
|
||
|
|
||
|
|
||
|
# -- Option query/removal methods ----------------------------------
|
||
|
|
||
|
def get_option (self, opt_str):
|
||
|
return (self._short_opt.get(opt_str) or
|
||
|
self._long_opt.get(opt_str))
|
||
|
|
||
|
def has_option (self, opt_str):
|
||
|
return (self._short_opt.has_key(opt_str) or
|
||
|
self._long_opt.has_key(opt_str))
|
||
|
|
||
|
|
||
|
def remove_option (self, opt_str):
|
||
|
option = self._short_opt.get(opt_str)
|
||
|
if option is None:
|
||
|
option = self._long_opt.get(opt_str)
|
||
|
if option is None:
|
||
|
raise ValueError("no such option %s" % (repr(opt_str),))
|
||
|
|
||
|
for opt in option._short_opts:
|
||
|
del self._short_opt[opt]
|
||
|
for opt in option._long_opts:
|
||
|
del self._long_opt[opt]
|
||
|
self.option_list.remove(option)
|
||
|
|
||
|
|
||
|
# -- Option-parsing methods ----------------------------------------
|
||
|
|
||
|
def _get_args (self, args):
|
||
|
if args is None:
|
||
|
return sys.argv[1:]
|
||
|
else:
|
||
|
return args[:] # don't modify caller's list
|
||
|
|
||
|
def parse_args (self, args=None, values=None):
|
||
|
"""
|
||
|
parse_args(args : [string] = sys.argv[1:],
|
||
|
values : Values = None)
|
||
|
-> (values : Values, args : [string])
|
||
|
|
||
|
Parse the command-line options found in 'args' (default:
|
||
|
sys.argv[1:]). Any errors result in a call to 'error()', which
|
||
|
by default prints the usage message to stderr and calls
|
||
|
sys.exit() with an error message. On success returns a pair
|
||
|
(values, args) where 'values' is an Values instance (with all
|
||
|
your option values) and 'args' is the list of arguments left
|
||
|
over after parsing options.
|
||
|
"""
|
||
|
rargs = self._get_args(args)
|
||
|
if values is None:
|
||
|
values = self.get_default_values()
|
||
|
|
||
|
# Store the halves of the argument list as attributes for the
|
||
|
# convenience of callbacks:
|
||
|
# rargs
|
||
|
# the rest of the command-line (the "r" stands for
|
||
|
# "remaining" or "right-hand")
|
||
|
# largs
|
||
|
# the leftover arguments -- ie. what's left after removing
|
||
|
# options and their arguments (the "l" stands for "leftover"
|
||
|
# or "left-hand")
|
||
|
self.rargs = rargs
|
||
|
self.largs = largs = []
|
||
|
self.values = values
|
||
|
|
||
|
try:
|
||
|
stop = self._process_args(largs, rargs, values)
|
||
|
except (BadOptionError, OptionValueError), err:
|
||
|
self.error(err.msg)
|
||
|
|
||
|
args = largs + rargs
|
||
|
return self.check_values(values, args)
|
||
|
|
||
|
def check_values (self, values, args):
|
||
|
"""
|
||
|
check_values(values : Values, args : [string])
|
||
|
-> (values : Values, args : [string])
|
||
|
|
||
|
Check that the supplied option values and leftover arguments are
|
||
|
valid. Returns the option values and leftover arguments
|
||
|
(possibly adjusted, possibly completely new -- whatever you
|
||
|
like). Default implementation just returns the passed-in
|
||
|
values; subclasses may override as desired.
|
||
|
"""
|
||
|
return (values, args)
|
||
|
|
||
|
def _process_args (self, largs, rargs, values):
|
||
|
"""_process_args(largs : [string],
|
||
|
rargs : [string],
|
||
|
values : Values)
|
||
|
|
||
|
Process command-line arguments and populate 'values', consuming
|
||
|
options and arguments from 'rargs'. If 'allow_interspersed_args' is
|
||
|
false, stop at the first non-option argument. If true, accumulate any
|
||
|
interspersed non-option arguments in 'largs'.
|
||
|
"""
|
||
|
while rargs:
|
||
|
arg = rargs[0]
|
||
|
# We handle bare "--" explicitly, and bare "-" is handled by the
|
||
|
# standard arg handler since the short arg case ensures that the
|
||
|
# len of the opt string is greater than 1.
|
||
|
if arg == "--":
|
||
|
del rargs[0]
|
||
|
return
|
||
|
elif arg[0:2] == "--":
|
||
|
# process a single long option (possibly with value(s))
|
||
|
self._process_long_opt(rargs, values)
|
||
|
elif arg[:1] == "-" and len(arg) > 1:
|
||
|
# process a cluster of short options (possibly with
|
||
|
# value(s) for the last one only)
|
||
|
self._process_short_opts(rargs, values)
|
||
|
elif self.allow_interspersed_args:
|
||
|
largs.append(arg)
|
||
|
del rargs[0]
|
||
|
else:
|
||
|
return # stop now, leave this arg in rargs
|
||
|
|
||
|
# Say this is the original argument list:
|
||
|
# [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)]
|
||
|
# ^
|
||
|
# (we are about to process arg(i)).
|
||
|
#
|
||
|
# Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of
|
||
|
# [arg0, ..., arg(i-1)] (any options and their arguments will have
|
||
|
# been removed from largs).
|
||
|
#
|
||
|
# The while loop will usually consume 1 or more arguments per pass.
|
||
|
# If it consumes 1 (eg. arg is an option that takes no arguments),
|
||
|
# then after _process_arg() is done the situation is:
|
||
|
#
|
||
|
# largs = subset of [arg0, ..., arg(i)]
|
||
|
# rargs = [arg(i+1), ..., arg(N-1)]
|
||
|
#
|
||
|
# If allow_interspersed_args is false, largs will always be
|
||
|
# *empty* -- still a subset of [arg0, ..., arg(i-1)], but
|
||
|
# not a very interesting subset!
|
||
|
|
||
|
def _match_long_opt (self, opt):
|
||
|
"""_match_long_opt(opt : string) -> string
|
||
|
|
||
|
Determine which long option string 'opt' matches, ie. which one
|
||
|
it is an unambiguous abbrevation for. Raises BadOptionError if
|
||
|
'opt' doesn't unambiguously match any long option string.
|
||
|
"""
|
||
|
return _match_abbrev(opt, self._long_opt)
|
||
|
|
||
|
def _process_long_opt (self, rargs, values):
|
||
|
arg = rargs.pop(0)
|
||
|
|
||
|
# Value explicitly attached to arg? Pretend it's the next
|
||
|
# argument.
|
||
|
if "=" in arg:
|
||
|
(opt, next_arg) = string.split(arg,"=", 1)
|
||
|
rargs.insert(0, next_arg)
|
||
|
had_explicit_value = 1
|
||
|
else:
|
||
|
opt = arg
|
||
|
had_explicit_value = 0
|
||
|
|
||
|
opt = self._match_long_opt(opt)
|
||
|
option = self._long_opt[opt]
|
||
|
if option.takes_value():
|
||
|
nargs = option.nargs
|
||
|
if len(rargs) < nargs:
|
||
|
if nargs == 1:
|
||
|
self.error("%s option requires a value" % opt)
|
||
|
else:
|
||
|
self.error("%s option requires %d values"
|
||
|
% (opt, nargs))
|
||
|
elif nargs == 1:
|
||
|
value = rargs.pop(0)
|
||
|
else:
|
||
|
value = tuple(rargs[0:nargs])
|
||
|
del rargs[0:nargs]
|
||
|
|
||
|
elif had_explicit_value:
|
||
|
self.error("%s option does not take a value" % opt)
|
||
|
|
||
|
else:
|
||
|
value = None
|
||
|
|
||
|
option.process(opt, value, values, self)
|
||
|
|
||
|
def _process_short_opts (self, rargs, values):
|
||
|
arg = rargs.pop(0)
|
||
|
stop = 0
|
||
|
i = 1
|
||
|
for ch in arg[1:]:
|
||
|
opt = "-" + ch
|
||
|
option = self._short_opt.get(opt)
|
||
|
i = i+1 # we have consumed a character
|
||
|
|
||
|
if not option:
|
||
|
self.error("no such option: %s" % opt)
|
||
|
if option.takes_value():
|
||
|
# Any characters left in arg? Pretend they're the
|
||
|
# next arg, and stop consuming characters of arg.
|
||
|
if i < len(arg):
|
||
|
rargs.insert(0, arg[i:])
|
||
|
stop = 1
|
||
|
|
||
|
nargs = option.nargs
|
||
|
if len(rargs) < nargs:
|
||
|
if nargs == 1:
|
||
|
self.error("%s option requires a value" % opt)
|
||
|
else:
|
||
|
self.error("%s option requires %s values"
|
||
|
% (opt, nargs))
|
||
|
elif nargs == 1:
|
||
|
value = rargs.pop(0)
|
||
|
else:
|
||
|
value = tuple(rargs[0:nargs])
|
||
|
del rargs[0:nargs]
|
||
|
|
||
|
else: # option doesn't take a value
|
||
|
value = None
|
||
|
|
||
|
option.process(opt, value, values, self)
|
||
|
|
||
|
if stop:
|
||
|
break
|
||
|
|
||
|
|
||
|
# -- Output/error methods ------------------------------------------
|
||
|
|
||
|
def error (self, msg):
|
||
|
"""error(msg : string)
|
||
|
|
||
|
Print a usage message incorporating 'msg' to stderr and exit.
|
||
|
If you override this in a subclass, it should not return -- it
|
||
|
should either exit or raise an exception.
|
||
|
"""
|
||
|
self.print_usage(sys.stderr)
|
||
|
sys.stderr.write("\nSCons error: %s\n" % msg)
|
||
|
sys.exit(2)
|
||
|
|
||
|
def print_usage (self, file=None):
|
||
|
"""print_usage(file : file = stdout)
|
||
|
|
||
|
Print the usage message for the current program (self.usage) to
|
||
|
'file' (default stdout). Any occurence of the string "%prog" in
|
||
|
self.usage is replaced with the name of the current program
|
||
|
(basename of sys.argv[0]). Does nothing if self.usage is empty
|
||
|
or not defined.
|
||
|
"""
|
||
|
if file is None:
|
||
|
file = sys.stdout
|
||
|
if self.usage:
|
||
|
usage = string.replace(self.usage,"%prog", get_prog_name())
|
||
|
file.write(usage + "\n")
|
||
|
|
||
|
def print_version (self, file=None):
|
||
|
"""print_version(file : file = stdout)
|
||
|
|
||
|
Print the version message for this program (self.version) to
|
||
|
'file' (default stdout). As with print_usage(), any occurence
|
||
|
of "%prog" in self.version is replaced by the current program's
|
||
|
name. Does nothing if self.version is empty or undefined.
|
||
|
"""
|
||
|
if file is None:
|
||
|
file = sys.stdout
|
||
|
if self.version:
|
||
|
version = string.replace(self.version,"%prog", get_prog_name())
|
||
|
file.write(version+"\n")
|
||
|
|
||
|
def print_help (self, file=None):
|
||
|
"""print_help(file : file = stdout)
|
||
|
|
||
|
Print an extended help message, listing all options and any
|
||
|
help text provided with them, to 'file' (default stdout).
|
||
|
"""
|
||
|
# SCons: don't import wrap_text from distutils, use the
|
||
|
# copy we've included below, so we can avoid being dependent
|
||
|
# on having the right version of distutils installed.
|
||
|
#from distutils.fancy_getopt import wrap_text
|
||
|
|
||
|
if file is None:
|
||
|
file = sys.stdout
|
||
|
|
||
|
self.print_usage(file)
|
||
|
|
||
|
# The help for each option consists of two parts:
|
||
|
# * the opt strings and metavars
|
||
|
# eg. ("-x", or "-fFILENAME, --file=FILENAME")
|
||
|
# * the user-supplied help string
|
||
|
# eg. ("turn on expert mode", "read data from FILENAME")
|
||
|
#
|
||
|
# If possible, we write both of these on the same line:
|
||
|
# -x turn on expert mode
|
||
|
#
|
||
|
# But if the opt string list is too long, we put the help
|
||
|
# string on a second line, indented to the same column it would
|
||
|
# start in if it fit on the first line.
|
||
|
# -fFILENAME, --file=FILENAME
|
||
|
# read data from FILENAME
|
||
|
|
||
|
file.write("Options:\n")
|
||
|
width = 78 # assume 80 cols for now
|
||
|
|
||
|
option_help = [] # list of (string, string) tuples
|
||
|
lengths = []
|
||
|
|
||
|
for option in self.option_list:
|
||
|
takes_value = option.takes_value()
|
||
|
if takes_value:
|
||
|
metavar = option.metavar or string.upper(option.dest)
|
||
|
|
||
|
opts = [] # list of "-a" or "--foo=FILE" strings
|
||
|
if option.help is SUPPRESS_HELP:
|
||
|
continue
|
||
|
|
||
|
if takes_value:
|
||
|
for sopt in option._short_opts:
|
||
|
opts.append(sopt + ' ' + metavar)
|
||
|
for lopt in option._long_opts:
|
||
|
opts.append(lopt + "=" + metavar)
|
||
|
else:
|
||
|
for opt in option._short_opts + option._long_opts:
|
||
|
opts.append(opt)
|
||
|
|
||
|
opts = string.join(opts,", ")
|
||
|
option_help.append((opts, option.help))
|
||
|
lengths.append(len(opts))
|
||
|
|
||
|
max_opts = min(max(lengths), 26)
|
||
|
|
||
|
for (opts, help) in option_help:
|
||
|
# how much to indent lines 2 .. N of help text
|
||
|
indent_rest = 2 + max_opts + 2
|
||
|
help_width = width - indent_rest
|
||
|
|
||
|
if len(opts) > max_opts:
|
||
|
opts = " " + opts + "\n"
|
||
|
indent_first = indent_rest
|
||
|
else: # start help on same line as opts
|
||
|
opts = " %-*s " % (max_opts, opts)
|
||
|
indent_first = 0
|
||
|
|
||
|
file.write(opts)
|
||
|
|
||
|
if help:
|
||
|
help_lines = wrap_text(help, help_width)
|
||
|
file.write( "%*s%s\n" % (indent_first, "", help_lines[0]))
|
||
|
for line in help_lines[1:]:
|
||
|
file.write(" %*s%s\n" % (indent_rest, "", line))
|
||
|
elif opts[-1] != "\n":
|
||
|
file.write("\n")
|
||
|
|
||
|
# class OptionParser
|
||
|
|
||
|
|
||
|
def _match_abbrev (s, wordmap):
|
||
|
"""_match_abbrev(s : string, wordmap : {string : Option}) -> string
|
||
|
|
||
|
Return the string key in 'wordmap' for which 's' is an unambiguous
|
||
|
abbreviation. If 's' is found to be ambiguous or doesn't match any of
|
||
|
'words', raise BadOptionError.
|
||
|
"""
|
||
|
# Is there an exact match?
|
||
|
if wordmap.has_key(s):
|
||
|
return s
|
||
|
else:
|
||
|
# Isolate all words with s as a prefix.
|
||
|
possibilities = []
|
||
|
ls = len(s)
|
||
|
for word in wordmap.keys():
|
||
|
if len(word)>=ls and word[:ls]==s:
|
||
|
possibilities.append(word)
|
||
|
# No exact match, so there had better be just one possibility.
|
||
|
if len(possibilities) == 1:
|
||
|
return possibilities[0]
|
||
|
elif not possibilities:
|
||
|
raise BadOptionError("no such option: %s" % s)
|
||
|
else:
|
||
|
# More than one possible completion: ambiguous prefix.
|
||
|
raise BadOptionError("ambiguous option: %s (%s?)"
|
||
|
% (s, string.join(possibilities,", ")))
|
||
|
|
||
|
# SCons: Include a snarfed copy of wrap_text(), so we're not dependent
|
||
|
# on the right version of distutils being installed.
|
||
|
import re
|
||
|
|
||
|
WS_TRANS = string.maketrans(string.whitespace, ' ' * len(string.whitespace))
|
||
|
|
||
|
def wrap_text (text, width):
|
||
|
"""wrap_text(text : string, width : int) -> [string]
|
||
|
|
||
|
Split 'text' into multiple lines of no more than 'width' characters
|
||
|
each, and return the list of strings that results.
|
||
|
"""
|
||
|
|
||
|
if text is None:
|
||
|
return []
|
||
|
if len(text) <= width:
|
||
|
return [text]
|
||
|
|
||
|
text = string.expandtabs(text)
|
||
|
text = string.translate(text, WS_TRANS)
|
||
|
chunks = re.split(r'( +|-+)', text)
|
||
|
chunks = filter(None, chunks) # ' - ' results in empty strings
|
||
|
lines = []
|
||
|
|
||
|
while chunks:
|
||
|
|
||
|
cur_line = [] # list of chunks (to-be-joined)
|
||
|
cur_len = 0 # length of current line
|
||
|
|
||
|
while chunks:
|
||
|
l = len(chunks[0])
|
||
|
if cur_len + l <= width: # can squeeze (at least) this chunk in
|
||
|
cur_line.append(chunks[0])
|
||
|
del chunks[0]
|
||
|
cur_len = cur_len + l
|
||
|
else: # this line is full
|
||
|
# drop last chunk if all space
|
||
|
if cur_line and cur_line[-1][0] == ' ':
|
||
|
del cur_line[-1]
|
||
|
break
|
||
|
|
||
|
if chunks: # any chunks left to process?
|
||
|
|
||
|
# if the current line is still empty, then we had a single
|
||
|
# chunk that's too big too fit on a line -- so we break
|
||
|
# down and break it up at the line width
|
||
|
if cur_len == 0:
|
||
|
cur_line.append(chunks[0][0:width])
|
||
|
chunks[0] = chunks[0][width:]
|
||
|
|
||
|
# all-whitespace chunks at the end of a line can be discarded
|
||
|
# (and we know from the re.split above that if a chunk has
|
||
|
# *any* whitespace, it is *all* whitespace)
|
||
|
if chunks[0][0] == ' ':
|
||
|
del chunks[0]
|
||
|
|
||
|
# and store this line in the list-of-all-lines -- as a single
|
||
|
# string, of course!
|
||
|
lines.append(string.join(cur_line, ''))
|
||
|
|
||
|
# while chunks
|
||
|
|
||
|
return lines
|
||
|
|
||
|
# wrap_text ()
|