summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorGreg Ward <gward@python.net>2004-07-31 16:15:44 (GMT)
committerGreg Ward <gward@python.net>2004-07-31 16:15:44 (GMT)
commiteba20e601520adef966da23ff149150da362ebb6 (patch)
treee8655146180fa6f0a7871cb87ab6afd6e597e2ad /Lib
parent7357222d0e441725d6d8ccd7269a542ac340de89 (diff)
downloadcpython-eba20e601520adef966da23ff149150da362ebb6.zip
cpython-eba20e601520adef966da23ff149150da362ebb6.tar.gz
cpython-eba20e601520adef966da23ff149150da362ebb6.tar.bz2
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.)
Diffstat (limited to 'Lib')
-rw-r--r--Lib/optparse.py548
-rw-r--r--Lib/test/test_optparse.py540
2 files changed, 747 insertions, 341 deletions
diff --git a/Lib/optparse.py b/Lib/optparse.py
index d3593d3..f30fc45 100644
--- a/Lib/optparse.py
+++ b/Lib/optparse.py
@@ -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
# 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 @@ __all__ = ['Option',
'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,12 +69,23 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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):
+ def __init__(self, msg):
self.msg = msg
- def __str__ (self):
+ def __str__(self):
return self.msg
@@ -85,11 +95,11 @@ class OptionError (OptParseError):
inconsistent arguments.
"""
- def __init__ (self, msg, option):
+ def __init__(self, msg, option):
self.msg = msg
self.option_id = str(option)
- def __str__ (self):
+ def __str__(self):
if self.option_id:
return "option %s: %s" % (self.option_id, self.msg)
else:
@@ -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,51 +140,108 @@ 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").
"""
- def __init__ (self,
- indent_increment,
- max_help_position,
- width,
- short_first):
+ 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 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.level += 1
- def dedent (self):
+ def dedent(self):
self.current_indent -= self.indent_increment
assert self.current_indent >= 0, "Indent decreased below 0."
self.level -= 1
- def format_usage (self, usage):
+ def format_usage(self, usage):
raise NotImplementedError, "subclasses must implement"
- def format_heading (self, heading):
+ def format_heading(self, heading):
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
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
- 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 opt strings and metavars
# eg. ("-x", or "-fFILENAME, --file=FILENAME")
@@ -188,7 +257,7 @@ class HelpFormatter:
# -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 @@ class HelpFormatter:
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:]])
@@ -206,29 +276,32 @@ class HelpFormatter:
result.append("\n")
return "".join(result)
- def store_option_strings (self, parser):
+ def store_option_strings(self, parser):
self.indent()
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):
+ 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
@@ -244,18 +317,18 @@ class IndentedHelpFormatter (HelpFormatter):
"""Format help with indented section bodies.
"""
- def __init__ (self,
- indent_increment=2,
- max_help_position=24,
- width=79,
- short_first=1):
+ def __init__(self,
+ indent_increment=2,
+ max_help_position=24,
+ 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
+ def format_usage(self, usage):
+ return _("usage: %s\n") % usage
- def format_heading (self, heading):
+ def format_heading(self, heading):
return "%*s%s:\n" % (self.current_indent, "", heading)
@@ -263,34 +336,33 @@ class TitledHelpFormatter (HelpFormatter):
"""Format help with underlined section headers.
"""
- def __init__ (self,
- indent_increment=0,
- max_help_position=24,
- width=79,
- short_first=0):
+ def __init__(self,
+ indent_increment=0,
+ max_help_position=24,
+ 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)
+ def format_usage(self, 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))
-_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):
+def check_builtin(option, opt, value):
(cvt, what) = _builtin_cvt[option.type]
try:
return cvt(value)
except ValueError:
raise OptionValueError(
- #"%s: invalid %s argument %r" % (opt, what, value))
- "option %s: invalid %s value: %r" % (opt, what, value))
+ _("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:
@@ -392,7 +464,7 @@ class Option:
TYPE_CHECKER = { "int" : check_builtin,
"long" : check_builtin,
"float" : check_builtin,
- "complex" : check_builtin,
+ "complex": check_builtin,
"choice" : check_choice,
}
@@ -410,7 +482,7 @@ class Option:
# -- Constructor/initialization methods ----------------------------
- def __init__ (self, *opts, **attrs):
+ def __init__(self, *opts, **attrs):
# Set _short_opts, _long_opts attrs from 'opts' tuple.
# Have to be set now, in case no option strings are supplied.
self._short_opts = []
@@ -429,7 +501,7 @@ class Option:
for checker in self.CHECK_METHODS:
checker(self)
- def _check_opt_strings (self, opts):
+ def _check_opt_strings(self, opts):
# Filter out None because early versions of Optik had exactly
# one short option and one long option, either of which
# could be None.
@@ -438,7 +510,7 @@ class Option:
raise TypeError("at least one option string must be supplied")
return opts
- def _set_opt_strings (self, opts):
+ def _set_opt_strings(self, opts):
for opt in opts:
if len(opt) < 2:
raise OptionError(
@@ -459,7 +531,7 @@ class Option:
self)
self._long_opts.append(opt)
- def _set_attrs (self, attrs):
+ def _set_attrs(self, attrs):
for attr in self.ATTRS:
if attrs.has_key(attr):
setattr(self, attr, attrs[attr])
@@ -477,13 +549,13 @@ class Option:
# -- Constructor validation methods --------------------------------
- def _check_action (self):
+ def _check_action(self):
if self.action is None:
self.action = "store"
elif self.action not in self.ACTIONS:
raise OptionError("invalid action: %r" % self.action, self)
- def _check_type (self):
+ def _check_type(self):
if self.type is None:
# XXX should factor out another class attr here: list of
# actions that *require* a type
@@ -495,6 +567,12 @@ class Option:
# 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:
@@ -514,9 +592,13 @@ class Option:
raise OptionError(
"must not supply choices for type %r" % self.type, self)
- def _check_dest (self):
- if self.action in self.STORE_ACTIONS and self.dest is None:
- # No destination given, and we need one for this action.
+ def _check_dest(self):
+ # 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:
@@ -525,13 +607,13 @@ class Option:
else:
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:
raise OptionError(
"'const' must not be supplied for action %r" % self.action,
self)
- def _check_nargs (self):
+ def _check_nargs(self):
if self.action in self.TYPED_ACTIONS:
if self.nargs is None:
self.nargs = 1
@@ -540,7 +622,7 @@ class Option:
"'nargs' must not be supplied for action %r" % self.action,
self)
- def _check_callback (self):
+ def _check_callback(self):
if self.action == "callback":
if not callable(self.callback):
raise OptionError(
@@ -579,31 +661,42 @@ class Option:
# -- Miscellaneous methods -----------------------------------------
- def __str__ (self):
+ def __str__(self):
return "/".join(self._short_opts + self._long_opts)
- def takes_value (self):
+ __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 --------------------------------------------
- def check_value (self, opt, value):
+ def check_value(self, opt, value):
checker = self.TYPE_CHECKER.get(self.type)
if checker is None:
return value
else:
return checker(self, opt, value)
- def process (self, opt, value, values, parser):
-
- # First, convert the value(s) to the right type. Howl if any
- # value(s) are bogus.
+ def convert_value(self, opt, value):
if value is not None:
if self.nargs == 1:
- value = self.check_value(opt, value)
+ return self.check_value(opt, value)
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.
# This is a separate method to make life easier for
@@ -611,7 +704,7 @@ class Option:
return self.take_action(
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":
setattr(values, dest, value)
elif action == "store_const":
@@ -642,33 +735,44 @@ class Option:
# 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:
- def __init__ (self, defaults=None):
+ def __init__(self, defaults=None):
if defaults:
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 _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
use keys from dict that already have a corresponding attribute
@@ -681,7 +785,7 @@ class Values:
if dval is not None:
setattr(self, attr, dval)
- def _update_loose (self, dict):
+ def _update_loose(self, dict):
"""
Update the option values from an arbitrary dictionary,
using all keys from the dictionary regardless of whether
@@ -689,7 +793,7 @@ class Values:
"""
self.__dict__.update(dict)
- def _update (self, dict, mode):
+ def _update(self, dict, mode):
if mode == "careful":
self._update_careful(dict)
elif mode == "loose":
@@ -697,17 +801,17 @@ class Values:
else:
raise ValueError, "invalid update mode: %r" % mode
- def read_module (self, modname, mode="careful"):
+ def read_module(self, modname, mode="careful"):
__import__(modname)
mod = sys.modules[modname]
self._update(vars(mod), mode)
- def read_file (self, filename, mode="careful"):
+ def read_file(self, filename, mode="careful"):
vars = {}
execfile(filename, vars)
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:
setattr(self, attr, value)
return getattr(self, attr)
@@ -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.
# This method must be provided by subclasses, and it must
# initialize at least the following instance attributes:
@@ -756,7 +860,7 @@ class OptionContainer:
self.set_conflict_handler(conflict_handler)
self.set_description(description)
- def _create_option_mappings (self):
+ def _create_option_mappings(self):
# For use by OptionParser constructor -- create the master
# option mappings used by this OptionParser and all
# OptionGroups that it owns.
@@ -765,25 +869,28 @@ class OptionContainer:
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
# mappings from the OptionParser that owns this OptionGroup.
self._short_opt = parser._short_opt
self._long_opt = parser._long_opt
self.defaults = parser.defaults
- def set_conflict_handler (self, handler):
+ def set_conflict_handler(self, handler):
if handler not in ("ignore", "error", "resolve"):
raise ValueError, "invalid conflict_resolution value %r" % handler
self.conflict_handler = handler
- def set_description (self, description):
+ def set_description(self, description):
self.description = description
+ def get_description(self):
+ return self.description
+
# -- Option-adding methods -----------------------------------------
- def _check_conflict (self, option):
+ def _check_conflict(self, option):
conflict_opts = []
for opt in option._short_opts:
if self._short_opt.has_key(opt):
@@ -812,7 +919,7 @@ class OptionContainer:
if not (c_option._short_opts or c_option._long_opts):
c_option.container.option_list.remove(c_option)
- def add_option (self, *args, **kwargs):
+ def add_option(self, *args, **kwargs):
"""add_option(Option)
add_option(opt_str, ..., kwarg=val, ...)
"""
@@ -842,21 +949,21 @@ class OptionContainer:
return option
- def add_options (self, option_list):
+ def add_options(self, option_list):
for option in option_list:
self.add_option(option)
# -- Option query/removal methods ----------------------------------
- def get_option (self, opt_str):
+ def get_option(self, opt_str):
return (self._short_opt.get(opt_str) or
self._long_opt.get(opt_str))
- def has_option (self, opt_str):
+ def has_option(self, opt_str):
return (self._short_opt.has_key(opt_str) or
self._long_opt.has_key(opt_str))
- def remove_option (self, opt_str):
+ def remove_option(self, opt_str):
option = self._short_opt.get(opt_str)
if option is None:
option = self._long_opt.get(opt_str)
@@ -872,7 +979,7 @@ class OptionContainer:
# -- Help-formatting methods ---------------------------------------
- def format_option_help (self, formatter):
+ def format_option_help(self, formatter):
if not self.option_list:
return ""
result = []
@@ -881,38 +988,36 @@ class OptionContainer:
result.append(formatter.format_option(option))
return "".join(result)
- def format_description (self, formatter):
- if self.description:
- return formatter.format_description(self.description)
- else:
- return ""
+ def format_description(self, formatter):
+ return formatter.format_description(self.get_description())
- def format_help (self, formatter):
+ 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):
- def __init__ (self, parser, title, description=None):
+ def __init__(self, parser, title, description=None):
self.parser = parser
OptionContainer.__init__(
self, parser.option_class, parser.conflict_handler, description)
self.title = title
- def _create_option_list (self):
+ def _create_option_list(self):
self.option_list = []
self._share_option_mappings(self.parser)
- def set_title (self, title):
+ def set_title(self, title):
self.title = title
# -- Help-formatting methods ---------------------------------------
- def format_help (self, formatter):
+ def format_help(self, formatter):
result = formatter.format_heading(self.title)
formatter.indent()
result += OptionContainer.format_help(self, formatter)
@@ -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
@@ -972,30 +1090,32 @@ class OptionParser (OptionContainer):
standard_option_list = []
- def __init__ (self,
- usage=None,
- option_list=None,
- option_class=Option,
- version=None,
- conflict_handler="error",
- description=None,
- formatter=None,
- add_help_option=1,
- prog=None):
+ def __init__(self,
+ usage=None,
+ option_list=None,
+ option_class=Option,
+ version=None,
+ conflict_handler="error",
+ description=None,
+ formatter=None,
+ 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)
@@ -1004,65 +1124,90 @@ class OptionParser (OptionContainer):
# -- Private methods -----------------------------------------------
# (used by our or OptionContainer's constructor)
- def _create_option_list (self):
+ def _create_option_list(self):
self.option_list = []
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):
+ def _init_parsing_state(self):
# These are set in parse_args() for the convenience of callbacks.
self.rargs = None
self.largs = None
self.values = None
- def _get_prog_name(self):
- if self.prog:
- return self.prog
- else:
- return get_prog_name()
# -- Simple modifier methods ---------------------------------------
- def set_usage (self, usage):
+ 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
+ def enable_interspersed_args(self):
+ self.allow_interspersed_args = True
+
+ def disable_interspersed_args(self):
+ self.allow_interspersed_args = False
- def disable_interspersed_args (self):
- self.allow_interspersed_args = 0
+ def set_process_default_values(self, process):
+ self.process_default_values = process
- def set_default (self, dest, value):
+ def set_default(self, dest, value):
self.defaults[dest] = value
- def set_defaults (self, **kwargs):
+ def set_defaults(self, **kwargs):
self.defaults.update(kwargs)
- def get_default_values (self):
- return Values(self.defaults)
+ 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 -------------------------------------------
- def add_option_group (self, *args, **kwargs):
+ def add_option_group(self, *args, **kwargs):
# XXX lots of overlap with OptionContainer.add_option()
if type(args[0]) is types.StringType:
group = OptionGroup(self, *args, **kwargs)
@@ -1078,7 +1223,7 @@ class OptionParser (OptionContainer):
self.option_groups.append(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
self._long_opt.get(opt_str))
if option and option.container is not self:
@@ -1088,13 +1233,13 @@ class OptionParser (OptionContainer):
# -- Option-parsing methods ----------------------------------------
- def _get_args (self, args):
+ def _get_args(self, args):
if args is None:
return sys.argv[1:]
else:
return args[:] # don't modify caller's list
- def parse_args (self, args=None, values=None):
+ def parse_args(self, args=None, values=None):
"""
parse_args(args : [string] = sys.argv[1:],
values : Values = None)
@@ -1133,7 +1278,7 @@ class OptionParser (OptionContainer):
args = largs + rargs
return self.check_values(values, args)
- def check_values (self, values, args):
+ def check_values(self, values, args):
"""
check_values(values : Values, args : [string])
-> (values : Values, args : [string])
@@ -1146,7 +1291,7 @@ class OptionParser (OptionContainer):
"""
return (values, args)
- def _process_args (self, largs, rargs, values):
+ def _process_args(self, largs, rargs, values):
"""_process_args(largs : [string],
rargs : [string],
values : Values)
@@ -1197,7 +1342,7 @@ class OptionParser (OptionContainer):
# *empty* -- still a subset of [arg0, ..., arg(i-1)], but
# not a very interesting subset!
- def _match_long_opt (self, opt):
+ def _match_long_opt(self, opt):
"""_match_long_opt(opt : string) -> string
Determine which long option string 'opt' matches, ie. which one
@@ -1206,7 +1351,7 @@ class OptionParser (OptionContainer):
"""
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)
# Value explicitly attached to arg? Pretend it's the next
@@ -1214,10 +1359,10 @@ class OptionParser (OptionContainer):
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 @@ class OptionParser (OptionContainer):
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,16 +1381,16 @@ class OptionParser (OptionContainer):
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
option.process(opt, value, values, self)
- def _process_short_opts (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 @@ class OptionParser (OptionContainer):
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,7 +1430,19 @@ class OptionParser (OptionContainer):
# -- 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)
Print a usage message incorporating 'msg' to stderr and exit.
@@ -1293,16 +1450,17 @@ class OptionParser (OptionContainer):
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):
+ 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 ""
- def print_usage (self, file=None):
+ def print_usage(self, file=None):
"""print_usage(file : file = stdout)
Print the usage message for the current program (self.usage) to
@@ -1314,13 +1472,13 @@ class OptionParser (OptionContainer):
if self.usage:
print >>file, self.get_usage()
- def get_version (self):
+ 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 ""
- def print_version (self, file=None):
+ def print_version(self, file=None):
"""print_version(file : file = stdout)
Print the version message for this program (self.version) to
@@ -1331,12 +1489,12 @@ class OptionParser (OptionContainer):
if self.version:
print >>file, self.get_version()
- def format_option_help (self, formatter=None):
+ def format_option_help(self, formatter=None):
if formatter is 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))
@@ -1348,7 +1506,7 @@ class OptionParser (OptionContainer):
# Drop the last "\n", or the header if no options or option groups:
return "".join(result[:-1])
- def format_help (self, formatter=None):
+ def format_help(self, formatter=None):
if formatter is None:
formatter = self.formatter
result = []
@@ -1359,7 +1517,7 @@ class OptionParser (OptionContainer):
result.append(self.format_option_help(formatter))
return "".join(result)
- def print_help (self, file=None):
+ def print_help(self, file=None):
"""print_help(file : file = stdout)
Print an extended help message, listing all options and any
@@ -1372,7 +1530,7 @@ class OptionParser (OptionContainer):
# class OptionParser
-def _match_abbrev (s, wordmap):
+def _match_abbrev(s, wordmap):
"""_match_abbrev(s : string, wordmap : {string : Option}) -> string
Return the string key in 'wordmap' for which 's' is an unambiguous
@@ -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)))
diff --git a/Lib/test/test_optparse.py b/Lib/test/test_optparse.py
index 37e229c..b850cec 100644
--- a/Lib/test/test_optparse.py
+++ b/Lib/test/test_optparse.py
@@ -20,14 +20,7 @@ from test import test_support
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 @@ Args were %(args)s.""" % locals ())
return (options, positional_args)
- def assertRaises(self, func, expected_exception, expected_output,
- get_output=None,
- funcargs=[], funckwargs={}):
+ def assertRaises(self,
+ func,
+ args,
+ kwargs,
+ expected_exception,
+ expected_output,
+ get_output=None,
+ 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)
-
- 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())
+ actual_output = get_output(err)
+
+ 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 @@ With args/kwargs:
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 @@ class TestOptionChecks(BaseTest):
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 @@ class TestOptionChecks(BaseTest):
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 @@ class TestOptionParser(BaseTest):
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 @@ class TestStandard(BaseTest):
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 @@ class TestNArgs(BaseTest):
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 @@ class TestNArgsAppend(BaseTest):
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 @@ class TestVersion(BaseTest):
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 @@ class TestOptionGroup(BaseTest):
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 @@ class TestCallback(BaseTest):
{'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 @@ class TestCallBackExtraArgs(BaseTest):
{'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 @@ class TestCallBackMeddleArgs(BaseTest):
{'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 @@ class TestCallBackManyArgs(BaseTest):
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 @@ class TestCallBackCheckAbbrev(BaseTest):
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 @@ class ConflictBase(BaseTest):
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",
- 'callback':self.show_version,
- 'help':"show version"})
+ err = self.assertRaises(
+ func, ("-v", "--version"), {'action' : "callback",
+ 'callback' : self.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 @@ class TestConflict(ConflictBase):
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 @@ options:
# -- 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 @@ class TestHelp(BaseTest):
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 @@ class TestHelp(BaseTest):
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 @@ options
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:
- -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
+ -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
Dangerous Options:
- Caution: use of these options is at your own risk. It is believed that
- some of them bite.
- -g Group option.
+ 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 @@ class TestMatchAbbrev(BaseTest):
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 test_main():
+
+def _testclasses():
mod = sys.modules[__name__]
- test_support.run_unittest(
- *[getattr(mod, name) for name in dir(mod) if name.startswith('Test')]
- )
+ 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():
+ test_support.run_suite(suite())
if __name__ == '__main__':
unittest.main()