Kaydet (Commit) eba20e60 authored tarafından Greg Ward's avatar Greg Ward

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.)
üst 7357222d
...@@ -16,13 +16,11 @@ For support, use the optik-users@lists.sourceforge.net mailing list ...@@ -16,13 +16,11 @@ For support, use the optik-users@lists.sourceforge.net mailing list
# Python developers: please do not make changes to this file, since # Python developers: please do not make changes to this file, since
# it is automatically generated from the Optik source code. # it is automatically generated from the Optik source code.
__version__ = "1.4.1+" __version__ = "1.5a1"
__all__ = ['Option', __all__ = ['Option',
'SUPPRESS_HELP', 'SUPPRESS_HELP',
'SUPPRESS_USAGE', 'SUPPRESS_USAGE',
'STD_HELP_OPTION',
'STD_VERSION_OPTION',
'Values', 'Values',
'OptionContainer', 'OptionContainer',
'OptionGroup', 'OptionGroup',
...@@ -37,7 +35,8 @@ __all__ = ['Option', ...@@ -37,7 +35,8 @@ __all__ = ['Option',
'BadOptionError'] 'BadOptionError']
__copyright__ = """ __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 Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
...@@ -70,12 +69,23 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ...@@ -70,12 +69,23 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import sys, os import sys, os
import types import types
import textwrap 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): class OptParseError (Exception):
def __init__ (self, msg): def __init__(self, msg):
self.msg = msg self.msg = msg
def __str__ (self): def __str__(self):
return self.msg return self.msg
...@@ -85,11 +95,11 @@ class OptionError (OptParseError): ...@@ -85,11 +95,11 @@ class OptionError (OptParseError):
inconsistent arguments. inconsistent arguments.
""" """
def __init__ (self, msg, option): def __init__(self, msg, option):
self.msg = msg self.msg = msg
self.option_id = str(option) self.option_id = str(option)
def __str__ (self): def __str__(self):
if self.option_id: if self.option_id:
return "option %s: %s" % (self.option_id, self.msg) return "option %s: %s" % (self.option_id, self.msg)
else: else:
...@@ -120,6 +130,8 @@ class HelpFormatter: ...@@ -120,6 +130,8 @@ class HelpFormatter:
formatting help; by default IndentedHelpFormatter is used. formatting help; by default IndentedHelpFormatter is used.
Instance attributes: Instance attributes:
parser : OptionParser
the controlling OptionParser instance
indent_increment : int indent_increment : int
the number of columns to indent per nesting level the number of columns to indent per nesting level
max_help_position : int max_help_position : int
...@@ -128,51 +140,108 @@ class HelpFormatter: ...@@ -128,51 +140,108 @@ class HelpFormatter:
the calculated starting column for option help text; the calculated starting column for option help text;
initially the same as the maximum initially the same as the maximum
width : int 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 level : int
current indentation level current indentation level
current_indent : int current_indent : int
current indentation level (in columns) current indentation level (in columns)
help_width : int help_width : int
number of columns available for option help text (calculated) 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").
""" """
def __init__ (self, NO_DEFAULT_VALUE = "none"
indent_increment,
max_help_position, def __init__(self,
width, indent_increment,
short_first): max_help_position,
width,
short_first):
self.parser = None
self.indent_increment = indent_increment self.indent_increment = indent_increment
self.help_position = self.max_help_position = max_help_position 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.width = width
self.current_indent = 0 self.current_indent = 0
self.level = 0 self.level = 0
self.help_width = width - max_help_position self.help_width = None # computed later
self.short_first = short_first 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 indent (self): 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 self.current_indent += self.indent_increment
self.level += 1 self.level += 1
def dedent (self): def dedent(self):
self.current_indent -= self.indent_increment self.current_indent -= self.indent_increment
assert self.current_indent >= 0, "Indent decreased below 0." assert self.current_indent >= 0, "Indent decreased below 0."
self.level -= 1 self.level -= 1
def format_usage (self, usage): def format_usage(self, usage):
raise NotImplementedError, "subclasses must implement" raise NotImplementedError, "subclasses must implement"
def format_heading (self, heading): def format_heading(self, heading):
raise NotImplementedError, "subclasses must implement" raise NotImplementedError, "subclasses must implement"
def format_description (self, description): def format_description(self, description):
if not description:
return ""
desc_width = self.width - self.current_indent desc_width = self.width - self.current_indent
indent = " "*self.current_indent indent = " "*self.current_indent
return textwrap.fill(description, desc_width, return textwrap.fill(description,
desc_width,
initial_indent=indent, 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
def format_option (self, option): return option.help.replace(self.default_tag, str(default_value))
def format_option(self, option):
# The help for each option consists of two parts: # The help for each option consists of two parts:
# * the opt strings and metavars # * the opt strings and metavars
# eg. ("-x", or "-fFILENAME, --file=FILENAME") # eg. ("-x", or "-fFILENAME, --file=FILENAME")
...@@ -188,7 +257,7 @@ class HelpFormatter: ...@@ -188,7 +257,7 @@ class HelpFormatter:
# -fFILENAME, --file=FILENAME # -fFILENAME, --file=FILENAME
# read data from FILENAME # read data from FILENAME
result = [] result = []
opts = option.option_strings opts = self.option_strings[option]
opt_width = self.help_position - self.current_indent - 2 opt_width = self.help_position - self.current_indent - 2
if len(opts) > opt_width: if len(opts) > opt_width:
opts = "%*s%s\n" % (self.current_indent, "", opts) opts = "%*s%s\n" % (self.current_indent, "", opts)
...@@ -198,7 +267,8 @@ class HelpFormatter: ...@@ -198,7 +267,8 @@ class HelpFormatter:
indent_first = 0 indent_first = 0
result.append(opts) result.append(opts)
if option.help: 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.append("%*s%s\n" % (indent_first, "", help_lines[0]))
result.extend(["%*s%s\n" % (self.help_position, "", line) result.extend(["%*s%s\n" % (self.help_position, "", line)
for line in help_lines[1:]]) for line in help_lines[1:]])
...@@ -206,29 +276,32 @@ class HelpFormatter: ...@@ -206,29 +276,32 @@ class HelpFormatter:
result.append("\n") result.append("\n")
return "".join(result) return "".join(result)
def store_option_strings (self, parser): def store_option_strings(self, parser):
self.indent() self.indent()
max_len = 0 max_len = 0
for opt in parser.option_list: for opt in parser.option_list:
strings = self.format_option_strings(opt) 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) max_len = max(max_len, len(strings) + self.current_indent)
self.indent() self.indent()
for group in parser.option_groups: for group in parser.option_groups:
for opt in group.option_list: for opt in group.option_list:
strings = self.format_option_strings(opt) 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) max_len = max(max_len, len(strings) + self.current_indent)
self.dedent() self.dedent()
self.dedent() self.dedent()
self.help_position = min(max_len + 2, self.max_help_position) 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): def format_option_strings(self, option):
"""Return a comma-separated list of option strings & metavariables.""" """Return a comma-separated list of option strings & metavariables."""
if option.takes_value(): if option.takes_value():
metavar = option.metavar or option.dest.upper() metavar = option.metavar or option.dest.upper()
short_opts = [sopt + metavar for sopt in option._short_opts] short_opts = [self._short_opt_fmt % (sopt, metavar)
long_opts = [lopt + "=" + metavar for lopt in option._long_opts] for sopt in option._short_opts]
long_opts = [self._long_opt_fmt % (lopt, metavar)
for lopt in option._long_opts]
else: else:
short_opts = option._short_opts short_opts = option._short_opts
long_opts = option._long_opts long_opts = option._long_opts
...@@ -244,18 +317,18 @@ class IndentedHelpFormatter (HelpFormatter): ...@@ -244,18 +317,18 @@ class IndentedHelpFormatter (HelpFormatter):
"""Format help with indented section bodies. """Format help with indented section bodies.
""" """
def __init__ (self, def __init__(self,
indent_increment=2, indent_increment=2,
max_help_position=24, max_help_position=24,
width=79, width=None,
short_first=1): short_first=1):
HelpFormatter.__init__( HelpFormatter.__init__(
self, indent_increment, max_help_position, width, short_first) self, indent_increment, max_help_position, width, short_first)
def format_usage (self, usage): def format_usage(self, usage):
return "usage: %s\n" % usage return _("usage: %s\n") % usage
def format_heading (self, heading): def format_heading(self, heading):
return "%*s%s:\n" % (self.current_indent, "", heading) return "%*s%s:\n" % (self.current_indent, "", heading)
...@@ -263,34 +336,33 @@ class TitledHelpFormatter (HelpFormatter): ...@@ -263,34 +336,33 @@ class TitledHelpFormatter (HelpFormatter):
"""Format help with underlined section headers. """Format help with underlined section headers.
""" """
def __init__ (self, def __init__(self,
indent_increment=0, indent_increment=0,
max_help_position=24, max_help_position=24,
width=79, width=None,
short_first=0): short_first=0):
HelpFormatter.__init__ ( HelpFormatter.__init__ (
self, indent_increment, max_help_position, width, short_first) self, indent_increment, max_help_position, width, short_first)
def format_usage (self, usage): 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): def format_heading(self, heading):
return "%s\n%s\n" % (heading, "=-"[self.level] * len(heading)) return "%s\n%s\n" % (heading, "=-"[self.level] * len(heading))
_builtin_cvt = { "int" : (int, "integer"), _builtin_cvt = { "int" : (int, _("integer")),
"long" : (long, "long integer"), "long" : (long, _("long integer")),
"float" : (float, "floating-point"), "float" : (float, _("floating-point")),
"complex" : (complex, "complex") } "complex" : (complex, _("complex")) }
def check_builtin (option, opt, value): def check_builtin(option, opt, value):
(cvt, what) = _builtin_cvt[option.type] (cvt, what) = _builtin_cvt[option.type]
try: try:
return cvt(value) return cvt(value)
except ValueError: except ValueError:
raise OptionValueError( 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): def check_choice(option, opt, value):
if value in option.choices: if value in option.choices:
...@@ -298,12 +370,12 @@ def check_choice(option, opt, value): ...@@ -298,12 +370,12 @@ def check_choice(option, opt, value):
else: else:
choices = ", ".join(map(repr, option.choices)) choices = ", ".join(map(repr, option.choices))
raise OptionValueError( raise OptionValueError(
"option %s: invalid choice: %r (choose from %s)" _("option %s: invalid choice: %r (choose from %s)")
% (opt, value, choices)) % (opt, value, choices))
# Not supplying a default is different from a default of None, # Not supplying a default is different from a default of None,
# so we need an explicit "not supplied" value. # so we need an explicit "not supplied" value.
NO_DEFAULT = "NO"+"DEFAULT" NO_DEFAULT = ("NO", "DEFAULT")
class Option: class Option:
...@@ -392,7 +464,7 @@ class Option: ...@@ -392,7 +464,7 @@ class Option:
TYPE_CHECKER = { "int" : check_builtin, TYPE_CHECKER = { "int" : check_builtin,
"long" : check_builtin, "long" : check_builtin,
"float" : check_builtin, "float" : check_builtin,
"complex" : check_builtin, "complex": check_builtin,
"choice" : check_choice, "choice" : check_choice,
} }
...@@ -410,7 +482,7 @@ class Option: ...@@ -410,7 +482,7 @@ class Option:
# -- Constructor/initialization methods ---------------------------- # -- Constructor/initialization methods ----------------------------
def __init__ (self, *opts, **attrs): def __init__(self, *opts, **attrs):
# Set _short_opts, _long_opts attrs from 'opts' tuple. # Set _short_opts, _long_opts attrs from 'opts' tuple.
# Have to be set now, in case no option strings are supplied. # Have to be set now, in case no option strings are supplied.
self._short_opts = [] self._short_opts = []
...@@ -429,7 +501,7 @@ class Option: ...@@ -429,7 +501,7 @@ class Option:
for checker in self.CHECK_METHODS: for checker in self.CHECK_METHODS:
checker(self) checker(self)
def _check_opt_strings (self, opts): def _check_opt_strings(self, opts):
# Filter out None because early versions of Optik had exactly # Filter out None because early versions of Optik had exactly
# one short option and one long option, either of which # one short option and one long option, either of which
# could be None. # could be None.
...@@ -438,7 +510,7 @@ class Option: ...@@ -438,7 +510,7 @@ class Option:
raise TypeError("at least one option string must be supplied") raise TypeError("at least one option string must be supplied")
return opts return opts
def _set_opt_strings (self, opts): def _set_opt_strings(self, opts):
for opt in opts: for opt in opts:
if len(opt) < 2: if len(opt) < 2:
raise OptionError( raise OptionError(
...@@ -459,7 +531,7 @@ class Option: ...@@ -459,7 +531,7 @@ class Option:
self) self)
self._long_opts.append(opt) self._long_opts.append(opt)
def _set_attrs (self, attrs): def _set_attrs(self, attrs):
for attr in self.ATTRS: for attr in self.ATTRS:
if attrs.has_key(attr): if attrs.has_key(attr):
setattr(self, attr, attrs[attr]) setattr(self, attr, attrs[attr])
...@@ -477,13 +549,13 @@ class Option: ...@@ -477,13 +549,13 @@ class Option:
# -- Constructor validation methods -------------------------------- # -- Constructor validation methods --------------------------------
def _check_action (self): def _check_action(self):
if self.action is None: if self.action is None:
self.action = "store" self.action = "store"
elif self.action not in self.ACTIONS: elif self.action not in self.ACTIONS:
raise OptionError("invalid action: %r" % self.action, self) raise OptionError("invalid action: %r" % self.action, self)
def _check_type (self): def _check_type(self):
if self.type is None: if self.type is None:
# XXX should factor out another class attr here: list of # XXX should factor out another class attr here: list of
# actions that *require* a type # actions that *require* a type
...@@ -495,6 +567,12 @@ class Option: ...@@ -495,6 +567,12 @@ class Option:
# No type given? "string" is the most sensible default. # No type given? "string" is the most sensible default.
self.type = "string" self.type = "string"
else: 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: if self.type not in self.TYPES:
raise OptionError("invalid option type: %r" % self.type, self) raise OptionError("invalid option type: %r" % self.type, self)
if self.action not in self.TYPED_ACTIONS: if self.action not in self.TYPED_ACTIONS:
...@@ -514,9 +592,13 @@ class Option: ...@@ -514,9 +592,13 @@ class Option:
raise OptionError( raise OptionError(
"must not supply choices for type %r" % self.type, self) "must not supply choices for type %r" % self.type, self)
def _check_dest (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. The
# No destination given, and we need one for this action. # 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, # Glean a destination from the first long option string,
# or from the first short option string if no long options. # or from the first short option string if no long options.
if self._long_opts: if self._long_opts:
...@@ -525,13 +607,13 @@ class Option: ...@@ -525,13 +607,13 @@ class Option:
else: else:
self.dest = self._short_opts[0][1] self.dest = self._short_opts[0][1]
def _check_const (self): def _check_const(self):
if self.action != "store_const" and self.const is not None: if self.action != "store_const" and self.const is not None:
raise OptionError( raise OptionError(
"'const' must not be supplied for action %r" % self.action, "'const' must not be supplied for action %r" % self.action,
self) self)
def _check_nargs (self): def _check_nargs(self):
if self.action in self.TYPED_ACTIONS: if self.action in self.TYPED_ACTIONS:
if self.nargs is None: if self.nargs is None:
self.nargs = 1 self.nargs = 1
...@@ -540,7 +622,7 @@ class Option: ...@@ -540,7 +622,7 @@ class Option:
"'nargs' must not be supplied for action %r" % self.action, "'nargs' must not be supplied for action %r" % self.action,
self) self)
def _check_callback (self): def _check_callback(self):
if self.action == "callback": if self.action == "callback":
if not callable(self.callback): if not callable(self.callback):
raise OptionError( raise OptionError(
...@@ -579,31 +661,42 @@ class Option: ...@@ -579,31 +661,42 @@ class Option:
# -- Miscellaneous methods ----------------------------------------- # -- Miscellaneous methods -----------------------------------------
def __str__ (self): def __str__(self):
return "/".join(self._short_opts + self._long_opts) return "/".join(self._short_opts + self._long_opts)
def takes_value (self): __repr__ = _repr
def takes_value(self):
return self.type is not None 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 -------------------------------------------- # -- Processing methods --------------------------------------------
def check_value (self, opt, value): def check_value(self, opt, value):
checker = self.TYPE_CHECKER.get(self.type) checker = self.TYPE_CHECKER.get(self.type)
if checker is None: if checker is None:
return value return value
else: else:
return checker(self, opt, value) return checker(self, opt, value)
def process (self, opt, value, values, parser): def convert_value(self, opt, value):
# First, convert the value(s) to the right type. Howl if any
# value(s) are bogus.
if value is not None: if value is not None:
if self.nargs == 1: if self.nargs == 1:
value = self.check_value(opt, value) return self.check_value(opt, value)
else: else:
value = tuple([self.check_value(opt, v) for v in value]) 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.
value = self.convert_value(opt, value)
# And then take whatever action is expected of us. # And then take whatever action is expected of us.
# This is a separate method to make life easier for # This is a separate method to make life easier for
...@@ -611,7 +704,7 @@ class Option: ...@@ -611,7 +704,7 @@ class Option:
return self.take_action( return self.take_action(
self.action, self.dest, opt, value, values, parser) self.action, self.dest, opt, value, values, parser)
def take_action (self, action, dest, opt, value, values, parser): def take_action(self, action, dest, opt, value, values, parser):
if action == "store": if action == "store":
setattr(values, dest, value) setattr(values, dest, value)
elif action == "store_const": elif action == "store_const":
...@@ -642,33 +735,44 @@ class Option: ...@@ -642,33 +735,44 @@ class Option:
# class Option # class Option
def get_prog_name ():
return os.path.basename(sys.argv[0])
SUPPRESS_HELP = "SUPPRESS"+"HELP" SUPPRESS_HELP = "SUPPRESS"+"HELP"
SUPPRESS_USAGE = "SUPPRESS"+"USAGE" SUPPRESS_USAGE = "SUPPRESS"+"USAGE"
STD_HELP_OPTION = Option("-h", "--help", # For compatibility with Python 2.2
action="help", try:
help="show this help message and exit") True, False
STD_VERSION_OPTION = Option("--version", except NameError:
action="version", (True, False) = (1, 0)
help="show program's version number and exit") try:
basestring
except NameError:
basestring = (str, unicode)
class Values: class Values:
def __init__ (self, defaults=None): def __init__(self, defaults=None):
if defaults: if defaults:
for (attr, val) in defaults.items(): for (attr, val) in defaults.items():
setattr(self, attr, val) setattr(self, attr, val)
def __repr__ (self): def __str__(self):
return ("<%s at 0x%x: %r>" return str(self.__dict__)
% (self.__class__.__name__, id(self), self.__dict__))
__repr__ = _repr
def _update_careful (self, dict): 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):
""" """
Update the option values from an arbitrary dictionary, but only Update the option values from an arbitrary dictionary, but only
use keys from dict that already have a corresponding attribute use keys from dict that already have a corresponding attribute
...@@ -681,7 +785,7 @@ class Values: ...@@ -681,7 +785,7 @@ class Values:
if dval is not None: if dval is not None:
setattr(self, attr, dval) setattr(self, attr, dval)
def _update_loose (self, dict): def _update_loose(self, dict):
""" """
Update the option values from an arbitrary dictionary, Update the option values from an arbitrary dictionary,
using all keys from the dictionary regardless of whether using all keys from the dictionary regardless of whether
...@@ -689,7 +793,7 @@ class Values: ...@@ -689,7 +793,7 @@ class Values:
""" """
self.__dict__.update(dict) self.__dict__.update(dict)
def _update (self, dict, mode): def _update(self, dict, mode):
if mode == "careful": if mode == "careful":
self._update_careful(dict) self._update_careful(dict)
elif mode == "loose": elif mode == "loose":
...@@ -697,17 +801,17 @@ class Values: ...@@ -697,17 +801,17 @@ class Values:
else: else:
raise ValueError, "invalid update mode: %r" % mode raise ValueError, "invalid update mode: %r" % mode
def read_module (self, modname, mode="careful"): def read_module(self, modname, mode="careful"):
__import__(modname) __import__(modname)
mod = sys.modules[modname] mod = sys.modules[modname]
self._update(vars(mod), mode) self._update(vars(mod), mode)
def read_file (self, filename, mode="careful"): def read_file(self, filename, mode="careful"):
vars = {} vars = {}
execfile(filename, vars) execfile(filename, vars)
self._update(vars, mode) self._update(vars, mode)
def ensure_value (self, attr, value): def ensure_value(self, attr, value):
if not hasattr(self, attr) or getattr(self, attr) is None: if not hasattr(self, attr) or getattr(self, attr) is None:
setattr(self, attr, value) setattr(self, attr, value)
return getattr(self, attr) return getattr(self, attr)
...@@ -745,7 +849,7 @@ class OptionContainer: ...@@ -745,7 +849,7 @@ class OptionContainer:
""" """
def __init__ (self, option_class, conflict_handler, description): def __init__(self, option_class, conflict_handler, description):
# Initialize the option list and related data structures. # Initialize the option list and related data structures.
# This method must be provided by subclasses, and it must # This method must be provided by subclasses, and it must
# initialize at least the following instance attributes: # initialize at least the following instance attributes:
...@@ -756,7 +860,7 @@ class OptionContainer: ...@@ -756,7 +860,7 @@ class OptionContainer:
self.set_conflict_handler(conflict_handler) self.set_conflict_handler(conflict_handler)
self.set_description(description) self.set_description(description)
def _create_option_mappings (self): def _create_option_mappings(self):
# For use by OptionParser constructor -- create the master # For use by OptionParser constructor -- create the master
# option mappings used by this OptionParser and all # option mappings used by this OptionParser and all
# OptionGroups that it owns. # OptionGroups that it owns.
...@@ -765,25 +869,28 @@ class OptionContainer: ...@@ -765,25 +869,28 @@ class OptionContainer:
self.defaults = {} # maps option dest -> default value self.defaults = {} # maps option dest -> default value
def _share_option_mappings (self, parser): def _share_option_mappings(self, parser):
# For use by OptionGroup constructor -- use shared option # For use by OptionGroup constructor -- use shared option
# mappings from the OptionParser that owns this OptionGroup. # mappings from the OptionParser that owns this OptionGroup.
self._short_opt = parser._short_opt self._short_opt = parser._short_opt
self._long_opt = parser._long_opt self._long_opt = parser._long_opt
self.defaults = parser.defaults self.defaults = parser.defaults
def set_conflict_handler (self, handler): def set_conflict_handler(self, handler):
if handler not in ("ignore", "error", "resolve"): if handler not in ("ignore", "error", "resolve"):
raise ValueError, "invalid conflict_resolution value %r" % handler raise ValueError, "invalid conflict_resolution value %r" % handler
self.conflict_handler = handler self.conflict_handler = handler
def set_description (self, description): def set_description(self, description):
self.description = description self.description = description
def get_description(self):
return self.description
# -- Option-adding methods ----------------------------------------- # -- Option-adding methods -----------------------------------------
def _check_conflict (self, option): def _check_conflict(self, option):
conflict_opts = [] conflict_opts = []
for opt in option._short_opts: for opt in option._short_opts:
if self._short_opt.has_key(opt): if self._short_opt.has_key(opt):
...@@ -812,7 +919,7 @@ class OptionContainer: ...@@ -812,7 +919,7 @@ class OptionContainer:
if not (c_option._short_opts or c_option._long_opts): if not (c_option._short_opts or c_option._long_opts):
c_option.container.option_list.remove(c_option) c_option.container.option_list.remove(c_option)
def add_option (self, *args, **kwargs): def add_option(self, *args, **kwargs):
"""add_option(Option) """add_option(Option)
add_option(opt_str, ..., kwarg=val, ...) add_option(opt_str, ..., kwarg=val, ...)
""" """
...@@ -842,21 +949,21 @@ class OptionContainer: ...@@ -842,21 +949,21 @@ class OptionContainer:
return option return option
def add_options (self, option_list): def add_options(self, option_list):
for option in option_list: for option in option_list:
self.add_option(option) self.add_option(option)
# -- Option query/removal methods ---------------------------------- # -- Option query/removal methods ----------------------------------
def get_option (self, opt_str): def get_option(self, opt_str):
return (self._short_opt.get(opt_str) or return (self._short_opt.get(opt_str) or
self._long_opt.get(opt_str)) self._long_opt.get(opt_str))
def has_option (self, opt_str): def has_option(self, opt_str):
return (self._short_opt.has_key(opt_str) or return (self._short_opt.has_key(opt_str) or
self._long_opt.has_key(opt_str)) self._long_opt.has_key(opt_str))
def remove_option (self, opt_str): def remove_option(self, opt_str):
option = self._short_opt.get(opt_str) option = self._short_opt.get(opt_str)
if option is None: if option is None:
option = self._long_opt.get(opt_str) option = self._long_opt.get(opt_str)
...@@ -872,7 +979,7 @@ class OptionContainer: ...@@ -872,7 +979,7 @@ class OptionContainer:
# -- Help-formatting methods --------------------------------------- # -- Help-formatting methods ---------------------------------------
def format_option_help (self, formatter): def format_option_help(self, formatter):
if not self.option_list: if not self.option_list:
return "" return ""
result = [] result = []
...@@ -881,38 +988,36 @@ class OptionContainer: ...@@ -881,38 +988,36 @@ class OptionContainer:
result.append(formatter.format_option(option)) result.append(formatter.format_option(option))
return "".join(result) return "".join(result)
def format_description (self, formatter): def format_description(self, formatter):
if self.description: return formatter.format_description(self.get_description())
return formatter.format_description(self.description)
else:
return ""
def format_help (self, formatter): def format_help(self, formatter):
result = []
if self.description: if self.description:
desc = self.format_description(formatter) + "\n" result.append(self.format_description(formatter))
else: if self.option_list:
desc = "" result.append(self.format_option_help(formatter))
return desc + self.format_option_help(formatter) return "\n".join(result)
class OptionGroup (OptionContainer): class OptionGroup (OptionContainer):
def __init__ (self, parser, title, description=None): def __init__(self, parser, title, description=None):
self.parser = parser self.parser = parser
OptionContainer.__init__( OptionContainer.__init__(
self, parser.option_class, parser.conflict_handler, description) self, parser.option_class, parser.conflict_handler, description)
self.title = title self.title = title
def _create_option_list (self): def _create_option_list(self):
self.option_list = [] self.option_list = []
self._share_option_mappings(self.parser) self._share_option_mappings(self.parser)
def set_title (self, title): def set_title(self, title):
self.title = title self.title = title
# -- Help-formatting methods --------------------------------------- # -- Help-formatting methods ---------------------------------------
def format_help (self, formatter): def format_help(self, formatter):
result = formatter.format_heading(self.title) result = formatter.format_heading(self.title)
formatter.indent() formatter.indent()
result += OptionContainer.format_help(self, formatter) result += OptionContainer.format_help(self, formatter)
...@@ -937,7 +1042,12 @@ class OptionParser (OptionContainer): ...@@ -937,7 +1042,12 @@ class OptionParser (OptionContainer):
the name of the current program (to override the name of the current program (to override
os.path.basename(sys.argv[0])). 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. if true, positional arguments may be interspersed with options.
Assuming -a and -b each take a single argument, the command-line Assuming -a and -b each take a single argument, the command-line
-ablah foo bar -bboo baz -ablah foo bar -bboo baz
...@@ -950,6 +1060,14 @@ class OptionParser (OptionContainer): ...@@ -950,6 +1060,14 @@ class OptionParser (OptionContainer):
Python's getopt module, Perl's Getopt::Std, and other argument- Python's getopt module, Perl's Getopt::Std, and other argument-
parsing libraries, but it is generally annoying to users.) 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] rargs : [string]
the argument list currently being parsed. Only set when the argument list currently being parsed. Only set when
parse_args() is active, and continually trimmed down as parse_args() is active, and continually trimmed down as
...@@ -972,30 +1090,32 @@ class OptionParser (OptionContainer): ...@@ -972,30 +1090,32 @@ class OptionParser (OptionContainer):
standard_option_list = [] standard_option_list = []
def __init__ (self, def __init__(self,
usage=None, usage=None,
option_list=None, option_list=None,
option_class=Option, option_class=Option,
version=None, version=None,
conflict_handler="error", conflict_handler="error",
description=None, description=None,
formatter=None, formatter=None,
add_help_option=1, add_help_option=True,
prog=None): prog=None):
OptionContainer.__init__( OptionContainer.__init__(
self, option_class, conflict_handler, description) self, option_class, conflict_handler, description)
self.set_usage(usage) self.set_usage(usage)
self.prog = prog self.prog = prog
self.version = version self.version = version
self.allow_interspersed_args = 1 self.allow_interspersed_args = True
self.process_default_values = True
if formatter is None: if formatter is None:
formatter = IndentedHelpFormatter() formatter = IndentedHelpFormatter()
self.formatter = formatter self.formatter = formatter
self.formatter.set_parser(self)
# Populate the option list; initial sources are the # Populate the option list; initial sources are the
# standard_option_list class attribute, the 'option_list' # standard_option_list class attribute, the 'option_list'
# argument, and the STD_VERSION_OPTION (if 'version' supplied) # argument, and (if applicable) the _add_version_option() and
# and STD_HELP_OPTION globals. # _add_help_option() methods.
self._populate_option_list(option_list, self._populate_option_list(option_list,
add_help=add_help_option) add_help=add_help_option)
...@@ -1004,65 +1124,90 @@ class OptionParser (OptionContainer): ...@@ -1004,65 +1124,90 @@ class OptionParser (OptionContainer):
# -- Private methods ----------------------------------------------- # -- Private methods -----------------------------------------------
# (used by our or OptionContainer's constructor) # (used by our or OptionContainer's constructor)
def _create_option_list (self): def _create_option_list(self):
self.option_list = [] self.option_list = []
self.option_groups = [] self.option_groups = []
self._create_option_mappings() 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: if self.standard_option_list:
self.add_options(self.standard_option_list) self.add_options(self.standard_option_list)
if option_list: if option_list:
self.add_options(option_list) self.add_options(option_list)
if self.version: if self.version:
self.add_option(STD_VERSION_OPTION) self._add_version_option()
if add_help: if add_help:
self.add_option(STD_HELP_OPTION) self._add_help_option()
def _init_parsing_state (self): def _init_parsing_state(self):
# These are set in parse_args() for the convenience of callbacks. # These are set in parse_args() for the convenience of callbacks.
self.rargs = None self.rargs = None
self.largs = None self.largs = None
self.values = None self.values = None
def _get_prog_name(self):
if self.prog:
return self.prog
else:
return get_prog_name()
# -- Simple modifier methods --------------------------------------- # -- Simple modifier methods ---------------------------------------
def set_usage (self, usage): def set_usage(self, usage):
if usage is None: if usage is None:
self.usage = "%prog [options]" self.usage = _("%prog [options]")
elif usage is SUPPRESS_USAGE: elif usage is SUPPRESS_USAGE:
self.usage = None 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:] self.usage = usage[7:]
else: else:
self.usage = usage self.usage = usage
def enable_interspersed_args (self): def enable_interspersed_args(self):
self.allow_interspersed_args = 1 self.allow_interspersed_args = True
def disable_interspersed_args(self):
self.allow_interspersed_args = False
def disable_interspersed_args (self): def set_process_default_values(self, process):
self.allow_interspersed_args = 0 self.process_default_values = process
def set_default (self, dest, value): def set_default(self, dest, value):
self.defaults[dest] = value self.defaults[dest] = value
def set_defaults (self, **kwargs): def set_defaults(self, **kwargs):
self.defaults.update(kwargs) self.defaults.update(kwargs)
def get_default_values (self): def _get_all_options(self):
return Values(self.defaults) 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 ------------------------------------------- # -- OptionGroup methods -------------------------------------------
def add_option_group (self, *args, **kwargs): def add_option_group(self, *args, **kwargs):
# XXX lots of overlap with OptionContainer.add_option() # XXX lots of overlap with OptionContainer.add_option()
if type(args[0]) is types.StringType: if type(args[0]) is types.StringType:
group = OptionGroup(self, *args, **kwargs) group = OptionGroup(self, *args, **kwargs)
...@@ -1078,7 +1223,7 @@ class OptionParser (OptionContainer): ...@@ -1078,7 +1223,7 @@ class OptionParser (OptionContainer):
self.option_groups.append(group) self.option_groups.append(group)
return group return group
def get_option_group (self, opt_str): def get_option_group(self, opt_str):
option = (self._short_opt.get(opt_str) or option = (self._short_opt.get(opt_str) or
self._long_opt.get(opt_str)) self._long_opt.get(opt_str))
if option and option.container is not self: if option and option.container is not self:
...@@ -1088,13 +1233,13 @@ class OptionParser (OptionContainer): ...@@ -1088,13 +1233,13 @@ class OptionParser (OptionContainer):
# -- Option-parsing methods ---------------------------------------- # -- Option-parsing methods ----------------------------------------
def _get_args (self, args): def _get_args(self, args):
if args is None: if args is None:
return sys.argv[1:] return sys.argv[1:]
else: else:
return args[:] # don't modify caller's list return args[:] # don't modify caller's list
def parse_args (self, args=None, values=None): def parse_args(self, args=None, values=None):
""" """
parse_args(args : [string] = sys.argv[1:], parse_args(args : [string] = sys.argv[1:],
values : Values = None) values : Values = None)
...@@ -1133,7 +1278,7 @@ class OptionParser (OptionContainer): ...@@ -1133,7 +1278,7 @@ class OptionParser (OptionContainer):
args = largs + rargs args = largs + rargs
return self.check_values(values, args) return self.check_values(values, args)
def check_values (self, values, args): def check_values(self, values, args):
""" """
check_values(values : Values, args : [string]) check_values(values : Values, args : [string])
-> (values : Values, args : [string]) -> (values : Values, args : [string])
...@@ -1146,7 +1291,7 @@ class OptionParser (OptionContainer): ...@@ -1146,7 +1291,7 @@ class OptionParser (OptionContainer):
""" """
return (values, args) return (values, args)
def _process_args (self, largs, rargs, values): def _process_args(self, largs, rargs, values):
"""_process_args(largs : [string], """_process_args(largs : [string],
rargs : [string], rargs : [string],
values : Values) values : Values)
...@@ -1197,7 +1342,7 @@ class OptionParser (OptionContainer): ...@@ -1197,7 +1342,7 @@ class OptionParser (OptionContainer):
# *empty* -- still a subset of [arg0, ..., arg(i-1)], but # *empty* -- still a subset of [arg0, ..., arg(i-1)], but
# not a very interesting subset! # not a very interesting subset!
def _match_long_opt (self, opt): def _match_long_opt(self, opt):
"""_match_long_opt(opt : string) -> string """_match_long_opt(opt : string) -> string
Determine which long option string 'opt' matches, ie. which one Determine which long option string 'opt' matches, ie. which one
...@@ -1206,7 +1351,7 @@ class OptionParser (OptionContainer): ...@@ -1206,7 +1351,7 @@ class OptionParser (OptionContainer):
""" """
return _match_abbrev(opt, self._long_opt) return _match_abbrev(opt, self._long_opt)
def _process_long_opt (self, rargs, values): def _process_long_opt(self, rargs, values):
arg = rargs.pop(0) arg = rargs.pop(0)
# Value explicitly attached to arg? Pretend it's the next # Value explicitly attached to arg? Pretend it's the next
...@@ -1214,10 +1359,10 @@ class OptionParser (OptionContainer): ...@@ -1214,10 +1359,10 @@ class OptionParser (OptionContainer):
if "=" in arg: if "=" in arg:
(opt, next_arg) = arg.split("=", 1) (opt, next_arg) = arg.split("=", 1)
rargs.insert(0, next_arg) rargs.insert(0, next_arg)
had_explicit_value = 1 had_explicit_value = True
else: else:
opt = arg opt = arg
had_explicit_value = 0 had_explicit_value = False
opt = self._match_long_opt(opt) opt = self._match_long_opt(opt)
option = self._long_opt[opt] option = self._long_opt[opt]
...@@ -1225,9 +1370,9 @@ class OptionParser (OptionContainer): ...@@ -1225,9 +1370,9 @@ class OptionParser (OptionContainer):
nargs = option.nargs nargs = option.nargs
if len(rargs) < nargs: if len(rargs) < nargs:
if nargs == 1: if nargs == 1:
self.error("%s option requires a value" % opt) self.error(_("%s option requires an argument") % opt)
else: else:
self.error("%s option requires %d values" self.error(_("%s option requires %d arguments")
% (opt, nargs)) % (opt, nargs))
elif nargs == 1: elif nargs == 1:
value = rargs.pop(0) value = rargs.pop(0)
...@@ -1236,16 +1381,16 @@ class OptionParser (OptionContainer): ...@@ -1236,16 +1381,16 @@ class OptionParser (OptionContainer):
del rargs[0:nargs] del rargs[0:nargs]
elif had_explicit_value: 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: else:
value = None value = None
option.process(opt, value, values, self) option.process(opt, value, values, self)
def _process_short_opts (self, rargs, values): def _process_short_opts(self, rargs, values):
arg = rargs.pop(0) arg = rargs.pop(0)
stop = 0 stop = False
i = 1 i = 1
for ch in arg[1:]: for ch in arg[1:]:
opt = "-" + ch opt = "-" + ch
...@@ -1253,20 +1398,20 @@ class OptionParser (OptionContainer): ...@@ -1253,20 +1398,20 @@ class OptionParser (OptionContainer):
i += 1 # we have consumed a character i += 1 # we have consumed a character
if not option: if not option:
self.error("no such option: %s" % opt) self.error(_("no such option: %s") % opt)
if option.takes_value(): if option.takes_value():
# Any characters left in arg? Pretend they're the # Any characters left in arg? Pretend they're the
# next arg, and stop consuming characters of arg. # next arg, and stop consuming characters of arg.
if i < len(arg): if i < len(arg):
rargs.insert(0, arg[i:]) rargs.insert(0, arg[i:])
stop = 1 stop = True
nargs = option.nargs nargs = option.nargs
if len(rargs) < nargs: if len(rargs) < nargs:
if nargs == 1: if nargs == 1:
self.error("%s option requires a value" % opt) self.error(_("%s option requires an argument") % opt)
else: else:
self.error("%s option requires %s values" self.error(_("%s option requires %d arguments")
% (opt, nargs)) % (opt, nargs))
elif nargs == 1: elif nargs == 1:
value = rargs.pop(0) value = rargs.pop(0)
...@@ -1285,7 +1430,19 @@ class OptionParser (OptionContainer): ...@@ -1285,7 +1430,19 @@ class OptionParser (OptionContainer):
# -- Feedback methods ---------------------------------------------- # -- Feedback methods ----------------------------------------------
def error (self, msg): 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) """error(msg : string)
Print a usage message incorporating 'msg' to stderr and exit. Print a usage message incorporating 'msg' to stderr and exit.
...@@ -1293,16 +1450,17 @@ class OptionParser (OptionContainer): ...@@ -1293,16 +1450,17 @@ class OptionParser (OptionContainer):
should either exit or raise an exception. should either exit or raise an exception.
""" """
self.print_usage(sys.stderr) 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): def get_usage(self):
if self.usage: if self.usage:
return self.formatter.format_usage( return self.formatter.format_usage(
self.usage.replace("%prog", self._get_prog_name())) self.expand_prog_name(self.usage))
else: else:
return "" return ""
def print_usage (self, file=None): def print_usage(self, file=None):
"""print_usage(file : file = stdout) """print_usage(file : file = stdout)
Print the usage message for the current program (self.usage) to Print the usage message for the current program (self.usage) to
...@@ -1314,13 +1472,13 @@ class OptionParser (OptionContainer): ...@@ -1314,13 +1472,13 @@ class OptionParser (OptionContainer):
if self.usage: if self.usage:
print >>file, self.get_usage() print >>file, self.get_usage()
def get_version (self): def get_version(self):
if self.version: if self.version:
return self.version.replace("%prog", self._get_prog_name()) return self.expand_prog_name(self.version)
else: else:
return "" return ""
def print_version (self, file=None): def print_version(self, file=None):
"""print_version(file : file = stdout) """print_version(file : file = stdout)
Print the version message for this program (self.version) to Print the version message for this program (self.version) to
...@@ -1331,12 +1489,12 @@ class OptionParser (OptionContainer): ...@@ -1331,12 +1489,12 @@ class OptionParser (OptionContainer):
if self.version: if self.version:
print >>file, self.get_version() print >>file, self.get_version()
def format_option_help (self, formatter=None): def format_option_help(self, formatter=None):
if formatter is None: if formatter is None:
formatter = self.formatter formatter = self.formatter
formatter.store_option_strings(self) formatter.store_option_strings(self)
result = [] result = []
result.append(formatter.format_heading("options")) result.append(formatter.format_heading(_("options")))
formatter.indent() formatter.indent()
if self.option_list: if self.option_list:
result.append(OptionContainer.format_option_help(self, formatter)) result.append(OptionContainer.format_option_help(self, formatter))
...@@ -1348,7 +1506,7 @@ class OptionParser (OptionContainer): ...@@ -1348,7 +1506,7 @@ class OptionParser (OptionContainer):
# Drop the last "\n", or the header if no options or option groups: # Drop the last "\n", or the header if no options or option groups:
return "".join(result[:-1]) return "".join(result[:-1])
def format_help (self, formatter=None): def format_help(self, formatter=None):
if formatter is None: if formatter is None:
formatter = self.formatter formatter = self.formatter
result = [] result = []
...@@ -1359,7 +1517,7 @@ class OptionParser (OptionContainer): ...@@ -1359,7 +1517,7 @@ class OptionParser (OptionContainer):
result.append(self.format_option_help(formatter)) result.append(self.format_option_help(formatter))
return "".join(result) return "".join(result)
def print_help (self, file=None): def print_help(self, file=None):
"""print_help(file : file = stdout) """print_help(file : file = stdout)
Print an extended help message, listing all options and any Print an extended help message, listing all options and any
...@@ -1372,7 +1530,7 @@ class OptionParser (OptionContainer): ...@@ -1372,7 +1530,7 @@ class OptionParser (OptionContainer):
# class OptionParser # class OptionParser
def _match_abbrev (s, wordmap): def _match_abbrev(s, wordmap):
"""_match_abbrev(s : string, wordmap : {string : Option}) -> string """_match_abbrev(s : string, wordmap : {string : Option}) -> string
Return the string key in 'wordmap' for which 's' is an unambiguous Return the string key in 'wordmap' for which 's' is an unambiguous
...@@ -1390,10 +1548,10 @@ def _match_abbrev (s, wordmap): ...@@ -1390,10 +1548,10 @@ def _match_abbrev (s, wordmap):
if len(possibilities) == 1: if len(possibilities) == 1:
return possibilities[0] return possibilities[0]
elif not possibilities: elif not possibilities:
raise BadOptionError("no such option: %s" % s) raise BadOptionError(_("no such option: %s") % s)
else: else:
# More than one possible completion: ambiguous prefix. # More than one possible completion: ambiguous prefix.
raise BadOptionError("ambiguous option: %s (%s?)" raise BadOptionError(_("ambiguous option: %s (%s?)")
% (s, ", ".join(possibilities))) % (s, ", ".join(possibilities)))
......
...@@ -20,14 +20,7 @@ from test import test_support ...@@ -20,14 +20,7 @@ from test import test_support
from optparse import make_option, Option, IndentedHelpFormatter, \ from optparse import make_option, Option, IndentedHelpFormatter, \
TitledHelpFormatter, OptionParser, OptionContainer, OptionGroup, \ TitledHelpFormatter, OptionParser, OptionContainer, OptionGroup, \
SUPPRESS_HELP, SUPPRESS_USAGE, OptionError, OptionConflictError, \ SUPPRESS_HELP, SUPPRESS_USAGE, OptionError, OptionConflictError, \
BadOptionError, OptionValueError BadOptionError, OptionValueError, _match_abbrev
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)
class BaseTest(unittest.TestCase): class BaseTest(unittest.TestCase):
def assertParseOK(self, args, expected_opts, expected_positional_args): def assertParseOK(self, args, expected_opts, expected_positional_args):
...@@ -60,50 +53,62 @@ Args were %(args)s.""" % locals ()) ...@@ -60,50 +53,62 @@ Args were %(args)s.""" % locals ())
return (options, positional_args) return (options, positional_args)
def assertRaises(self, func, expected_exception, expected_output, def assertRaises(self,
get_output=None, func,
funcargs=[], funckwargs={}): args,
kwargs,
expected_exception,
expected_output,
get_output=None,
exact_match=False):
"""Assert the expected exception is raised when calling a function. """Assert the expected exception is raised when calling a function.
Also check whether the right error message is given for a given error. Also check whether the right error message is given for a given error.
Keyword arguments: Arguments:
func -- The function to be called. func -- the function to call
expected_exception -- The exception that should be raised. args -- positional arguments to `func`
expected_output -- The output we expect to see. kwargs -- keyword arguments to `func`
get_output -- The function to call to get the output. expected_exception -- exception that should be raised
funcargs -- The arguments `func` should be called with. expected_output -- output we expect to see
funckwargs -- The keyword arguments `func` should be called with. 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. Returns the exception raised for further testing.
""" """
if args is None:
args = ()
if kwargs is None:
kwargs = {}
if get_output is None: if get_output is None:
get_output = self.exception get_output = self.exception
try: try:
out = func(*funcargs, **funckwargs) out = func(*args, **kwargs)
except expected_exception, err: except expected_exception, err:
output = get_output(err) actual_output = get_output(err)
self.failUnless(output.find(expected_output) != -1, if exact_match:
""" match = actual_output == expected_exception
Message was: else:
%(output)s match = actual_output.find(expected_output) != -1
Should contain:
%(expected_output)s self.assert_(match,
Function called: """mismatched output
%(func)s expected output:
With args/kwargs: '''%(expected_output)s'''
%(funcargs)s/%(funckwargs)s""" % locals()) actual output:
'''%(actual_output)s'''
""" % locals())
return err return err
else: else:
self.fail(""" self.fail("""expected exception %(expected_exception)s not raised
No %(expected_exception)s raised. called %(func)r
Function called: with args %(args)r
%(func)s and kwargs %(kwargs)r
With args/kwargs: """ % locals ())
%(funcargs)s/%(funckwargs)s""" % locals ())
# -- Functions to be used as the get_output argument to assertRaises ------ # -- Functions to be used as the get_output argument to assertRaises ------
...@@ -113,23 +118,38 @@ With args/kwargs: ...@@ -113,23 +118,38 @@ With args/kwargs:
def redirected_stdout(self, err): def redirected_stdout(self, err):
return sys.stdout.getvalue() return sys.stdout.getvalue()
def redirected_stderr(self, err):
return sys.stderr.getvalue()
# -- Assertions used in more than one class -------------------- # -- Assertions used in more than one class --------------------
def assertParseFail(self, cmdline_args, expected_output): def assertParseFail(self, cmdline_args, expected_output):
"""Assert the parser fails with the expected message.""" """Assert the parser fails with the expected message."""
self.assertRaises(self.parser.parse_args, SystemExit, expected_output, sys.stderr = StringIO()
funcargs=[cmdline_args]) 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): def assertStdoutEquals(self, cmdline_args, expected_output):
"""Assert the parser prints the expected output on stdout.""" """Assert the parser prints the expected output on stdout."""
sys.stdout = StringIO() sys.stdout = StringIO()
self.assertRaises(self.parser.parse_args, SystemExit, expected_output, self.assertRaises(self.parser.parse_args, (cmdline_args,), None,
self.redirected_stdout, [cmdline_args]) SystemExit, expected_output,
self.redirected_stdout)
sys.stdout = sys.__stdout__ sys.stdout = sys.__stdout__
def assertTypeError(self, func, expected_output, *args): def assertTypeError(self, func, expected_output, *args):
"""Assert a TypeError is raised when executing func.""" """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 ------------------------------------- # -- Test make_option() aka Option -------------------------------------
...@@ -142,8 +162,8 @@ class TestOptionChecks(BaseTest): ...@@ -142,8 +162,8 @@ class TestOptionChecks(BaseTest):
self.parser = OptionParser(usage=SUPPRESS_USAGE) self.parser = OptionParser(usage=SUPPRESS_USAGE)
def assertOptionError(self, expected_output, args=[], kwargs={}): def assertOptionError(self, expected_output, args=[], kwargs={}):
self.assertRaises(make_option, OptionError, expected_output, self.assertRaises(make_option, args, kwargs,
funcargs=args, funckwargs=kwargs) OptionError, expected_output)
def test_opt_string_empty(self): def test_opt_string_empty(self):
self.assertTypeError(make_option, self.assertTypeError(make_option,
...@@ -175,6 +195,8 @@ class TestOptionChecks(BaseTest): ...@@ -175,6 +195,8 @@ class TestOptionChecks(BaseTest):
def test_type_invalid(self): def test_type_invalid(self):
self.assertOptionError("invalid option type: 'foo'", self.assertOptionError("invalid option type: 'foo'",
["-b"], {'type': 'foo'}) ["-b"], {'type': 'foo'})
self.assertOptionError("invalid option type: 'tuple'",
["-b"], {'type': tuple})
def test_no_type_for_action(self): def test_no_type_for_action(self):
self.assertOptionError("must not supply a type for action 'count'", self.assertOptionError("must not supply a type for action 'count'",
...@@ -304,8 +326,204 @@ class TestOptionParser(BaseTest): ...@@ -304,8 +326,204 @@ class TestOptionParser(BaseTest):
self.assert_removed() self.assert_removed()
def test_remove_nonexistent(self): def test_remove_nonexistent(self):
self.assertRaises(self.parser.remove_option, ValueError, self.assertRaises(self.parser.remove_option, ('foo',), None,
"no such option 'foo'", funcargs=['foo']) 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() ------------------------------------------ # -- Test parser.parse_args() ------------------------------------------
...@@ -318,7 +536,7 @@ class TestStandard(BaseTest): ...@@ -318,7 +536,7 @@ class TestStandard(BaseTest):
self.parser = OptionParser(usage=SUPPRESS_USAGE, option_list=options) self.parser = OptionParser(usage=SUPPRESS_USAGE, option_list=options)
def test_required_value(self): 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): def test_invalid_integer(self):
self.assertParseFail(["-b", "5x"], self.assertParseFail(["-b", "5x"],
...@@ -580,7 +798,7 @@ class TestNArgs(BaseTest): ...@@ -580,7 +798,7 @@ class TestNArgs(BaseTest):
def test_nargs_required_values(self): def test_nargs_required_values(self):
self.assertParseFail(["--point", "1.0", "3.5"], self.assertParseFail(["--point", "1.0", "3.5"],
"--point option requires 3 values") "--point option requires 3 arguments")
class TestNArgsAppend(BaseTest): class TestNArgsAppend(BaseTest):
def setUp(self): def setUp(self):
...@@ -597,7 +815,7 @@ class TestNArgsAppend(BaseTest): ...@@ -597,7 +815,7 @@ class TestNArgsAppend(BaseTest):
def test_nargs_append_required_values(self): def test_nargs_append_required_values(self):
self.assertParseFail(["-f4,3"], self.assertParseFail(["-f4,3"],
"-f option requires 2 values") "-f option requires 2 arguments")
def test_nargs_append_simple(self): def test_nargs_append_simple(self):
self.assertParseOK(["--foo=3", "4"], self.assertParseOK(["--foo=3", "4"],
...@@ -612,22 +830,6 @@ class TestVersion(BaseTest): ...@@ -612,22 +830,6 @@ class TestVersion(BaseTest):
self.assertStdoutEquals(["--version"], "bar 0.1\n") self.assertStdoutEquals(["--version"], "bar 0.1\n")
sys.argv[0] = oldargv 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): def test_no_version(self):
self.parser = OptionParser(usage=SUPPRESS_USAGE) self.parser = OptionParser(usage=SUPPRESS_USAGE)
self.assertParseFail(["--version"], self.assertParseFail(["--version"],
...@@ -673,8 +875,8 @@ class TestOptionGroup(BaseTest): ...@@ -673,8 +875,8 @@ class TestOptionGroup(BaseTest):
def test_add_group_wrong_parser(self): def test_add_group_wrong_parser(self):
group = OptionGroup(self.parser, "Spam") group = OptionGroup(self.parser, "Spam")
group.parser = OptionParser() group.parser = OptionParser()
self.assertRaises(self.parser.add_option_group, ValueError, self.assertRaises(self.parser.add_option_group, (group,), None,
"invalid OptionGroup (wrong parser)", funcargs=[group]) ValueError, "invalid OptionGroup (wrong parser)")
def test_group_manipulate(self): def test_group_manipulate(self):
group = self.parser.add_option_group("Group 2", group = self.parser.add_option_group("Group 2",
...@@ -794,7 +996,22 @@ class TestCallback(BaseTest): ...@@ -794,7 +996,22 @@ class TestCallback(BaseTest):
{'filename': "foo", 'x': 42}, {'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): def setUp(self):
options = [make_option("-p", "--point", action="callback", options = [make_option("-p", "--point", action="callback",
callback=self.process_tuple, callback=self.process_tuple,
...@@ -819,7 +1036,7 @@ class TestCallBackExtraArgs(BaseTest): ...@@ -819,7 +1036,7 @@ class TestCallBackExtraArgs(BaseTest):
{'points': [(1,2,3), (4,5,6)]}, {'points': [(1,2,3), (4,5,6)]},
[]) [])
class TestCallBackMeddleArgs(BaseTest): class TestCallbackMeddleArgs(BaseTest):
def setUp(self): def setUp(self):
options = [make_option(str(x), action="callback", options = [make_option(str(x), action="callback",
callback=self.process_n, dest='things') callback=self.process_n, dest='things')
...@@ -848,7 +1065,7 @@ class TestCallBackMeddleArgs(BaseTest): ...@@ -848,7 +1065,7 @@ class TestCallBackMeddleArgs(BaseTest):
{'things': [('foo', '--')]}, {'things': [('foo', '--')]},
[2]) [2])
class TestCallBackManyArgs(BaseTest): class TestCallbackManyArgs(BaseTest):
def setUp(self): def setUp(self):
options = [make_option("-a", "--apple", action="callback", nargs=2, options = [make_option("-a", "--apple", action="callback", nargs=2,
callback=self.process_many, type="string"), callback=self.process_many, type="string"),
...@@ -870,10 +1087,10 @@ class TestCallBackManyArgs(BaseTest): ...@@ -870,10 +1087,10 @@ class TestCallBackManyArgs(BaseTest):
self.assertParseOK(["-a", "foo", "bar", "--apple", "ding", "dong", self.assertParseOK(["-a", "foo", "bar", "--apple", "ding", "dong",
"-b", "1", "2", "3", "--bob", "-666", "42", "-b", "1", "2", "3", "--bob", "-666", "42",
"0"], "0"],
{}, {"apple": None, "bob": None},
[]) [])
class TestCallBackCheckAbbrev(BaseTest): class TestCallbackCheckAbbrev(BaseTest):
def setUp(self): def setUp(self):
self.parser = OptionParser() self.parser = OptionParser()
self.parser.add_option("--foo-bar", action="callback", self.parser.add_option("--foo-bar", action="callback",
...@@ -885,7 +1102,7 @@ class TestCallBackCheckAbbrev(BaseTest): ...@@ -885,7 +1102,7 @@ class TestCallBackCheckAbbrev(BaseTest):
def test_abbrev_callback_expansion(self): def test_abbrev_callback_expansion(self):
self.assertParseOK(["--foo"], {}, []) self.assertParseOK(["--foo"], {}, [])
class TestCallBackVarArgs(BaseTest): class TestCallbackVarArgs(BaseTest):
def setUp(self): def setUp(self):
options = [make_option("-a", type="int", nargs=2, dest="a"), options = [make_option("-a", type="int", nargs=2, dest="a"),
make_option("-b", action="store_true", dest="b"), make_option("-b", action="store_true", dest="b"),
...@@ -950,13 +1167,12 @@ class ConflictBase(BaseTest): ...@@ -950,13 +1167,12 @@ class ConflictBase(BaseTest):
class TestConflict(ConflictBase): class TestConflict(ConflictBase):
"""Use the default conflict resolution for Optik 1.2: error.""" """Use the default conflict resolution for Optik 1.2: error."""
def assert_conflict_error(self, func): def assert_conflict_error(self, func):
err = self.assertRaises(func, OptionConflictError, err = self.assertRaises(
"option -v/--version: conflicting option " func, ("-v", "--version"), {'action' : "callback",
"string(s): -v", 'callback' : self.show_version,
funcargs=["-v", "--version"], 'help' : "show version"},
funckwargs={'action':"callback", OptionConflictError,
'callback':self.show_version, "option -v/--version: conflicting option string(s): -v")
'help':"show version"})
self.assertEqual(err.msg, "conflicting option string(s): -v") self.assertEqual(err.msg, "conflicting option string(s): -v")
self.assertEqual(err.option_id, "-v/--version") self.assertEqual(err.option_id, "-v/--version")
...@@ -969,9 +1185,9 @@ class TestConflict(ConflictBase): ...@@ -969,9 +1185,9 @@ class TestConflict(ConflictBase):
self.assert_conflict_error(group.add_option) self.assert_conflict_error(group.add_option)
def test_no_such_conflict_handler(self): def test_no_such_conflict_handler(self):
self.assertRaises(self.parser.set_conflict_handler, ValueError, self.assertRaises(
"invalid conflict_resolution value 'foo'", self.parser.set_conflict_handler, ('foo',), None,
funcargs=['foo']) ValueError, "invalid conflict_resolution value 'foo'")
class TestConflictIgnore(ConflictBase): class TestConflictIgnore(ConflictBase):
...@@ -1082,8 +1298,60 @@ options: ...@@ -1082,8 +1298,60 @@ options:
# -- Other testing. ---------------------------------------------------- # -- 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): class TestHelp(BaseTest):
def setUp(self): def setUp(self):
self.parser = self.make_parser(80)
def make_parser(self, columns):
options = [ options = [
make_option("-a", type="string", dest='a', make_option("-a", type="string", dest='a',
metavar="APPLE", help="throw APPLEs at basket"), metavar="APPLE", help="throw APPLEs at basket"),
...@@ -1095,9 +1363,8 @@ class TestHelp(BaseTest): ...@@ -1095,9 +1363,8 @@ class TestHelp(BaseTest):
make_option("--foo", action="append", type="string", dest='foo', make_option("--foo", action="append", type="string", dest='foo',
help="store FOO in the foo list for later fooing"), help="store FOO in the foo list for later fooing"),
] ]
os.environ['COLUMNS'] = str(columns)
usage = "%prog [options]" return OptionParser(option_list=options)
self.parser = OptionParser(usage=usage, option_list=options)
def assertHelpEquals(self, expected_output): def assertHelpEquals(self, expected_output):
# This trick is used to make optparse believe bar.py is being executed. # This trick is used to make optparse believe bar.py is being executed.
...@@ -1109,62 +1376,30 @@ class TestHelp(BaseTest): ...@@ -1109,62 +1376,30 @@ class TestHelp(BaseTest):
sys.argv[0] = oldargv sys.argv[0] = oldargv
def test_help(self): def test_help(self):
self.assertHelpEquals("""\ self.assertHelpEquals(_expected_help_basic)
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
""")
def test_help_old_usage(self): def test_help_old_usage(self):
self.parser.set_usage("usage: %prog [options]") self.parser.set_usage("usage: %prog [options]")
self.assertHelpEquals("""\ self.assertHelpEquals(_expected_help_basic)
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
""")
def test_help_long_opts_first(self): def test_help_long_opts_first(self):
self.parser.formatter.short_first = 0 self.parser.formatter.short_first = 0
self.assertHelpEquals("""\ self.assertHelpEquals(_expected_help_long_opts_first)
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
""")
def test_help_title_formatter(self): def test_help_title_formatter(self):
self.parser.formatter = TitledHelpFormatter() self.parser.formatter = TitledHelpFormatter()
self.assertHelpEquals("""\ self.assertHelpEquals(_expected_help_title_formatter)
Usage
=====
bar.py [options]
options def test_wrap_columns(self):
======= # Ensure that wrapping respects $COLUMNS environment variable.
-aAPPLE throw APPLEs at basket # Need to reconstruct the parser, since that's the only time
--boo=NUM, -bNUM shout "boo!" NUM times (in order to frighten away all # we look at $COLUMNS.
the evil spirits that cause trouble and mayhem) self.parser = self.make_parser(60)
--foo=FOO store FOO in the foo list for later fooing self.assertHelpEquals(_expected_help_short_lines)
--help, -h show this help message and exit
""")
def test_help_description_groups(self): def test_help_description_groups(self):
self.parser.set_description( 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.") "an option group as well as single options.")
group = OptionGroup( group = OptionGroup(
...@@ -1177,21 +1412,26 @@ options ...@@ -1177,21 +1412,26 @@ options
self.assertHelpEquals("""\ self.assertHelpEquals("""\
usage: bar.py [options] usage: bar.py [options]
This is the program description. This program has an option group as well as This is the program description for bar.py. bar.py has an option group as
single options. well as single options.
options: options:
-aAPPLE throw APPLEs at basket -a APPLE throw APPLEs at basket
-bNUM, --boo=NUM shout "boo!" NUM times (in order to frighten away all -b NUM, --boo=NUM shout "boo!" NUM times (in order to frighten away all the
the evil spirits that cause trouble and mayhem) evil spirits that cause trouble and mayhem)
--foo=FOO store FOO in the foo list for later fooing --foo=FOO store FOO in the foo list for later fooing
-h, --help show this help message and exit -h, --help show this help message and exit
Dangerous Options: Dangerous Options:
Caution: use of these options is at your own risk. It is believed that Caution: use of these options is at your own risk. It is believed
some of them bite. that some of them bite.
-g Group option.
-g Group option.
""") """)
class TestMatchAbbrev(BaseTest): class TestMatchAbbrev(BaseTest):
def test_match_abbrev(self): def test_match_abbrev(self):
self.assertEqual(_match_abbrev("--f", self.assertEqual(_match_abbrev("--f",
...@@ -1205,15 +1445,23 @@ class TestMatchAbbrev(BaseTest): ...@@ -1205,15 +1445,23 @@ class TestMatchAbbrev(BaseTest):
s = "--f" s = "--f"
wordmap = {"--foz": None, "--foo": None, "--fie": None} wordmap = {"--foz": None, "--foo": None, "--fie": None}
possibilities = ", ".join(wordmap.keys()) possibilities = ", ".join(wordmap.keys())
self.assertRaises(_match_abbrev, BadOptionError, self.assertRaises(
"ambiguous option: --f (%s?)" % possibilities, _match_abbrev, (s, wordmap), None,
funcargs=[s, wordmap]) BadOptionError, "ambiguous option: --f (%s?)" % possibilities)
def test_main():
def _testclasses():
mod = sys.modules[__name__] mod = sys.modules[__name__]
test_support.run_unittest( return [getattr(mod, name) for name in dir(mod) if name.startswith('Test')]
*[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():
test_support.run_suite(suite())
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment