Upgrade optparse module and tests to Optik 1.5a1:

* add expansion of default values in help text: the string
    "%default" in an option's help string is expanded to str() of
    that option's default value, or "none" if no default value.
  * bug #955889: option default values that happen to be strings are
    now processed in the same way as values from the command line; this
    allows generation of nicer help when using custom types.  Can
    be disabled with parser.set_process_default_values(False).
  * bug #960515: don't crash when generating help for callback
    options that specify 'type', but not 'dest' or 'metavar'.
  * feature #815264: change the default help format for short options
    that take an argument from e.g. "-oARG" to "-o ARG"; add
    set_short_opt_delimiter() and set_long_opt_delimiter() methods to
    HelpFormatter to allow (slight) customization of the formatting.
  * patch #736940: internationalize Optik: all built-in user-
    targeted literal strings are passed through gettext.gettext().  (If
    you want translations (.po files), they're not included with Python
    -- you'll find them in the Optik source distribution from
    http://optik.sourceforge.net/ .)
  * bug #878453: respect $COLUMNS environment variable for
    wrapping help output.
  * feature #988122: expand "%prog" in the 'description' passed
    to OptionParser, just like in the 'usage' and 'version' strings.
    (This is *not* done in the 'description' passed to OptionGroup.)
This commit is contained in:
Greg Ward 2004-07-31 16:15:44 +00:00
parent 7357222d0e
commit eba20e6015
2 changed files with 746 additions and 340 deletions

View file

@ -16,13 +16,11 @@
# Python developers: please do not make changes to this file, since
# it is automatically generated from the Optik source code.
__version__ = "1.4.1+"
__version__ = "1.5a1"
__all__ = ['Option',
'SUPPRESS_HELP',
'SUPPRESS_USAGE',
'STD_HELP_OPTION',
'STD_VERSION_OPTION',
'Values',
'OptionContainer',
'OptionGroup',
@ -37,7 +35,8 @@
'BadOptionError']
__copyright__ = """
Copyright (c) 2001-2003 Gregory P. Ward. All rights reserved.
Copyright (c) 2001-2004 Gregory P. Ward. All rights reserved.
Copyright (c) 2002-2004 Python Software Foundation. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
@ -70,6 +69,17 @@
import sys, os
import types
import textwrap
from gettext import gettext as _
def _repr(self):
return "<%s at 0x%x: %s>" % (self.__class__.__name__, id(self), self)
# This file was generated from:
# Id: option_parser.py,v 1.67 2004/07/24 23:21:21 gward Exp
# Id: option.py,v 1.33 2004/07/24 23:21:21 gward Exp
# Id: help.py,v 1.15 2004/07/24 23:21:21 gward Exp
# Id: errors.py,v 1.9 2004/07/24 23:21:21 gward Exp
class OptParseError (Exception):
def __init__(self, msg):
@ -120,6 +130,8 @@ class HelpFormatter:
formatting help; by default IndentedHelpFormatter is used.
Instance attributes:
parser : OptionParser
the controlling OptionParser instance
indent_increment : int
the number of columns to indent per nesting level
max_help_position : int
@ -128,27 +140,71 @@ class HelpFormatter:
the calculated starting column for option help text;
initially the same as the maximum
width : int
total number of columns for output
total number of columns for output (pass None to constructor for
this value to be taken from the $COLUMNS environment variable)
level : int
current indentation level
current_indent : int
current indentation level (in columns)
help_width : int
number of columns available for option help text (calculated)
default_tag : str
text to replace with each option's default value, "%default"
by default. Set to false value to disable default value expansion.
option_strings : { Option : str }
maps Option instances to the snippet of help text explaining
the syntax of that option, e.g. "-h, --help" or
"-fFILE, --file=FILE"
_short_opt_fmt : str
format string controlling how short options with values are
printed in help text. Must be either "%s%s" ("-fFILE") or
"%s %s" ("-f FILE"), because those are the two syntaxes that
Optik supports.
_long_opt_fmt : str
similar but for long options; must be either "%s %s" ("--file FILE")
or "%s=%s" ("--file=FILE").
"""
NO_DEFAULT_VALUE = "none"
def __init__(self,
indent_increment,
max_help_position,
width,
short_first):
self.parser = None
self.indent_increment = indent_increment
self.help_position = self.max_help_position = max_help_position
if width is None:
try:
width = int(os.environ['COLUMNS'])
except (KeyError, ValueError):
width = 80
width -= 2
self.width = width
self.current_indent = 0
self.level = 0
self.help_width = width - max_help_position
self.help_width = None # computed later
self.short_first = short_first
self.default_tag = "%default"
self.option_strings = {}
self._short_opt_fmt = "%s %s"
self._long_opt_fmt = "%s=%s"
def set_parser(self, parser):
self.parser = parser
def set_short_opt_delimiter(self, delim):
if delim not in ("", " "):
raise ValueError(
"invalid metavar delimiter for short options: %r" % delim)
self._short_opt_fmt = "%s" + delim + "%s"
def set_long_opt_delimiter(self, delim):
if delim not in ("=", " "):
raise ValueError(
"invalid metavar delimiter for long options: %r" % delim)
self._long_opt_fmt = "%s" + delim + "%s"
def indent(self):
self.current_indent += self.indent_increment
@ -166,11 +222,24 @@ def format_heading (self, heading):
raise NotImplementedError, "subclasses must implement"
def format_description(self, description):
if not description:
return ""
desc_width = self.width - self.current_indent
indent = " "*self.current_indent
return textwrap.fill(description, desc_width,
return textwrap.fill(description,
desc_width,
initial_indent=indent,
subsequent_indent=indent)
subsequent_indent=indent) + "\n"
def expand_default(self, option):
if self.parser is None or not self.default_tag:
return option.help
default_value = self.parser.defaults.get(option.dest)
if default_value is NO_DEFAULT or default_value is None:
default_value = self.NO_DEFAULT_VALUE
return option.help.replace(self.default_tag, str(default_value))
def format_option(self, option):
# The help for each option consists of two parts:
@ -188,7 +257,7 @@ def format_option (self, option):
# -fFILENAME, --file=FILENAME
# read data from FILENAME
result = []
opts = option.option_strings
opts = self.option_strings[option]
opt_width = self.help_position - self.current_indent - 2
if len(opts) > opt_width:
opts = "%*s%s\n" % (self.current_indent, "", opts)
@ -198,7 +267,8 @@ def format_option (self, option):
indent_first = 0
result.append(opts)
if option.help:
help_lines = textwrap.wrap(option.help, self.help_width)
help_text = self.expand_default(option)
help_lines = textwrap.wrap(help_text, 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:]])
@ -211,24 +281,27 @@ def store_option_strings (self, parser):
max_len = 0
for opt in parser.option_list:
strings = self.format_option_strings(opt)
opt.option_strings = strings
self.option_strings[opt] = strings
max_len = max(max_len, len(strings) + self.current_indent)
self.indent()
for group in parser.option_groups:
for opt in group.option_list:
strings = self.format_option_strings(opt)
opt.option_strings = strings
self.option_strings[opt] = strings
max_len = max(max_len, len(strings) + self.current_indent)
self.dedent()
self.dedent()
self.help_position = min(max_len + 2, self.max_help_position)
self.help_width = self.width - self.help_position
def format_option_strings(self, option):
"""Return a comma-separated list of option strings & metavariables."""
if option.takes_value():
metavar = option.metavar or option.dest.upper()
short_opts = [sopt + metavar for sopt in option._short_opts]
long_opts = [lopt + "=" + metavar for lopt in option._long_opts]
short_opts = [self._short_opt_fmt % (sopt, metavar)
for sopt in option._short_opts]
long_opts = [self._long_opt_fmt % (lopt, metavar)
for lopt in option._long_opts]
else:
short_opts = option._short_opts
long_opts = option._long_opts
@ -247,13 +320,13 @@ class IndentedHelpFormatter (HelpFormatter):
def __init__(self,
indent_increment=2,
max_help_position=24,
width=79,
width=None,
short_first=1):
HelpFormatter.__init__(
self, indent_increment, max_help_position, width, short_first)
def format_usage(self, usage):
return "usage: %s\n" % usage
return _("usage: %s\n") % usage
def format_heading(self, heading):
return "%*s%s:\n" % (self.current_indent, "", heading)
@ -266,22 +339,22 @@ class TitledHelpFormatter (HelpFormatter):
def __init__(self,
indent_increment=0,
max_help_position=24,
width=79,
width=None,
short_first=0):
HelpFormatter.__init__ (
self, indent_increment, max_help_position, width, short_first)
def format_usage(self, usage):
return "%s %s\n" % (self.format_heading("Usage"), 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))
_builtin_cvt = { "int" : (int, "integer"),
"long" : (long, "long integer"),
"float" : (float, "floating-point"),
"complex" : (complex, "complex") }
_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]
@ -289,8 +362,7 @@ def check_builtin (option, opt, value):
return cvt(value)
except ValueError:
raise OptionValueError(
#"%s: invalid %s argument %r" % (opt, what, value))
"option %s: invalid %s value: %r" % (opt, what, value))
_("option %s: invalid %s value: %r") % (opt, what, value))
def check_choice(option, opt, value):
if value in option.choices:
@ -298,12 +370,12 @@ def check_choice(option, opt, value):
else:
choices = ", ".join(map(repr, option.choices))
raise OptionValueError(
"option %s: invalid choice: %r (choose from %s)"
_("option %s: invalid choice: %r (choose from %s)")
% (opt, value, choices))
# Not supplying a default is different from a default of None,
# so we need an explicit "not supplied" value.
NO_DEFAULT = "NO"+"DEFAULT"
NO_DEFAULT = ("NO", "DEFAULT")
class Option:
@ -495,6 +567,12 @@ def _check_type (self):
# No type given? "string" is the most sensible default.
self.type = "string"
else:
# Allow type objects as an alternative to their names.
if type(self.type) is type:
self.type = self.type.__name__
if self.type == "str":
self.type = "string"
if self.type not in self.TYPES:
raise OptionError("invalid option type: %r" % self.type, self)
if self.action not in self.TYPED_ACTIONS:
@ -515,8 +593,12 @@ def _check_choice(self):
"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.
# No destination given, and we need one for this action. The
# self.type check is for callbacks that take a value.
takes_value = (self.action in self.STORE_ACTIONS or
self.type is not None)
if self.dest is None and takes_value:
# Glean a destination from the first long option string,
# or from the first short option string if no long options.
if self._long_opts:
@ -582,9 +664,17 @@ def _check_callback (self):
def __str__(self):
return "/".join(self._short_opts + self._long_opts)
__repr__ = _repr
def takes_value(self):
return self.type is not None
def get_opt_string(self):
if self._long_opts:
return self._long_opts[0]
else:
return self._short_opts[0]
# -- Processing methods --------------------------------------------
@ -595,15 +685,18 @@ def check_value (self, opt, value):
else:
return checker(self, opt, value)
def convert_value(self, opt, value):
if value is not None:
if self.nargs == 1:
return self.check_value(opt, value)
else:
return tuple([self.check_value(opt, v) for v in 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])
value = self.convert_value(opt, value)
# And then take whatever action is expected of us.
# This is a separate method to make life easier for
@ -642,19 +735,18 @@ def take_action (self, action, dest, opt, value, values, parser):
# class Option
def get_prog_name ():
return os.path.basename(sys.argv[0])
SUPPRESS_HELP = "SUPPRESS"+"HELP"
SUPPRESS_USAGE = "SUPPRESS"+"USAGE"
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")
# For compatibility with Python 2.2
try:
True, False
except NameError:
(True, False) = (1, 0)
try:
basestring
except NameError:
basestring = (str, unicode)
class Values:
@ -664,9 +756,21 @@ def __init__ (self, defaults=None):
for (attr, val) in defaults.items():
setattr(self, attr, val)
def __repr__ (self):
return ("<%s at 0x%x: %r>"
% (self.__class__.__name__, id(self), self.__dict__))
def __str__(self):
return str(self.__dict__)
__repr__ = _repr
def __eq__(self, other):
if isinstance(other, Values):
return self.__dict__ == other.__dict__
elif isinstance(other, dict):
return self.__dict__ == other
else:
return false
def __ne__(self, other):
return not (self == other)
def _update_careful(self, dict):
"""
@ -780,6 +884,9 @@ def set_conflict_handler (self, handler):
def set_description(self, description):
self.description = description
def get_description(self):
return self.description
# -- Option-adding methods -----------------------------------------
@ -882,17 +989,15 @@ def format_option_help (self, formatter):
return "".join(result)
def format_description(self, formatter):
if self.description:
return formatter.format_description(self.description)
else:
return ""
return formatter.format_description(self.get_description())
def format_help(self, formatter):
result = []
if self.description:
desc = self.format_description(formatter) + "\n"
else:
desc = ""
return desc + self.format_option_help(formatter)
result.append(self.format_description(formatter))
if self.option_list:
result.append(self.format_option_help(formatter))
return "\n".join(result)
class OptionGroup (OptionContainer):
@ -937,7 +1042,12 @@ class OptionParser (OptionContainer):
the name of the current program (to override
os.path.basename(sys.argv[0])).
allow_interspersed_args : boolean = true
option_groups : [OptionGroup]
list of option groups in this parser (option groups are
irrelevant for parsing the command-line, but very useful
for generating help)
allow_interspersed_args : bool = 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
@ -950,6 +1060,14 @@ class OptionParser (OptionContainer):
Python's getopt module, Perl's Getopt::Std, and other argument-
parsing libraries, but it is generally annoying to users.)
process_default_values : bool = true
if true, option default values are processed similarly to option
values from the command line: that is, they are passed to the
type-checking function for the option's type (as long as the
default value is a string). (This really only matters if you
have defined custom types; see SF bug #955889.) Set it to false
to restore the behaviour of Optik 1.4.1 and earlier.
rargs : [string]
the argument list currently being parsed. Only set when
parse_args() is active, and continually trimmed down as
@ -980,22 +1098,24 @@ def __init__ (self,
conflict_handler="error",
description=None,
formatter=None,
add_help_option=1,
add_help_option=True,
prog=None):
OptionContainer.__init__(
self, option_class, conflict_handler, description)
self.set_usage(usage)
self.prog = prog
self.version = version
self.allow_interspersed_args = 1
self.allow_interspersed_args = True
self.process_default_values = True
if formatter is None:
formatter = IndentedHelpFormatter()
self.formatter = formatter
self.formatter.set_parser(self)
# Populate the option list; initial sources are the
# standard_option_list class attribute, the 'option_list'
# argument, and the STD_VERSION_OPTION (if 'version' supplied)
# and STD_HELP_OPTION globals.
# argument, and (if applicable) the _add_version_option() and
# _add_help_option() methods.
self._populate_option_list(option_list,
add_help=add_help_option)
@ -1009,15 +1129,25 @@ def _create_option_list (self):
self.option_groups = []
self._create_option_mappings()
def _populate_option_list (self, option_list, add_help=1):
def _add_help_option(self):
self.add_option("-h", "--help",
action="help",
help=_("show this help message and exit"))
def _add_version_option(self):
self.add_option("--version",
action="version",
help=_("show program's version number and exit"))
def _populate_option_list(self, option_list, add_help=True):
if self.standard_option_list:
self.add_options(self.standard_option_list)
if option_list:
self.add_options(option_list)
if self.version:
self.add_option(STD_VERSION_OPTION)
self._add_version_option()
if add_help:
self.add_option(STD_HELP_OPTION)
self._add_help_option()
def _init_parsing_state(self):
# These are set in parse_args() for the convenience of callbacks.
@ -1025,30 +1155,28 @@ def _init_parsing_state (self):
self.largs = None
self.values = None
def _get_prog_name(self):
if self.prog:
return self.prog
else:
return get_prog_name()
# -- Simple modifier methods ---------------------------------------
def set_usage(self, usage):
if usage is None:
self.usage = "%prog [options]"
self.usage = _("%prog [options]")
elif usage is SUPPRESS_USAGE:
self.usage = None
elif usage.lower().startswith("usage: "):
# for backwards compatibility with Optik 1.3 and earlier
# For backwards compatibility with Optik 1.3 and earlier.
elif usage.startswith("usage:" + " "):
self.usage = usage[7:]
else:
self.usage = usage
def enable_interspersed_args(self):
self.allow_interspersed_args = 1
self.allow_interspersed_args = True
def disable_interspersed_args(self):
self.allow_interspersed_args = 0
self.allow_interspersed_args = False
def set_process_default_values(self, process):
self.process_default_values = process
def set_default(self, dest, value):
self.defaults[dest] = value
@ -1056,9 +1184,26 @@ def set_default (self, dest, value):
def set_defaults(self, **kwargs):
self.defaults.update(kwargs)
def _get_all_options(self):
options = self.option_list[:]
for group in self.option_groups:
options.extend(group.option_list)
return options
def get_default_values(self):
if not self.process_default_values:
# Old, pre-Optik 1.5 behaviour.
return Values(self.defaults)
defaults = self.defaults.copy()
for option in self._get_all_options():
default = defaults.get(option.dest)
if isinstance(default, basestring):
opt_str = option.get_opt_string()
defaults[option.dest] = option.check_value(opt_str, default)
return Values(defaults)
# -- OptionGroup methods -------------------------------------------
@ -1214,10 +1359,10 @@ def _process_long_opt (self, rargs, values):
if "=" in arg:
(opt, next_arg) = arg.split("=", 1)
rargs.insert(0, next_arg)
had_explicit_value = 1
had_explicit_value = True
else:
opt = arg
had_explicit_value = 0
had_explicit_value = False
opt = self._match_long_opt(opt)
option = self._long_opt[opt]
@ -1225,9 +1370,9 @@ def _process_long_opt (self, rargs, values):
nargs = option.nargs
if len(rargs) < nargs:
if nargs == 1:
self.error("%s option requires a value" % opt)
self.error(_("%s option requires an argument") % opt)
else:
self.error("%s option requires %d values"
self.error(_("%s option requires %d arguments")
% (opt, nargs))
elif nargs == 1:
value = rargs.pop(0)
@ -1236,7 +1381,7 @@ def _process_long_opt (self, rargs, values):
del rargs[0:nargs]
elif had_explicit_value:
self.error("%s option does not take a value" % opt)
self.error(_("%s option does not take a value") % opt)
else:
value = None
@ -1245,7 +1390,7 @@ def _process_long_opt (self, rargs, values):
def _process_short_opts(self, rargs, values):
arg = rargs.pop(0)
stop = 0
stop = False
i = 1
for ch in arg[1:]:
opt = "-" + ch
@ -1253,20 +1398,20 @@ def _process_short_opts (self, rargs, values):
i += 1 # we have consumed a character
if not option:
self.error("no such option: %s" % opt)
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
stop = True
nargs = option.nargs
if len(rargs) < nargs:
if nargs == 1:
self.error("%s option requires a value" % opt)
self.error(_("%s option requires an argument") % opt)
else:
self.error("%s option requires %s values"
self.error(_("%s option requires %d arguments")
% (opt, nargs))
elif nargs == 1:
value = rargs.pop(0)
@ -1285,6 +1430,18 @@ def _process_short_opts (self, rargs, values):
# -- Feedback methods ----------------------------------------------
def get_prog_name(self):
if self.prog is None:
return os.path.basename(sys.argv[0])
else:
return self.prog
def expand_prog_name(self, s):
return s.replace("%prog", self.get_prog_name())
def get_description(self):
return self.expand_prog_name(self.description)
def error(self, msg):
"""error(msg : string)
@ -1293,12 +1450,13 @@ def error (self, msg):
should either exit or raise an exception.
"""
self.print_usage(sys.stderr)
sys.exit("%s: error: %s" % (self._get_prog_name(), msg))
sys.stderr.write("%s: error: %s\n" % (self.get_prog_name(), msg))
sys.exit(2) # command-line usage error
def get_usage(self):
if self.usage:
return self.formatter.format_usage(
self.usage.replace("%prog", self._get_prog_name()))
self.expand_prog_name(self.usage))
else:
return ""
@ -1316,7 +1474,7 @@ def print_usage (self, file=None):
def get_version(self):
if self.version:
return self.version.replace("%prog", self._get_prog_name())
return self.expand_prog_name(self.version)
else:
return ""
@ -1336,7 +1494,7 @@ def format_option_help (self, formatter=None):
formatter = self.formatter
formatter.store_option_strings(self)
result = []
result.append(formatter.format_heading("options"))
result.append(formatter.format_heading(_("options")))
formatter.indent()
if self.option_list:
result.append(OptionContainer.format_option_help(self, formatter))
@ -1390,10 +1548,10 @@ def _match_abbrev (s, wordmap):
if len(possibilities) == 1:
return possibilities[0]
elif not possibilities:
raise BadOptionError("no such option: %s" % s)
raise BadOptionError(_("no such option: %s") % s)
else:
# More than one possible completion: ambiguous prefix.
raise BadOptionError("ambiguous option: %s (%s?)"
raise BadOptionError(_("ambiguous option: %s (%s?)")
% (s, ", ".join(possibilities)))

View file

@ -20,14 +20,7 @@
from optparse import make_option, Option, IndentedHelpFormatter, \
TitledHelpFormatter, OptionParser, OptionContainer, OptionGroup, \
SUPPRESS_HELP, SUPPRESS_USAGE, OptionError, OptionConflictError, \
BadOptionError, OptionValueError
from optparse import _match_abbrev
# Do the right thing with boolean values for all known Python versions.
try:
True, False
except NameError:
(True, False) = (1, 0)
BadOptionError, OptionValueError, _match_abbrev
class BaseTest(unittest.TestCase):
def assertParseOK(self, args, expected_opts, expected_positional_args):
@ -60,50 +53,62 @@ def assertParseOK(self, args, expected_opts, expected_positional_args):
return (options, positional_args)
def assertRaises(self, func, expected_exception, expected_output,
def assertRaises(self,
func,
args,
kwargs,
expected_exception,
expected_output,
get_output=None,
funcargs=[], funckwargs={}):
exact_match=False):
"""Assert the expected exception is raised when calling a function.
Also check whether the right error message is given for a given error.
Keyword arguments:
func -- The function to be called.
expected_exception -- The exception that should be raised.
expected_output -- The output we expect to see.
get_output -- The function to call to get the output.
funcargs -- The arguments `func` should be called with.
funckwargs -- The keyword arguments `func` should be called with.
Arguments:
func -- the function to call
args -- positional arguments to `func`
kwargs -- keyword arguments to `func`
expected_exception -- exception that should be raised
expected_output -- output we expect to see
get_output -- function to call to get the output
exact_match -- whether output must exactly match expected output,
or merely contain it
Returns the exception raised for further testing.
"""
if args is None:
args = ()
if kwargs is None:
kwargs = {}
if get_output is None:
get_output = self.exception
try:
out = func(*funcargs, **funckwargs)
out = func(*args, **kwargs)
except expected_exception, err:
output = get_output(err)
actual_output = get_output(err)
self.failUnless(output.find(expected_output) != -1,
"""
Message was:
%(output)s
Should contain:
%(expected_output)s
Function called:
%(func)s
With args/kwargs:
%(funcargs)s/%(funckwargs)s""" % locals())
if exact_match:
match = actual_output == expected_exception
else:
match = actual_output.find(expected_output) != -1
self.assert_(match,
"""mismatched output
expected output:
'''%(expected_output)s'''
actual output:
'''%(actual_output)s'''
""" % locals())
return err
else:
self.fail("""
No %(expected_exception)s raised.
Function called:
%(func)s
With args/kwargs:
%(funcargs)s/%(funckwargs)s""" % locals ())
self.fail("""expected exception %(expected_exception)s not raised
called %(func)r
with args %(args)r
and kwargs %(kwargs)r
""" % locals ())
# -- Functions to be used as the get_output argument to assertRaises ------
@ -113,23 +118,38 @@ def exception(self, err):
def redirected_stdout(self, err):
return sys.stdout.getvalue()
def redirected_stderr(self, err):
return sys.stderr.getvalue()
# -- Assertions used in more than one class --------------------
def assertParseFail(self, cmdline_args, expected_output):
"""Assert the parser fails with the expected message."""
self.assertRaises(self.parser.parse_args, SystemExit, expected_output,
funcargs=[cmdline_args])
sys.stderr = StringIO()
self.assertRaises(self.parser.parse_args, (cmdline_args,), None,
SystemExit, expected_output,
self.redirected_stderr)
sys.stderr = sys.__stderr__
def assertStdoutEquals(self, cmdline_args, expected_output):
"""Assert the parser prints the expected output on stdout."""
sys.stdout = StringIO()
self.assertRaises(self.parser.parse_args, SystemExit, expected_output,
self.redirected_stdout, [cmdline_args])
self.assertRaises(self.parser.parse_args, (cmdline_args,), None,
SystemExit, expected_output,
self.redirected_stdout)
sys.stdout = sys.__stdout__
def assertTypeError(self, func, expected_output, *args):
"""Assert a TypeError is raised when executing func."""
self.assertRaises(func, TypeError, expected_output, funcargs=args)
self.assertRaises(func, args, None, TypeError, expected_output)
def assertHelp(self, parser, expected_help):
actual_help = parser.format_help()
if actual_help != expected_help:
raise self.failureException(
'help text failure; expected:\n"' +
expected_help + '"; got:\n"' +
actual_help + '"\n')
# -- Test make_option() aka Option -------------------------------------
@ -142,8 +162,8 @@ def setUp(self):
self.parser = OptionParser(usage=SUPPRESS_USAGE)
def assertOptionError(self, expected_output, args=[], kwargs={}):
self.assertRaises(make_option, OptionError, expected_output,
funcargs=args, funckwargs=kwargs)
self.assertRaises(make_option, args, kwargs,
OptionError, expected_output)
def test_opt_string_empty(self):
self.assertTypeError(make_option,
@ -175,6 +195,8 @@ def test_action_invalid(self):
def test_type_invalid(self):
self.assertOptionError("invalid option type: 'foo'",
["-b"], {'type': 'foo'})
self.assertOptionError("invalid option type: 'tuple'",
["-b"], {'type': tuple})
def test_no_type_for_action(self):
self.assertOptionError("must not supply a type for action 'count'",
@ -304,8 +326,204 @@ def test_remove_long_opt(self):
self.assert_removed()
def test_remove_nonexistent(self):
self.assertRaises(self.parser.remove_option, ValueError,
"no such option 'foo'", funcargs=['foo'])
self.assertRaises(self.parser.remove_option, ('foo',), None,
ValueError, "no such option 'foo'")
class TestTypeAliases(BaseTest):
def setUp(self):
self.parser = OptionParser()
def test_type_aliases(self):
self.parser.add_option("-x", type=int)
self.parser.add_option("-s", type=str)
self.parser.add_option("-t", type="str")
self.assertEquals(self.parser.get_option("-x").type, "int")
self.assertEquals(self.parser.get_option("-s").type, "string")
self.assertEquals(self.parser.get_option("-t").type, "string")
# Custom type for testing processing of default values.
_time_units = { 's' : 1, 'm' : 60, 'h' : 60*60, 'd' : 60*60*24 }
def _check_duration(option, opt, value):
try:
if value[-1].isdigit():
return int(value)
else:
return int(value[:-1]) * _time_units[value[-1]]
except ValueError, IndexError:
raise OptionValueError(
'option %s: invalid duration: %r' % (opt, value))
class DurationOption(Option):
TYPES = Option.TYPES + ('duration',)
TYPE_CHECKER = copy.copy(Option.TYPE_CHECKER)
TYPE_CHECKER['duration'] = _check_duration
class TestDefaultValues(BaseTest):
def setUp(self):
self.parser = OptionParser()
self.parser.add_option("-v", "--verbose", default=True)
self.parser.add_option("-q", "--quiet", dest='verbose')
self.parser.add_option("-n", type="int", default=37)
self.parser.add_option("-m", type="int")
self.parser.add_option("-s", default="foo")
self.parser.add_option("-t")
self.parser.add_option("-u", default=None)
self.expected = { 'verbose': True,
'n': 37,
'm': None,
's': "foo",
't': None,
'u': None }
def test_basic_defaults(self):
self.assertEqual(self.parser.get_default_values(), self.expected)
def test_mixed_defaults_post(self):
self.parser.set_defaults(n=42, m=-100)
self.expected.update({'n': 42, 'm': -100})
self.assertEqual(self.parser.get_default_values(), self.expected)
def test_mixed_defaults_pre(self):
self.parser.set_defaults(x="barf", y="blah")
self.parser.add_option("-x", default="frob")
self.parser.add_option("-y")
self.expected.update({'x': "frob", 'y': "blah"})
self.assertEqual(self.parser.get_default_values(), self.expected)
self.parser.remove_option("-y")
self.parser.add_option("-y", default=None)
self.expected.update({'y': None})
self.assertEqual(self.parser.get_default_values(), self.expected)
def test_process_default(self):
self.parser.option_class = DurationOption
self.parser.add_option("-d", type="duration", default=300)
self.parser.add_option("-e", type="duration", default="6m")
self.parser.set_defaults(n="42")
self.expected.update({'d': 300, 'e': 360, 'n': 42})
self.assertEqual(self.parser.get_default_values(), self.expected)
self.parser.set_process_default_values(False)
self.expected.update({'d': 300, 'e': "6m", 'n': "42"})
self.assertEqual(self.parser.get_default_values(), self.expected)
class TestProgName(BaseTest):
"""
Test that %prog expands to the right thing in usage, version,
and help strings.
"""
def assertUsage(self, parser, expected_usage):
self.assertEqual(parser.get_usage(), expected_usage)
def assertVersion(self, parser, expected_version):
self.assertEqual(parser.get_version(), expected_version)
def test_default_progname(self):
# Make sure that program name taken from sys.argv[0] by default.
sys.argv[0] = "/foo/bar/baz.py"
parser = OptionParser("usage: %prog ...", version="%prog 1.2")
expected_usage = "usage: baz.py ...\n"
self.assertUsage(parser, expected_usage)
self.assertVersion(parser, "baz.py 1.2")
self.assertHelp(parser,
expected_usage + "\n" +
"options:\n"
" --version show program's version number and exit\n"
" -h, --help show this help message and exit\n")
def test_custom_progname(self):
parser = OptionParser(prog="thingy",
version="%prog 0.1",
usage="%prog arg arg")
parser.remove_option("-h")
parser.remove_option("--version")
expected_usage = "usage: thingy arg arg\n"
self.assertUsage(parser, expected_usage)
self.assertVersion(parser, "thingy 0.1")
self.assertHelp(parser, expected_usage + "\n")
class TestExpandDefaults(BaseTest):
def setUp(self):
self.parser = OptionParser(prog="test")
self.help_prefix = """\
usage: test [options]
options:
-h, --help show this help message and exit
"""
self.file_help = "read from FILE [default: %default]"
self.expected_help_file = self.help_prefix + \
" -f FILE, --file=FILE read from FILE [default: foo.txt]\n"
self.expected_help_none = self.help_prefix + \
" -f FILE, --file=FILE read from FILE [default: none]\n"
def test_option_default(self):
self.parser.add_option("-f", "--file",
default="foo.txt",
help=self.file_help)
self.assertHelp(self.parser, self.expected_help_file)
def test_parser_default_1(self):
self.parser.add_option("-f", "--file",
help=self.file_help)
self.parser.set_default('file', "foo.txt")
self.assertHelp(self.parser, self.expected_help_file)
def test_parser_default_2(self):
self.parser.add_option("-f", "--file",
help=self.file_help)
self.parser.set_defaults(file="foo.txt")
self.assertHelp(self.parser, self.expected_help_file)
def test_no_default(self):
self.parser.add_option("-f", "--file",
help=self.file_help)
self.assertHelp(self.parser, self.expected_help_none)
def test_default_none_1(self):
self.parser.add_option("-f", "--file",
default=None,
help=self.file_help)
self.assertHelp(self.parser, self.expected_help_none)
def test_default_none_2(self):
self.parser.add_option("-f", "--file",
help=self.file_help)
self.parser.set_defaults(file=None)
self.assertHelp(self.parser, self.expected_help_none)
def test_float_default(self):
self.parser.add_option(
"-p", "--prob",
help="blow up with probability PROB [default: %default]")
self.parser.set_defaults(prob=0.43)
expected_help = self.help_prefix + \
" -p PROB, --prob=PROB blow up with probability PROB [default: 0.43]\n"
self.assertHelp(self.parser, expected_help)
def test_alt_expand(self):
self.parser.add_option("-f", "--file",
default="foo.txt",
help="read from FILE [default: *DEFAULT*]")
self.parser.formatter.default_tag = "*DEFAULT*"
self.assertHelp(self.parser, self.expected_help_file)
def test_no_expand(self):
self.parser.add_option("-f", "--file",
default="foo.txt",
help="read from %default file")
self.parser.formatter.default_tag = None
expected_help = self.help_prefix + \
" -f FILE, --file=FILE read from %default file\n"
self.assertHelp(self.parser, expected_help)
# -- Test parser.parse_args() ------------------------------------------
@ -318,7 +536,7 @@ def setUp(self):
self.parser = OptionParser(usage=SUPPRESS_USAGE, option_list=options)
def test_required_value(self):
self.assertParseFail(["-a"], "-a option requires a value")
self.assertParseFail(["-a"], "-a option requires an argument")
def test_invalid_integer(self):
self.assertParseFail(["-b", "5x"],
@ -580,7 +798,7 @@ def test_nargs_invalid_float_value(self):
def test_nargs_required_values(self):
self.assertParseFail(["--point", "1.0", "3.5"],
"--point option requires 3 values")
"--point option requires 3 arguments")
class TestNArgsAppend(BaseTest):
def setUp(self):
@ -597,7 +815,7 @@ def test_nargs_append(self):
def test_nargs_append_required_values(self):
self.assertParseFail(["-f4,3"],
"-f option requires 2 values")
"-f option requires 2 arguments")
def test_nargs_append_simple(self):
self.assertParseOK(["--foo=3", "4"],
@ -612,22 +830,6 @@ def test_version(self):
self.assertStdoutEquals(["--version"], "bar 0.1\n")
sys.argv[0] = oldargv
def test_version_with_prog_keyword(self):
oldargv = sys.argv[0]
sys.argv[0] = "./foo/bar"
self.parser = OptionParser(usage=SUPPRESS_USAGE, version="%prog 0.1",
prog="splat")
self.assertStdoutEquals(["--version"], "splat 0.1\n")
sys.argv[0] = oldargv
def test_version_with_prog_attribute(self):
oldargv = sys.argv[0]
sys.argv[0] = "./foo/bar"
self.parser = OptionParser(usage=SUPPRESS_USAGE, version="%prog 0.1")
self.parser.prog = "splat"
self.assertStdoutEquals(["--version"], "splat 0.1\n")
sys.argv[0] = oldargv
def test_no_version(self):
self.parser = OptionParser(usage=SUPPRESS_USAGE)
self.assertParseFail(["--version"],
@ -673,8 +875,8 @@ def test_add_group_invalid_arguments(self):
def test_add_group_wrong_parser(self):
group = OptionGroup(self.parser, "Spam")
group.parser = OptionParser()
self.assertRaises(self.parser.add_option_group, ValueError,
"invalid OptionGroup (wrong parser)", funcargs=[group])
self.assertRaises(self.parser.add_option_group, (group,), None,
ValueError, "invalid OptionGroup (wrong parser)")
def test_group_manipulate(self):
group = self.parser.add_option_group("Group 2",
@ -794,7 +996,22 @@ def test_callback(self):
{'filename': "foo", 'x': 42},
[])
class TestCallBackExtraArgs(BaseTest):
def test_callback_help(self):
# This test was prompted by SF bug #960515 -- the point is
# not to inspect the help text, just to make sure that
# format_help() doesn't crash.
parser = OptionParser(usage=SUPPRESS_USAGE)
parser.remove_option("-h")
parser.add_option("-t", "--test", action="callback",
callback=lambda: None, type="string",
help="foo")
expected_help = ("options:\n"
" -t TEST, --test=TEST foo\n")
self.assertHelp(parser, expected_help)
class TestCallbackExtraArgs(BaseTest):
def setUp(self):
options = [make_option("-p", "--point", action="callback",
callback=self.process_tuple,
@ -819,7 +1036,7 @@ def test_callback_extra_args(self):
{'points': [(1,2,3), (4,5,6)]},
[])
class TestCallBackMeddleArgs(BaseTest):
class TestCallbackMeddleArgs(BaseTest):
def setUp(self):
options = [make_option(str(x), action="callback",
callback=self.process_n, dest='things')
@ -848,7 +1065,7 @@ def test_callback_meddle_args_separator(self):
{'things': [('foo', '--')]},
[2])
class TestCallBackManyArgs(BaseTest):
class TestCallbackManyArgs(BaseTest):
def setUp(self):
options = [make_option("-a", "--apple", action="callback", nargs=2,
callback=self.process_many, type="string"),
@ -870,10 +1087,10 @@ def test_many_args(self):
self.assertParseOK(["-a", "foo", "bar", "--apple", "ding", "dong",
"-b", "1", "2", "3", "--bob", "-666", "42",
"0"],
{},
{"apple": None, "bob": None},
[])
class TestCallBackCheckAbbrev(BaseTest):
class TestCallbackCheckAbbrev(BaseTest):
def setUp(self):
self.parser = OptionParser()
self.parser.add_option("--foo-bar", action="callback",
@ -885,7 +1102,7 @@ def check_abbrev (self, option, opt, value, parser):
def test_abbrev_callback_expansion(self):
self.assertParseOK(["--foo"], {}, [])
class TestCallBackVarArgs(BaseTest):
class TestCallbackVarArgs(BaseTest):
def setUp(self):
options = [make_option("-a", type="int", nargs=2, dest="a"),
make_option("-b", action="store_true", dest="b"),
@ -950,13 +1167,12 @@ def show_version (self, option, opt, value, parser):
class TestConflict(ConflictBase):
"""Use the default conflict resolution for Optik 1.2: error."""
def assert_conflict_error(self, func):
err = self.assertRaises(func, OptionConflictError,
"option -v/--version: conflicting option "
"string(s): -v",
funcargs=["-v", "--version"],
funckwargs={'action':"callback",
err = self.assertRaises(
func, ("-v", "--version"), {'action' : "callback",
'callback' : self.show_version,
'help':"show version"})
'help' : "show version"},
OptionConflictError,
"option -v/--version: conflicting option string(s): -v")
self.assertEqual(err.msg, "conflicting option string(s): -v")
self.assertEqual(err.option_id, "-v/--version")
@ -969,9 +1185,9 @@ def test_conflict_error_group(self):
self.assert_conflict_error(group.add_option)
def test_no_such_conflict_handler(self):
self.assertRaises(self.parser.set_conflict_handler, ValueError,
"invalid conflict_resolution value 'foo'",
funcargs=['foo'])
self.assertRaises(
self.parser.set_conflict_handler, ('foo',), None,
ValueError, "invalid conflict_resolution value 'foo'")
class TestConflictIgnore(ConflictBase):
@ -1082,8 +1298,60 @@ def test_conflict_override_args(self):
# -- Other testing. ----------------------------------------------------
_expected_help_basic = """\
usage: bar.py [options]
options:
-a APPLE throw APPLEs at basket
-b NUM, --boo=NUM shout "boo!" NUM times (in order to frighten away all the
evil spirits that cause trouble and mayhem)
--foo=FOO store FOO in the foo list for later fooing
-h, --help show this help message and exit
"""
_expected_help_long_opts_first = """\
usage: bar.py [options]
options:
-a APPLE throw APPLEs at basket
--boo=NUM, -b NUM shout "boo!" NUM times (in order to frighten away all the
evil spirits that cause trouble and mayhem)
--foo=FOO store FOO in the foo list for later fooing
--help, -h show this help message and exit
"""
_expected_help_title_formatter = """\
Usage
=====
bar.py [options]
options
=======
-a APPLE throw APPLEs at basket
--boo=NUM, -b NUM shout "boo!" NUM times (in order to frighten away all the
evil spirits that cause trouble and mayhem)
--foo=FOO store FOO in the foo list for later fooing
--help, -h show this help message and exit
"""
_expected_help_short_lines = """\
usage: bar.py [options]
options:
-a APPLE throw APPLEs at basket
-b NUM, --boo=NUM shout "boo!" NUM times (in order to
frighten away all the evil spirits
that cause trouble and mayhem)
--foo=FOO store FOO in the foo list for later
fooing
-h, --help show this help message and exit
"""
class TestHelp(BaseTest):
def setUp(self):
self.parser = self.make_parser(80)
def make_parser(self, columns):
options = [
make_option("-a", type="string", dest='a',
metavar="APPLE", help="throw APPLEs at basket"),
@ -1095,9 +1363,8 @@ def setUp(self):
make_option("--foo", action="append", type="string", dest='foo',
help="store FOO in the foo list for later fooing"),
]
usage = "%prog [options]"
self.parser = OptionParser(usage=usage, option_list=options)
os.environ['COLUMNS'] = str(columns)
return OptionParser(option_list=options)
def assertHelpEquals(self, expected_output):
# This trick is used to make optparse believe bar.py is being executed.
@ -1109,62 +1376,30 @@ def assertHelpEquals(self, expected_output):
sys.argv[0] = oldargv
def test_help(self):
self.assertHelpEquals("""\
usage: bar.py [options]
options:
-aAPPLE throw APPLEs at basket
-bNUM, --boo=NUM shout "boo!" NUM times (in order to frighten away all
the evil spirits that cause trouble and mayhem)
--foo=FOO store FOO in the foo list for later fooing
-h, --help show this help message and exit
""")
self.assertHelpEquals(_expected_help_basic)
def test_help_old_usage(self):
self.parser.set_usage("usage: %prog [options]")
self.assertHelpEquals("""\
usage: bar.py [options]
options:
-aAPPLE throw APPLEs at basket
-bNUM, --boo=NUM shout "boo!" NUM times (in order to frighten away all
the evil spirits that cause trouble and mayhem)
--foo=FOO store FOO in the foo list for later fooing
-h, --help show this help message and exit
""")
self.assertHelpEquals(_expected_help_basic)
def test_help_long_opts_first(self):
self.parser.formatter.short_first = 0
self.assertHelpEquals("""\
usage: bar.py [options]
options:
-aAPPLE throw APPLEs at basket
--boo=NUM, -bNUM shout "boo!" NUM times (in order to frighten away all
the evil spirits that cause trouble and mayhem)
--foo=FOO store FOO in the foo list for later fooing
--help, -h show this help message and exit
""")
self.assertHelpEquals(_expected_help_long_opts_first)
def test_help_title_formatter(self):
self.parser.formatter = TitledHelpFormatter()
self.assertHelpEquals("""\
Usage
=====
bar.py [options]
self.assertHelpEquals(_expected_help_title_formatter)
options
=======
-aAPPLE throw APPLEs at basket
--boo=NUM, -bNUM shout "boo!" NUM times (in order to frighten away all
the evil spirits that cause trouble and mayhem)
--foo=FOO store FOO in the foo list for later fooing
--help, -h show this help message and exit
""")
def test_wrap_columns(self):
# Ensure that wrapping respects $COLUMNS environment variable.
# Need to reconstruct the parser, since that's the only time
# we look at $COLUMNS.
self.parser = self.make_parser(60)
self.assertHelpEquals(_expected_help_short_lines)
def test_help_description_groups(self):
self.parser.set_description(
"This is the program description. This program has "
"This is the program description for %prog. %prog has "
"an option group as well as single options.")
group = OptionGroup(
@ -1177,21 +1412,26 @@ def test_help_description_groups(self):
self.assertHelpEquals("""\
usage: bar.py [options]
This is the program description. This program has an option group as well as
single options.
This is the program description for bar.py. bar.py has an option group as
well as single options.
options:
-a APPLE throw APPLEs at basket
-bNUM, --boo=NUM shout "boo!" NUM times (in order to frighten away all
the evil spirits that cause trouble and mayhem)
-b NUM, --boo=NUM shout "boo!" NUM times (in order to frighten away all the
evil spirits that cause trouble and mayhem)
--foo=FOO store FOO in the foo list for later fooing
-h, --help show this help message and exit
Dangerous Options:
Caution: use of these options is at your own risk. It is believed that
some of them bite.
Caution: use of these options is at your own risk. It is believed
that some of them bite.
-g Group option.
""")
class TestMatchAbbrev(BaseTest):
def test_match_abbrev(self):
self.assertEqual(_match_abbrev("--f",
@ -1205,15 +1445,23 @@ def test_match_abbrev_error(self):
s = "--f"
wordmap = {"--foz": None, "--foo": None, "--fie": None}
possibilities = ", ".join(wordmap.keys())
self.assertRaises(_match_abbrev, BadOptionError,
"ambiguous option: --f (%s?)" % possibilities,
funcargs=[s, wordmap])
self.assertRaises(
_match_abbrev, (s, wordmap), None,
BadOptionError, "ambiguous option: --f (%s?)" % possibilities)
def _testclasses():
mod = sys.modules[__name__]
return [getattr(mod, name) for name in dir(mod) if name.startswith('Test')]
def suite():
suite = unittest.TestSuite()
for testclass in _testclasses():
suite.addTest(unittest.makeSuite(testclass))
return suite
def test_main():
mod = sys.modules[__name__]
test_support.run_unittest(
*[getattr(mod, name) for name in dir(mod) if name.startswith('Test')]
)
test_support.run_suite(suite())
if __name__ == '__main__':
unittest.main()