# This is *not* the official distribution of Optik. See # http://optik.sourceforge.net/ for the official distro. # # This combined module was converted from the "optik" package for Docutils use # by David Goodger (2002-06-12). Optik is slated for inclusion in the Python # standard library as OptionParser.py. Once Optik itself becomes a single # module, Docutils will include the official module and kill off this # temporary fork. """optik A powerful, extensible, and easy-to-use command-line parser for Python. By Greg Ward See http://optik.sourceforge.net/ """ # Copyright (c) 2001 Gregory P. Ward. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of the author nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. __revision__ = "$Id$" __version__ = "1.3+" import sys import os from types import TupleType, ListType, DictType, StringType from distutils.fancy_getopt import wrap_text SUPPRESS_HELP = "SUPPRESS"+"HELP" SUPPRESS_USAGE = "SUPPRESS"+"USAGE" # Not supplying a default is different from a default of None, # so we need an explicit "not supplied" value. NO_DEFAULT = "NO"+"DEFAULT" class OptikError (Exception): def __init__ (self, msg): self.msg = msg def __str__ (self): return self.msg class OptionError (OptikError): """ Raised if an Option instance is created with invalid or inconsistent arguments. """ def __init__ (self, msg, option): self.msg = msg self.option_id = str(option) def __str__ (self): if self.option_id: return "option %s: %s" % (self.option_id, self.msg) else: return self.msg class OptionConflictError (OptionError): """ Raised if conflicting options are added to an OptionParser. """ class OptionValueError (OptikError): """ Raised if an invalid option value is encountered on the command line. """ class BadOptionError (OptikError): """ Raised if an invalid or ambiguous option is seen on the command-line. """ _builtin_cvt = { "int" : (int, "integer"), "long" : (long, "long integer"), "float" : (float, "floating-point"), "complex" : (complex, "complex") } def check_builtin (option, opt, value): (cvt, what) = _builtin_cvt[option.type] try: return cvt(value) except ValueError: raise OptionValueError( #"%s: invalid %s argument %r" % (opt, what, value)) "option %s: invalid %s value: %r" % (opt, what, value)) def check_choice(option, opt, value): if value in option.choices: return value else: raise OptionValueError( "option %s: invalid choice: %r (choose one of %r)" % (opt, value, option.choices)) class Option: """ Instance attributes: _short_opts : [string] _long_opts : [string] option_string : string Set when help output is formatted. action : string type : string dest : string default : any nargs : int const : any choices : [string] callback : function callback_args : (any*) callback_kwargs : { string : any } help : string metavar : string """ # The list of instance attributes that may be set through # keyword args to the constructor. ATTRS = ['action', 'type', 'dest', 'default', 'nargs', 'const', 'choices', 'callback', 'callback_args', 'callback_kwargs', 'help', 'metavar'] # The set of actions allowed by option parsers. Explicitly listed # here so the constructor can validate its arguments. ACTIONS = ("store", "store_const", "store_true", "store_false", "append", "count", "callback", "help", "version") # The set of actions that involve storing a value somewhere; # also listed just for constructor argument validation. (If # the action is one of these, there must be a destination.) STORE_ACTIONS = ("store", "store_const", "store_true", "store_false", "append", "count") # The set of actions for which it makes sense to supply a value # type, ie. where we expect an argument to this option. TYPED_ACTIONS = ("store", "append", "callback") # The set of known types for option parsers. Again, listed here for # constructor argument validation. TYPES = ("string", "int", "long", "float", "complex", "choice") # Dictionary of argument checking functions, which convert and # validate option arguments according to the option type. # # Signature of checking functions is: # check(option : Option, opt : string, value : string) -> any # where # option is the Option instance calling the checker # opt is the actual option seen on the command-line # (eg. "-a", "--file") # value is the option argument seen on the command-line # # The return value should be in the appropriate Python type # for option.type -- eg. an integer if option.type == "int". # # If no checker is defined for a type, arguments will be # unchecked and remain strings. TYPE_CHECKER = { "int" : check_builtin, "long" : check_builtin, "float" : check_builtin, "complex" : check_builtin, "choice" : check_choice, } # CHECK_METHODS is a list of unbound method objects; they are called # by the constructor, in order, after all attributes are # initialized. The list is created and filled in later, after all # the methods are actually defined. (I just put it here because I # like to define and document all class attributes in the same # place.) Subclasses that add another _check_*() method should # define their own CHECK_METHODS list that adds their check method # to those from this class. CHECK_METHODS = None # -- Constructor/initialization methods ---------------------------- def __init__ (self, *opts, **attrs): # Set _short_opts, _long_opts attrs from 'opts' tuple opts = self._check_opt_strings(opts) self._set_opt_strings(opts) # Set all other attrs (action, type, etc.) from 'attrs' dict self._set_attrs(attrs) # Check all the attributes we just set. There are lots of # complicated interdependencies, but luckily they can be farmed # out to the _check_*() methods listed in CHECK_METHODS -- which # could be handy for subclasses! The one thing these all share # is that they raise OptionError if they discover a problem. for checker in self.CHECK_METHODS: checker(self) def _check_opt_strings (self, opts): # Filter out None because early versions of Optik had exactly # one short option and one long option, either of which # could be None. opts = filter(None, opts) if not opts: raise OptionError("at least one option string must be supplied", self) return opts def _set_opt_strings (self, opts): self._short_opts = [] self._long_opts = [] for opt in opts: if len(opt) < 2: raise OptionError( "invalid option string %r: " "must be at least two characters long" % opt, self) elif len(opt) == 2: if not (opt[0] == "-" and opt[1] != "-"): raise OptionError( "invalid short option string %r: " "must be of the form -x, (x any non-dash char)" % opt, self) self._short_opts.append(opt) else: if not (opt[0:2] == "--" and opt[2] != "-"): raise OptionError( "invalid long option string %r: " "must start with --, followed by non-dash" % opt, self) self._long_opts.append(opt) def _set_attrs (self, attrs): for attr in self.ATTRS: if attrs.has_key(attr): setattr(self, attr, attrs[attr]) del attrs[attr] else: if attr == 'default': setattr(self, attr, NO_DEFAULT) else: setattr(self, attr, None) if attrs: raise OptionError( "invalid keyword arguments: %s" % ", ".join(attrs.keys()), self) # -- Constructor validation methods -------------------------------- def _check_action (self): if self.action is None: self.action = "store" elif self.action not in self.ACTIONS: raise OptionError("invalid action: %r" % self.action, self) def _check_type (self): if self.type is None: # XXX should factor out another class attr here: list of # actions that *require* a type if self.action in ("store", "append"): if self.choices is not None: # The "choices" attribute implies "choice" type. self.type = "choice" else: # No type given? "string" is the most sensible default. self.type = "string" else: if self.type not in self.TYPES: raise OptionError("invalid option type: %r" % self.type, self) if self.action not in self.TYPED_ACTIONS: raise OptionError( "must not supply a type for action %r" % self.action, self) def _check_choice(self): if self.type == "choice": if self.choices is None: raise OptionError( "must supply a list of choices for type 'choice'", self) elif type(self.choices) not in (TupleType, ListType): raise OptionError( "choices must be a list of strings ('%s' supplied)" % str(type(self.choices)).split("'")[1], self) elif self.choices is not None: raise OptionError( "must not supply choices for type %r" % self.type, self) def _check_dest (self): if self.action in self.STORE_ACTIONS and self.dest is None: # No destination given, and we need one for this action. # Glean a destination from the first long option string, # or from the first short option string if no long options. if self._long_opts: # eg. "--foo-bar" -> "foo_bar" self.dest = self._long_opts[0][2:].replace('-', '_') else: self.dest = self._short_opts[0][1] def _check_const (self): if self.action != "store_const" and self.const is not None: raise OptionError( "'const' must not be supplied for action %r" % self.action, self) def _check_nargs (self): if self.action in self.TYPED_ACTIONS: if self.nargs is None: self.nargs = 1 elif self.nargs is not None: raise OptionError( "'nargs' must not be supplied for action %r" % self.action, self) def _check_callback (self): if self.action == "callback": if not callable(self.callback): raise OptionError( "callback not callable: %r" % self.callback, self) if (self.callback_args is not None and type(self.callback_args) is not TupleType): raise OptionError( "callback_args, if supplied, must be a tuple: not %r" % self.callback_args, self) if (self.callback_kwargs is not None and type(self.callback_kwargs) is not DictType): raise OptionError( "callback_kwargs, if supplied, must be a dict: not %r" % self.callback_kwargs, self) else: if self.callback is not None: raise OptionError( "callback supplied (%r) for non-callback option" % self.callback, self) if self.callback_args is not None: raise OptionError( "callback_args supplied for non-callback option", self) if self.callback_kwargs is not None: raise OptionError( "callback_kwargs supplied for non-callback option", self) CHECK_METHODS = [_check_action, _check_type, _check_choice, _check_dest, _check_const, _check_nargs, _check_callback] # -- Miscellaneous methods ----------------------------------------- def __str__ (self): if self._short_opts or self._long_opts: return "/".join(self._short_opts + self._long_opts) else: raise RuntimeError, "short_opts and long_opts both empty!" def takes_value (self): return self.type is not None # -- Processing methods -------------------------------------------- def check_value (self, opt, value): checker = self.TYPE_CHECKER.get(self.type) if checker is None: return value else: return checker(self, opt, value) def process (self, opt, value, values, parser): # First, convert the value(s) to the right type. Howl if any # value(s) are bogus. if value is not None: if self.nargs == 1: value = self.check_value(opt, value) else: value = tuple([self.check_value(opt, v) for v in value]) # And then take whatever action is expected of us. # This is a separate method to make life easier for # subclasses to add new actions. return self.take_action( self.action, self.dest, opt, value, values, parser) def take_action (self, action, dest, opt, value, values, parser): if action == "store": setattr(values, dest, value) elif action == "store_const": setattr(values, dest, self.const) elif action == "store_true": setattr(values, dest, 1) elif action == "store_false": setattr(values, dest, 0) elif action == "append": values.ensure_value(dest, []).append(value) elif action == "count": setattr(values, dest, values.ensure_value(dest, 0) + 1) elif action == "callback": args = self.callback_args or () kwargs = self.callback_kwargs or {} self.callback(self, opt, value, parser, *args, **kwargs) elif action == "help": parser.print_help() sys.exit(0) elif action == "version": parser.print_version() sys.exit(0) else: raise RuntimeError, "unknown action %r" % self.action return 1 # class Option # Some day, there might be many Option classes. As of Optik 1.3, the # preferred way to instantiate Options is indirectly, via make_option(), # which will become a factory function when there are many Option # classes. make_option = Option STD_HELP_OPTION = Option("-h", "--help", action="help", help="show this help message and exit") STD_VERSION_OPTION = Option("--version", action="version", help="show program's version number and exit") class OptionContainer: """ Abstract base class. 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: option_list : [Option] The list of Option objects contained by this OptionContainer. _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. [1] _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. [1] defaults : { string : any } Dictionary mapping option destination names to default values for each destination. [1] [1] These mappings are common to (shared by) all components of the controlling OptionParser, where they are initially created. """ standard_option_list = [] def __init__(self, parser, description=None, option_list=None): # List of Options is local to this OptionContainer. self.option_list = [] # The shared mappings are stored in the parser. self._short_opt = parser._short_opt self._long_opt = parser._long_opt self.defaults = parser.defaults # self.format = parser.format self.option_class = parser.option_class self.conflict_handler = parser.conflict_handler self.set_description(description) self._populate_option_list(option_list) def set_description(self, description): self.description = description def _populate_option_list(self, option_list, version=None, help=None): if self.standard_option_list: self.add_options(self.standard_option_list) if option_list: self.add_options(option_list) if version: self.add_option(STD_VERSION_OPTION) if help: self.add_option(STD_HELP_OPTION) # -- 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" % ", ".join([co[0] for co in conflict_opts]), option) elif handler == "resolve": # new in 1.2 for (opt, c_option) in conflict_opts: if opt.startswith("--"): 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): c_option.container.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 StringType: option = 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: %r" % option else: raise TypeError, "invalid arguments" self._check_conflict(option) self.option_list.append(option) option.container = self 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 %r" % opt_str) for opt in option._short_opts: del self._short_opt[opt] for opt in option._long_opts: del self._long_opt[opt] option.container.option_list.remove(option) # -- Feedback methods ---------------------------------------------- def get_option_help(self): if not self.option_list: return "" result = [] # list of strings to "".join() later for option in self.option_list: if not option.help is SUPPRESS_HELP: result.append(self.format.format_option(option)) return "".join(result) def get_description_help(self): if self.description: return self.format.format_description(self.description) else: return "" def get_help(self): result = "" if self.description: result = self.get_description_help() + "\n" return result + self.get_option_help() class OptionGroup(OptionContainer): def __init__(self, parser, title, description=None): OptionContainer.__init__(self, parser, description) self.parser = parser self.title = title self.description = description def set_title(self, title): self.title = title def get_help(self): result = self.format.format_heading(self.title) self.format.increase_nesting() result += OptionContainer.get_help(self) self.format.decrease_nesting() return result class Values: def __init__ (self, defaults=None): if defaults: for (attr, val) in defaults.items(): setattr(self, attr, val) def __repr__(self): return "%s(%r)" % (self.__class__, self.__dict__) 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: %r" % 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(OptionContainer): """ 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])). 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. """ def __init__ (self, usage=None, description=None, option_list=None, option_class=Option, version=None, help=1, conflict_handler="error", format=None): """Override OptionContainer.__init__.""" self.set_usage(usage) self.set_description(description) self.option_class = option_class self.version = version self.set_conflict_handler(conflict_handler) self.allow_interspersed_args = 1 if not format: format = Indented() self.format = format # 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 and STD_HELP_OPTION globals (if 'version' # and/or 'help' supplied). self._populate_option_list(option_list, version=version, help=help) self._init_parsing_state() # -- Private methods ----------------------------------------------- # (used by the constructor) def _create_option_list (self): self.option_list = [] self.option_groups = [] self._short_opt = {} # single letter -> Option instance self._long_opt = {} # long option -> Option instance self.defaults = {} # maps option dest -> default value 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 = "%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 %r" % 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) # -- OptionGroup methods ------------------------------------------- def add_option_group(self, group): self.option_groups.append(group) def get_option_group(self, opt_str): option = (self._short_opt.get(opt_str) or self._long_opt.get(opt_str)) if option and option.container is not self: return option.container return None # -- 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) = arg.split("=", 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 += 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 # -- Feedback 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.exit("%s: error: %s" % (get_prog_name(), msg)) def get_usage_help(self): if self.usage: return self.format.format_usage( self.usage.replace("%prog", get_prog_name())) else: return "" 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 self.usage: print >>file, self.get_usage_help() def get_version_help(self): if self.version: return self.version.replace("%prog", get_prog_name()) else: return "" 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 self.version: print >>file, self.get_version_help() def get_option_help(self): self.format.store_option_strings(self) result = [] result.append(self.format.format_heading("Options")) self.format.increase_nesting() if self.option_list: result.append(OptionContainer.get_option_help(self)) result.append("\n") for group in self.option_groups: result.append(group.get_help()) result.append("\n") self.format.decrease_nesting() # Drop the last "\n", or the header if no options or option groups: return "".join(result[:-1]) def get_help(self): result = [] if self.usage: result.append(self.get_usage_help() + "\n") if self.description: result.append(self.get_description_help() + "\n") result.append(self.get_option_help()) return "".join(result) 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). """ if file is None: file = sys.stdout file.write(self.get_help()) # class OptionParser class HelpFormat: """ "--help" output format; abstract base class (Strategy design pattern). Instance attributes: indent_increment : int The number of columns to indent per nesting level. max_help_position : int The maximum starting column for option help text. help_position : int The calculated starting column for option help text; initially the same as the maximum. width : int The overall width (in columns) for output. current_indent : int In columns, calculated. level : int Increased for each additional nesting level. help_width : int Number of columns available for option help text, calculated. """ def __init__(self, indent_increment, max_help_position, width, short_first): self.indent_increment = indent_increment self.help_position = self.max_help_position = max_help_position self.width = width self.current_indent = 0 self.level = 0 self.help_width = width - max_help_position if short_first: self.format_option_strings = self.format_option_strings_short_first else: self.format_option_strings = self.format_option_strings_long_first def increase_nesting(self): self.current_indent += self.indent_increment self.level += 1 def decrease_nesting(self): self.current_indent -= self.indent_increment assert self.current_indent >= 0, "Indent decreased below 0." self.level -= 1 def format_usage(self, usage): raise NotImplementedError def format_heading(self, heading): raise NotImplementedError def format_description(self, description): desc_width = self.width - self.current_indent desc_lines = wrap_text(description, desc_width) result = ["%*s%s\n" % (self.current_indent, "", line) for line in desc_lines] return "".join(result) def format_option(self, option): # 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 result = [] opts = option.option_strings opt_width = self.help_position - self.current_indent - 2 if len(opts) > opt_width: opts = "%*s%s\n" % (self.current_indent, "", opts) indent_first = self.help_position else: # start help on same line as opts opts = "%*s%-*s " % (self.current_indent, "", opt_width, opts) indent_first = 0 result.append(opts) if option.help: help_lines = wrap_text(option.help, self.help_width) result.append("%*s%s\n" % (indent_first, "", help_lines[0])) result.extend(["%*s%s\n" % (self.help_position, "", line) for line in help_lines[1:]]) elif opts[-1] != "\n": result.append("\n") return "".join(result) def store_option_strings(self, parser): self.increase_nesting() max_len = 0 for opt in parser.option_list: strings = self.format_option_strings(opt) opt.option_strings = strings max_len = max(max_len, len(strings) + self.current_indent) self.increase_nesting() for group in parser.option_groups: for opt in group.option_list: strings = self.format_option_strings(opt) opt.option_strings = strings max_len = max(max_len, len(strings) + self.current_indent) self.decrease_nesting() self.decrease_nesting() self.help_position = min(max_len + 2, self.max_help_position) def format_option_strings(self, option): """Return a comma-separated list of option strigs & metavariables.""" raise NotImplementedError( "Virtual method; use format_option_strings_short_first or " "format_option_strings_long_first instead.") def format_option_strings_short_first(self, option): opts = [] # list of "-a" or "--foo=FILE" strings takes_value = option.takes_value() if takes_value: metavar = option.metavar or option.dest.upper() 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) return ", ".join(opts) def format_option_strings_long_first(self, option): opts = [] # list of "-a" or "--foo=FILE" strings takes_value = option.takes_value() if takes_value: metavar = option.metavar or option.dest.upper() for lopt in option._long_opts: opts.append(lopt + "=" + metavar) for sopt in option._short_opts: opts.append(sopt + metavar) else: for opt in option._long_opts + option._short_opts: opts.append(opt) return ", ".join(opts) class Indented(HelpFormat): """Formats help with indented section bodies.""" def __init__(self, indent_increment=2, max_help_position=24, width=78, short_first=1): HelpFormat.__init__(self, indent_increment, max_help_position, width, short_first) def format_usage(self, usage): return "Usage: %s\n" % usage def format_heading(self, heading): return "%*s%s:\n" % (self.current_indent, "", heading) class Titled(HelpFormat): """Formats help with underlined section headers.""" def __init__(self, indent_increment=0, max_help_position=24, width=78, short_first=None): HelpFormat.__init__(self, indent_increment, max_help_position, width, short_first) def format_usage(self, usage): return "%s %s\n" % (self.format_heading("Usage"), usage) def format_heading(self, heading): return "%s\n%s\n" % (heading, "=-"[self.level] * len(heading)) 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 = [word for word in wordmap.keys() if word.startswith(s)] # 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, ", ".join(possibilities))) def get_prog_name (): return os.path.basename(sys.argv[0])