[Zope-Checkins] CVS: Zope/lib/python/docutils - __init__.py:1.2.10.1 core.py:1.2.10.1 frontend.py:1.2.10.1 io.py:1.2.10.1 nodes.py:1.2.10.1 statemachine.py:1.2.10.1 utils.py:1.2.10.1

Chris McDonough chrism@zope.com
Mon, 21 Jul 2003 12:39:02 -0400


Update of /cvs-repository/Zope/lib/python/docutils
In directory cvs.zope.org:/tmp/cvs-serv17213/lib/python/docutils

Modified Files:
      Tag: Zope-2_7-branch
	__init__.py core.py frontend.py io.py nodes.py statemachine.py 
	utils.py 
Log Message:
Merge changes from HEAD since the release of Zope 2.7a1 into the Zope-2_7-branch in preparation for release of Zope 2.7b1.


=== Zope/lib/python/docutils/__init__.py 1.2 => 1.2.10.1 ===
--- Zope/lib/python/docutils/__init__.py:1.2	Sat Feb  1 04:26:00 2003
+++ Zope/lib/python/docutils/__init__.py	Mon Jul 21 12:37:52 2003
@@ -23,12 +23,6 @@
 
 - nodes.py: Docutils document tree (doctree) node class library.
 
-- optik.py: Option parsing and command-line help; from Greg Ward's
-  http://optik.sf.net/ project, included for convenience.
-
-- roman.py: Conversion to and from Roman numerals. Courtesy of Mark
-  Pilgrim (http://diveintopython.org/).
-
 - statemachine.py: A finite state machine specialized for
   regular-expression-based text filters.
 
@@ -55,12 +49,12 @@
 
 __docformat__ = 'reStructuredText'
 
-__version__ = '0.2.8'
+__version__ = '0.3.0'
 """``major.minor.micro`` version number.  The micro number is bumped any time
 there's a change in the API incompatible with one of the front ends.  The
 minor number is bumped whenever there is a project release.  The major number
-will be bumped when the project is complete, and perhaps if there is a major
-change in the design."""
+will be bumped when the project is feature-complete, and perhaps if there is a
+major change in the design."""
 
 
 class ApplicationError(StandardError): pass
@@ -85,7 +79,11 @@
     and/or description may be `None`; no group title implies no group, just a
     list of single options.  Runtime settings names are derived implicitly
     from long option names ("--a-setting" becomes ``settings.a_setting``) or
-    explicitly from the "destination" keyword argument."""
+    explicitly from the "dest" keyword argument."""
+
+    settings_defaults = None
+    """A dictionary of defaults for internal or inaccessible (by command-line
+    or config file) settings.  Override in subclasses."""
 
     settings_default_overrides = None
     """A dictionary of auxiliary defaults, to override defaults for settings


=== Zope/lib/python/docutils/core.py 1.2 => 1.2.10.1 ===
--- Zope/lib/python/docutils/core.py:1.2	Sat Feb  1 04:26:00 2003
+++ Zope/lib/python/docutils/core.py	Mon Jul 21 12:37:52 2003
@@ -15,9 +15,9 @@
 __docformat__ = 'reStructuredText'
 
 import sys
-from docutils import Component
-from docutils import frontend, io, readers, parsers, writers
-from docutils.frontend import OptionParser, ConfigParser
+from docutils import Component, __version__
+from docutils import frontend, io, utils, readers, parsers, writers
+from docutils.frontend import OptionParser
 
 
 class Publisher:
@@ -87,14 +87,8 @@
         #@@@ Add self.source & self.destination to components in future?
         option_parser = OptionParser(
             components=(settings_spec, self.parser, self.reader, self.writer),
+            defaults=defaults, read_config_files=1,
             usage=usage, description=description)
-        config = ConfigParser()
-        config.read_standard_files()
-        config_settings = config.get_section('options')
-        frontend.make_paths_absolute(config_settings,
-                                     option_parser.relative_path_settings)
-        defaults.update(config_settings)
-        option_parser.set_defaults(**defaults)
         return option_parser
 
     def get_settings(self, usage=None, description=None,
@@ -148,7 +142,8 @@
             self.settings._destination = destination_path
         self.destination = self.destination_class(
             destination=destination, destination_path=destination_path,
-            encoding=self.settings.output_encoding)
+            encoding=self.settings.output_encoding,
+            error_handler=self.settings.output_encoding_error_handler)
 
     def apply_transforms(self, document):
         document.transformer.populate_from_components(
@@ -157,7 +152,8 @@
         document.transformer.apply_transforms()
 
     def publish(self, argv=None, usage=None, description=None,
-                settings_spec=None, settings_overrides=None):
+                settings_spec=None, settings_overrides=None,
+                enable_exit=None):
         """
         Process command line options and arguments (if `self.settings` not
         already set), run `self.reader` and then `self.writer`.  Return
@@ -169,25 +165,52 @@
         elif settings_overrides:
             self.settings._update(settings_overrides, 'loose')
         self.set_io()
-        document = self.reader.read(self.source, self.parser, self.settings)
-        self.apply_transforms(document)
-        output = self.writer.write(document, self.destination)
+        exit = None
+        document = None
+        try:
+            document = self.reader.read(self.source, self.parser,
+                                        self.settings)
+            self.apply_transforms(document)
+            output = self.writer.write(document, self.destination)
+        except utils.SystemMessage, error:
+            if self.settings.traceback:
+                raise
+            print >>sys.stderr, ('Exiting due to level-%s (%s) system message.'
+                                 % (error.level,
+                                    utils.Reporter.levels[error.level]))
+            exit = 1
+        except Exception, error:
+            if self.settings.traceback:
+                raise
+            print >>sys.stderr, error
+            print >>sys.stderr, ("""\
+Exiting due to error.  Use "--traceback" to diagnose.
+Please report errors to <docutils-users@lists.sf.net>.
+Include "--traceback" output, Docutils version (%s),
+Python version (%s), your OS type & version, and the
+command line used.""" % (__version__, sys.version.split()[0]))
+            exit = 1
         if self.settings.dump_settings:
             from pprint import pformat
             print >>sys.stderr, '\n::: Runtime settings:'
             print >>sys.stderr, pformat(self.settings.__dict__)
-        if self.settings.dump_internals:
+        if self.settings.dump_internals and document:
             from pprint import pformat
             print >>sys.stderr, '\n::: Document internals:'
             print >>sys.stderr, pformat(document.__dict__)
-        if self.settings.dump_transforms:
+        if self.settings.dump_transforms and document:
             from pprint import pformat
             print >>sys.stderr, '\n::: Transforms applied:'
             print >>sys.stderr, pformat(document.transformer.applied)
-        if self.settings.dump_pseudo_xml:
+        if self.settings.dump_pseudo_xml and document:
             print >>sys.stderr, '\n::: Pseudo-XML:'
             print >>sys.stderr, document.pformat().encode(
                 'raw_unicode_escape')
+        if enable_exit and document and (document.reporter.max_level
+                                         >= self.settings.exit_level):
+            sys.exit(document.reporter.max_level + 10)
+        elif exit:
+            sys.exit(1)
         return output
 
 
@@ -199,7 +222,7 @@
                     parser=None, parser_name='restructuredtext',
                     writer=None, writer_name='pseudoxml',
                     settings=None, settings_spec=None,
-                    settings_overrides=None, argv=None,
+                    settings_overrides=None, enable_exit=1, argv=None,
                     usage=default_usage, description=default_description):
     """
     Set up & run a `Publisher`.  For command-line front ends.
@@ -220,6 +243,7 @@
       subclass.  Used only if no `settings` specified.
     - `settings_overrides`: A dictionary containing program-specific overrides
       of component settings.
+    - `enable_exit`: Boolean; enable exit status at end of processing?
     - `argv`: Command-line argument list to use instead of ``sys.argv[1:]``.
     - `usage`: Usage string, output if there's a problem parsing the command
       line.
@@ -228,14 +252,16 @@
     """
     pub = Publisher(reader, parser, writer, settings=settings)
     pub.set_components(reader_name, parser_name, writer_name)
-    pub.publish(argv, usage, description, settings_spec, settings_overrides)
+    pub.publish(argv, usage, description, settings_spec, settings_overrides,
+                enable_exit=enable_exit)
 
 def publish_file(source=None, source_path=None,
                  destination=None, destination_path=None,
                  reader=None, reader_name='standalone',
                  parser=None, parser_name='restructuredtext',
                  writer=None, writer_name='pseudoxml',
-                 settings=None, settings_spec=None, settings_overrides=None):
+                 settings=None, settings_spec=None, settings_overrides=None,
+                 enable_exit=None):
     """
     Set up & run a `Publisher`.  For programmatic use with file-like I/O.
 
@@ -263,6 +289,7 @@
       subclass.  Used only if no `settings` specified.
     - `settings_overrides`: A dictionary containing program-specific overrides
       of component settings.
+    - `enable_exit`: Boolean; enable exit status at end of processing?
     """
     pub = Publisher(reader, parser, writer, settings=settings)
     pub.set_components(reader_name, parser_name, writer_name)
@@ -272,21 +299,27 @@
         settings._update(settings_overrides, 'loose')
     pub.set_source(source, source_path)
     pub.set_destination(destination, destination_path)
-    pub.publish()
+    pub.publish(enable_exit=enable_exit)
 
 def publish_string(source, source_path=None, destination_path=None, 
                    reader=None, reader_name='standalone',
                    parser=None, parser_name='restructuredtext',
                    writer=None, writer_name='pseudoxml',
                    settings=None, settings_spec=None,
-                   settings_overrides=None):
+                   settings_overrides=None, enable_exit=None):
     """
     Set up & run a `Publisher`, and return the string output.
     For programmatic use with string I/O.
 
     For encoded string output, be sure to set the "output_encoding" setting to
     the desired encoding.  Set it to "unicode" for unencoded Unicode string
-    output.
+    output.  Here's how::
+
+        publish_string(..., settings_overrides={'output_encoding': 'unicode'})
+
+    Similarly for Unicode string input (`source`)::
+
+        publish_string(..., settings_overrides={'input_encoding': 'unicode'})
 
     Parameters:
 
@@ -312,6 +345,7 @@
       subclass.  Used only if no `settings` specified.
     - `settings_overrides`: A dictionary containing program-specific overrides
       of component settings.
+    - `enable_exit`: Boolean; enable exit status at end of processing?
     """
     pub = Publisher(reader, parser, writer, settings=settings,
                     source_class=io.StringInput,
@@ -323,4 +357,4 @@
         settings._update(settings_overrides, 'loose')
     pub.set_source(source, source_path)
     pub.set_destination(destination_path=destination_path)
-    return pub.publish()
+    return pub.publish(enable_exit=enable_exit)


=== Zope/lib/python/docutils/frontend.py 1.2 => 1.2.10.1 ===
--- Zope/lib/python/docutils/frontend.py:1.2	Sat Feb  1 04:26:00 2003
+++ Zope/lib/python/docutils/frontend.py	Mon Jul 21 12:37:52 2003
@@ -19,10 +19,13 @@
 
 import os
 import os.path
+import sys
+import types
 import ConfigParser as CP
+import codecs
 import docutils
-from docutils import optik
-from docutils.optik import Values
+import optik as optparse
+from optik import Values, SUPPRESS_HELP
 
 
 def store_multiple(option, opt, value, parser, *args, **kwargs):
@@ -42,12 +45,85 @@
     Read a configuration file during option processing.  (Option callback.)
     """
     config_parser = ConfigParser()
-    config_parser.read(value)
+    config_parser.read(value, parser)
     settings = config_parser.get_section('options')
     make_paths_absolute(settings, parser.relative_path_settings,
                         os.path.dirname(value))
     parser.values.__dict__.update(settings)
 
+def set_encoding(option, opt, value, parser):
+    """
+    Validate & set the encoding specified.  (Option callback.)
+    """
+    try:
+        value = validate_encoding(option.dest, value)
+    except LookupError, error:
+        raise (optparse.OptionValueError('option "%s": %s' % (opt, error)),
+               None, sys.exc_info()[2])
+    setattr(parser.values, option.dest, value)
+
+def validate_encoding(name, value):
+    try:
+        codecs.lookup(value)
+    except LookupError:
+        raise (LookupError('unknown encoding: "%s"' % value),
+               None, sys.exc_info()[2])
+    return value
+
+def set_encoding_error_handler(option, opt, value, parser):
+    """
+    Validate & set the encoding error handler specified.  (Option callback.)
+    """
+    try:
+        value = validate_encoding_error_handler(option.dest, value)
+    except LookupError, error:
+        raise (optparse.OptionValueError('option "%s": %s' % (opt, error)),
+               None, sys.exc_info()[2])
+    setattr(parser.values, option.dest, value)
+
+def validate_encoding_error_handler(name, value):
+    try:
+        codecs.lookup_error(value)
+    except AttributeError:              # prior to Python 2.3
+        if value not in ('strict', 'ignore', 'replace'):
+            raise (LookupError(
+                'unknown encoding error handler: "%s" (choices: '
+                '"strict", "ignore", or "replace")' % value),
+                   None, sys.exc_info()[2])
+    except LookupError:
+        raise (LookupError(
+            'unknown encoding error handler: "%s" (choices: '
+            '"strict", "ignore", "replace", "backslashreplace", '
+            '"xmlcharrefreplace", and possibly others; see documentation for '
+            'the Python ``codecs`` module)' % value),
+               None, sys.exc_info()[2])
+    return value
+
+def set_encoding_and_error_handler(option, opt, value, parser):
+    """
+    Validate & set the encoding and error handler specified.  (Option callback.)
+    """
+    try:
+        value = validate_encoding_and_error_handler(option.dest, value)
+    except LookupError, error:
+        raise (optparse.OptionValueError('option "%s": %s' % (opt, error)),
+               None, sys.exc_info()[2])
+    if ':' in value:
+        encoding, handler = value.split(':')
+        setattr(parser.values, option.dest + '_error_handler', handler)
+    else:
+        encoding = value
+    setattr(parser.values, option.dest, encoding)
+
+def validate_encoding_and_error_handler(name, value):
+    if ':' in value:
+        encoding, handler = value.split(':')
+        validate_encoding_error_handler(name + '_error_handler', handler)
+    else:
+        encoding = value
+    validate_encoding(name, encoding)
+    return value
+
 def make_paths_absolute(pathdict, keys, base_path=None):
     """
     Interpret filesystem path settings relative to the `base_path` given.
@@ -63,7 +139,7 @@
                 os.path.abspath(os.path.join(base_path, pathdict[key])))
 
 
-class OptionParser(optik.OptionParser, docutils.SettingsSpec):
+class OptionParser(optparse.OptionParser, docutils.SettingsSpec):
 
     """
     Parser for command-line and library use.  The `settings_spec`
@@ -81,6 +157,11 @@
     thresholds = {'info': 1, 'warning': 2, 'error': 3, 'severe': 4, 'none': 5}
     """Lookup table for --report and --halt threshold values."""
 
+    if hasattr(codecs, 'backslashreplace_errors'):
+        default_error_encoding_error_handler = 'backslashreplace'
+    else:
+        default_error_encoding_error_handler = 'replace'
+
     settings_spec = (
         'General Docutils Options',
         None,
@@ -147,17 +228,54 @@
          ('Same as "--halt=info": halt processing at the slightest problem.',
           ['--strict'], {'action': 'store_const', 'const': 'info',
                          'dest': 'halt_level'}),
-         ('Report debug-level system messages.',
+         ('Enable a non-zero exit status for normal exit if non-halting '
+          'system messages (at or above <level>) were generated.  Levels as '
+          'in --report.  Default is 5 (disabled).  Exit status is the maximum '
+          'system message level plus 10 (11 for INFO, etc.).',
+          ['--exit'], {'choices': threshold_choices, 'dest': 'exit_level',
+                       'default': 5, 'metavar': '<level>'}),
+         ('Report debug-level system messages and generate diagnostic output.',
           ['--debug'], {'action': 'store_true'}),
-         ('Do not report debug-level system messages.',
+         ('Do not report debug-level system messages or generate diagnostic '
+          'output.',
           ['--no-debug'], {'action': 'store_false', 'dest': 'debug'}),
          ('Send the output of system messages (warnings) to <file>.',
           ['--warnings'], {'dest': 'warning_stream', 'metavar': '<file>'}),
+         ('Enable Python tracebacks when an error occurs.',
+          ['--traceback'], {'action': 'store_true', 'default': None}),
+         ('Disable Python tracebacks when errors occur; report just the error '
+          'instead.  This is the default.',
+          ['--no-traceback'], {'dest': 'traceback', 'action': 'store_false'}),
          ('Specify the encoding of input text.  Default is locale-dependent.',
-          ['--input-encoding', '-i'], {'metavar': '<name>'}),
-         ('Specify the encoding for output.  Default is UTF-8.',
+          ['--input-encoding', '-i'],
+          {'action': 'callback', 'callback': set_encoding,
+           'metavar': '<name>', 'type': 'string', 'dest': 'input_encoding'}),
+         ('Specify the text encoding for output.  Default is UTF-8.  '
+          'Optionally also specify the encoding error handler for unencodable '
+          'characters (see "--error-encoding"); default is "strict".',
           ['--output-encoding', '-o'],
-          {'metavar': '<name>', 'default': 'utf-8'}),
+          {'action': 'callback', 'callback': set_encoding_and_error_handler,
+           'metavar': '<name[:handler]>', 'type': 'string',
+           'dest': 'output_encoding', 'default': 'utf-8'}),
+         (SUPPRESS_HELP,                # usually handled by --output-encoding
+          ['--output_encoding_error_handler'],
+          {'action': 'callback', 'callback': set_encoding_error_handler,
+           'type': 'string', 'dest': 'output_encoding_error_handler',
+           'default': 'strict'}),
+         ('Specify the text encoding for error output.  Default is ASCII.  '
+          'Optionally also specify the encoding error handler for unencodable '
+          'characters, after a colon (":").  Acceptable values are the same '
+          'as for the "error" parameter of Python\'s ``encode`` string '
+          'method.  Default is "%s".' % default_error_encoding_error_handler,
+          ['--error-encoding', '-e'],
+          {'action': 'callback', 'callback': set_encoding_and_error_handler,
+           'metavar': '<name[:handler]>', 'type': 'string',
+           'dest': 'error_encoding', 'default': 'ascii'}),
+         (SUPPRESS_HELP,                # usually handled by --error-encoding
+          ['--error_encoding_error_handler'],
+          {'action': 'callback', 'callback': set_encoding_error_handler,
+           'type': 'string', 'dest': 'error_encoding_error_handler',
+           'default': default_error_encoding_error_handler}),
          ('Specify the language of input text (ISO 639 2-letter identifier).'
           '  Default is "en" (English).',
           ['--language', '-l'], {'dest': 'language_code', 'default': 'en',
@@ -170,53 +288,60 @@
          ('Show this help message and exit.',
           ['--help', '-h'], {'action': 'help'}),
          # Hidden options, for development use only:
-         (optik.SUPPRESS_HELP,
-          ['--dump-settings'],
-          {'action': 'store_true'}),
-         (optik.SUPPRESS_HELP,
-          ['--dump-internals'],
-          {'action': 'store_true'}),
-         (optik.SUPPRESS_HELP,
-          ['--dump-transforms'],
-          {'action': 'store_true'}),
-         (optik.SUPPRESS_HELP,
-          ['--dump-pseudo-xml'],
-          {'action': 'store_true'}),
-         (optik.SUPPRESS_HELP,
-          ['--expose-internal-attribute'],
+         (SUPPRESS_HELP, ['--dump-settings'], {'action': 'store_true'}),
+         (SUPPRESS_HELP, ['--dump-internals'], {'action': 'store_true'}),
+         (SUPPRESS_HELP, ['--dump-transforms'], {'action': 'store_true'}),
+         (SUPPRESS_HELP, ['--dump-pseudo-xml'], {'action': 'store_true'}),
+         (SUPPRESS_HELP, ['--expose-internal-attribute'],
           {'action': 'append', 'dest': 'expose_internals'}),))
     """Runtime settings and command-line options common to all Docutils front
     ends.  Setting specs specific to individual Docutils components are also
     used (see `populate_from_components()`)."""
 
+    settings_defaults = {'_disable_config': None}
+    """Defaults for settings that don't have command-line option equivalents."""
+
     relative_path_settings = ('warning_stream',)
 
     version_template = '%%prog (Docutils %s)' % docutils.__version__
     """Default version message."""
 
-    def __init__(self, components=(), *args, **kwargs):
+    def __init__(self, components=(), defaults=None, read_config_files=None,
+                 *args, **kwargs):
         """
         `components` is a list of Docutils components each containing a
         ``.settings_spec`` attribute.  `defaults` is a mapping of setting
         default overrides.
         """
-        optik.OptionParser.__init__(
-            self, help=None,
-            format=optik.Titled(),
-            # Needed when Optik is updated (replaces above 2 lines):
-            #self, add_help=None,
-            #formatter=optik.TitledHelpFormatter(width=78),
+        optparse.OptionParser.__init__(
+            self, add_help_option=None,
+            formatter=optparse.TitledHelpFormatter(width=78),
             *args, **kwargs)
         if not self.version:
             self.version = self.version_template
-        # Internal settings with no defaults from settings specifications;
-        # initialize manually:
-        self.set_defaults(_source=None, _destination=None)
         # Make an instance copy (it will be modified):
         self.relative_path_settings = list(self.relative_path_settings)
-        self.populate_from_components(tuple(components) + (self,))
+        self.populate_from_components((self,) + tuple(components))
+        defaults = defaults or {}
+        if read_config_files and not self.defaults['_disable_config']:
+            # @@@ Extract this code into a method, which can be called from
+            # the read_config_file callback also.
+            config = ConfigParser()
+            config.read_standard_files(self)
+            config_settings = config.get_section('options')
+            make_paths_absolute(config_settings, self.relative_path_settings)
+            defaults.update(config_settings)
+        # Internal settings with no defaults from settings specifications;
+        # initialize manually:
+        self.set_defaults(_source=None, _destination=None, **defaults)
 
     def populate_from_components(self, components):
+        """
+        For each component, first populate from the `SettingsSpec.settings_spec`
+        structure, then from the `SettingsSpec.settings_defaults` dictionary.
+        After all components have been processed, check for and populate from
+        each component's `SettingsSpec.settings_default_overrides` dictionary.
+        """
         for component in components:
             if component is None:
                 continue
@@ -227,13 +352,15 @@
             while i < len(settings_spec):
                 title, description, option_spec = settings_spec[i:i+3]
                 if title:
-                    group = optik.OptionGroup(self, title, description)
+                    group = optparse.OptionGroup(self, title, description)
                     self.add_option_group(group)
                 else:
                     group = self        # single options
                 for (help_text, option_strings, kwargs) in option_spec:
                     group.add_option(help=help_text, *option_strings,
                                      **kwargs)
+                if component.settings_defaults:
+                    self.defaults.update(component.settings_defaults)
                 i += 3
         for component in components:
             if component and component.settings_default_overrides:
@@ -244,6 +371,8 @@
             values.report_level = self.check_threshold(values.report_level)
         if hasattr(values, 'halt_level'):
             values.halt_level = self.check_threshold(values.halt_level)
+        if hasattr(values, 'exit_level'):
+            values.exit_level = self.check_threshold(values.exit_level)
         values._source, values._destination = self.check_args(args)
         make_paths_absolute(values.__dict__, self.relative_path_settings,
                             os.getcwd())
@@ -262,8 +391,12 @@
         source = destination = None
         if args:
             source = args.pop(0)
+            if source == '-':           # means stdin
+                source = None
         if args:
             destination = args.pop(0)
+            if destination == '-':      # means stdout
+                destination = None
         if args:
             self.error('Maximum 2 arguments allowed.')
         if source and source == destination:
@@ -281,8 +414,44 @@
     """Docutils configuration files, using ConfigParser syntax (section
     'options').  Later files override earlier ones."""
 
-    def read_standard_files(self):
-        self.read(self.standard_config_files)
+    validation = {
+        'options':
+        {'input_encoding': validate_encoding,
+         'output_encoding': validate_encoding,
+         'output_encoding_error_handler': validate_encoding_error_handler,
+         'error_encoding': validate_encoding,
+         'error_encoding_error_handler': validate_encoding_error_handler}}
+    """{section: {option: validation function}} mapping, used by
+    `validate_options`.  Validation functions take two parameters: name and
+    value.  They return a (possibly modified) value, or raise an exception."""
+
+    def read_standard_files(self, option_parser):
+        self.read(self.standard_config_files, option_parser)
+
+    def read(self, filenames, option_parser):
+        if type(filenames) in types.StringTypes:
+            filenames = [filenames]
+        for filename in filenames:
+            CP.ConfigParser.read(self, filename)
+            self.validate_options(filename, option_parser)
+
+    def validate_options(self, filename, option_parser):
+        for section in self.validation.keys():
+            if not self.has_section(section):
+                continue
+            for option in self.validation[section].keys():
+                if self.has_option(section, option):
+                    value = self.get(section, option)
+                    validator = self.validation[section][option]
+                    try:
+                        new_value = validator(option, value)
+                    except Exception, error:
+                        raise (ValueError(
+                            'Error in config file "%s", section "[%s]":\n'
+                            '    %s: %s\n        %s = %s'
+                            % (filename, section, error.__class__.__name__,
+                               error, option, value)), None, sys.exc_info()[2])
+                    self.set(section, option, new_value)
 
     def optionxform(self, optionstr):
         """


=== Zope/lib/python/docutils/io.py 1.2 => 1.2.10.1 ===
--- Zope/lib/python/docutils/io.py:1.2	Sat Feb  1 04:26:00 2003
+++ Zope/lib/python/docutils/io.py	Mon Jul 21 12:37:52 2003
@@ -13,6 +13,7 @@
 
 import sys
 import locale
+from types import UnicodeType
 from docutils import TransformSpec
 
 
@@ -26,20 +27,9 @@
 
     default_source_path = None
 
-    def __init__(self, settings=None, source=None, source_path=None,
-                 encoding=None):
+    def __init__(self, source=None, source_path=None, encoding=None):
         self.encoding = encoding
-        """The character encoding for the input source."""
-
-        if settings:
-            if not encoding:
-                self.encoding = settings.input_encoding
-            import warnings, traceback
-            warnings.warn(
-                'Setting input encoding via a "settings" struct is '
-                'deprecated; send encoding directly instead.\n%s'
-                % ''.join(traceback.format_list(traceback.extract_stack()
-                                                [-3:-1])))
+        """Text encoding for the input source."""
 
         self.source = source
         """The source of input data."""
@@ -67,7 +57,8 @@
 
             locale.setlocale(locale.LC_ALL, '')
         """
-        if self.encoding and self.encoding.lower() == 'unicode':
+        if (self.encoding and self.encoding.lower() == 'unicode'
+            or isinstance(data, UnicodeType)):
             return unicode(data)
         encodings = [self.encoding, 'utf-8']
         try:
@@ -87,8 +78,7 @@
             if not enc:
                 continue
             try:
-                decoded = unicode(data, enc)
-                return decoded
+                return unicode(data, enc)
             except (UnicodeError, LookupError):
                 pass
         raise UnicodeError(
@@ -106,20 +96,13 @@
 
     default_destination_path = None
 
-    def __init__(self, settings=None, destination=None, destination_path=None,
-                 encoding=None):
+    def __init__(self, destination=None, destination_path=None,
+                 encoding=None, error_handler='strict'):
         self.encoding = encoding
-        """The character encoding for the output destination."""
+        """Text encoding for the output destination."""
 
-        if settings:
-            if not encoding:
-                self.encoding = settings.output_encoding
-            import warnings, traceback
-            warnings.warn(
-                'Setting output encoding via a "settings" struct is '
-                'deprecated; send encoding directly instead.\n%s'
-                % ''.join(traceback.format_list(traceback.extract_stack()
-                                                [-3:-1])))
+        self.error_handler = error_handler or 'strict'
+        """Text encoding error handler."""
 
         self.destination = destination
         """The destination for output data."""
@@ -141,7 +124,7 @@
         if self.encoding and self.encoding.lower() == 'unicode':
             return data
         else:
-            return data.encode(self.encoding or '')
+            return data.encode(self.encoding, self.error_handler)
 
 
 class FileInput(Input):
@@ -150,8 +133,8 @@
     Input for single, simple file-like objects.
     """
 
-    def __init__(self, settings=None, source=None, source_path=None,
-                 encoding=None, autoclose=1):
+    def __init__(self, source=None, source_path=None,
+                 encoding=None, autoclose=1, handle_io_errors=1):
         """
         :Parameters:
             - `source`: either a file-like object (which is read directly), or
@@ -160,11 +143,22 @@
             - `autoclose`: close automatically after read (boolean); always
               false if `sys.stdin` is the source.
         """
-        Input.__init__(self, settings, source, source_path, encoding)
+        Input.__init__(self, source, source_path, encoding)
         self.autoclose = autoclose
+        self.handle_io_errors = handle_io_errors
         if source is None:
             if source_path:
-                self.source = open(source_path)
+                try:
+                    self.source = open(source_path)
+                except IOError, error:
+                    if not handle_io_errors:
+                        raise
+                    print >>sys.stderr, '%s: %s' % (error.__class__.__name__,
+                                                    error)
+                    print >>sys.stderr, (
+                        'Unable to open source file for reading (%s).  Exiting.'
+                        % source_path)
+                    sys.exit(1)
             else:
                 self.source = sys.stdin
                 self.autoclose = None
@@ -191,8 +185,9 @@
     Output for single, simple file-like objects.
     """
 
-    def __init__(self, settings=None, destination=None, destination_path=None,
-                 encoding=None, autoclose=1):
+    def __init__(self, destination=None, destination_path=None,
+                 encoding=None, error_handler='strict', autoclose=1,
+                 handle_io_errors=1):
         """
         :Parameters:
             - `destination`: either a file-like object (which is written
@@ -203,10 +198,11 @@
             - `autoclose`: close automatically after write (boolean); always
               false if `sys.stdout` is the destination.
         """
-        Output.__init__(self, settings, destination, destination_path,
-                        encoding)
+        Output.__init__(self, destination, destination_path,
+                        encoding, error_handler)
         self.opened = 1
         self.autoclose = autoclose
+        self.handle_io_errors = handle_io_errors
         if destination is None:
             if destination_path:
                 self.opened = None
@@ -220,7 +216,16 @@
                 pass
 
     def open(self):
-        self.destination = open(self.destination_path, 'w')
+        try:
+            self.destination = open(self.destination_path, 'w')
+        except IOError, error:
+            if not self.handle_io_errors:
+                raise
+            print >>sys.stderr, '%s: %s' % (error.__class__.__name__,
+                                            error)
+            print >>sys.stderr, ('Unable to open destination file for writing '
+                                 '(%s).  Exiting.' % source_path)
+            sys.exit(1)
         self.opened = 1
 
     def write(self, data):


=== Zope/lib/python/docutils/nodes.py 1.2 => 1.2.10.1 ===
--- Zope/lib/python/docutils/nodes.py:1.2	Sat Feb  1 04:26:00 2003
+++ Zope/lib/python/docutils/nodes.py	Mon Jul 21 12:37:52 2003
@@ -53,7 +53,14 @@
     """The line number (1-based) of the beginning of this Node in `source`."""
 
     def __nonzero__(self):
-        """Node instances are always true."""
+        """
+        Node instances are always true, even if they're empty.  A node is more
+        than a simple container.  Its boolean "truth" does not depend on
+        having one or more subnodes in the doctree.
+
+        Use `len()` to check node length.  Use `None` to represent a boolean
+        false value.
+        """
         return 1
 
     def asdom(self, dom=xml.dom.minidom):
@@ -175,6 +182,9 @@
             data = repr(self.data[:64] + ' ...')
         return '<%s: %s>' % (self.tagname, data)
 
+    def __len__(self):
+        return len(self.data)
+
     def shortrepr(self):
         data = repr(self.data)
         if len(data) > 20:
@@ -261,9 +271,9 @@
     def _dom_node(self, domroot):
         element = domroot.createElement(self.tagname)
         for attribute, value in self.attributes.items():
-            if type(value) is ListType:
-                value = ' '.join(value)
-            element.setAttribute(attribute, str(value))
+            if isinstance(value, ListType):
+                value = ' '.join(['%s' % v for v in value])
+            element.setAttribute(attribute, '%s' % value)
         for child in self.children:
             element.appendChild(child._dom_node(domroot))
         return element
@@ -289,10 +299,13 @@
             return '<%s...>' % self.tagname
 
     def __str__(self):
+        return unicode(self).encode('raw_unicode_escape')
+
+    def __unicode__(self):
         if self.children:
-            return '%s%s%s' % (self.starttag(),
-                                ''.join([str(c) for c in self.children]),
-                                self.endtag())
+            return u'%s%s%s' % (self.starttag(),
+                                 ''.join([str(c) for c in self.children]),
+                                 self.endtag())
         else:
             return self.emptytag()
 
@@ -302,19 +315,19 @@
             if value is None:           # boolean attribute
                 parts.append(name)
             elif isinstance(value, ListType):
-                values = [str(v) for v in value]
+                values = ['%s' % v for v in value]
                 parts.append('%s="%s"' % (name, ' '.join(values)))
             else:
-                parts.append('%s="%s"' % (name, str(value)))
+                parts.append('%s="%s"' % (name, value))
         return '<%s>' % ' '.join(parts)
 
     def endtag(self):
         return '</%s>' % self.tagname
 
     def emptytag(self):
-        return '<%s/>' % ' '.join([self.tagname] +
-                                  ['%s="%s"' % (n, v)
-                                   for n, v in self.attlist()])
+        return u'<%s/>' % ' '.join([self.tagname] +
+                                    ['%s="%s"' % (n, v)
+                                     for n, v in self.attlist()])
 
     def __len__(self):
         return len(self.children)
@@ -619,6 +632,10 @@
         self.substitution_defs = {}
         """Mapping of substitution names to substitution_definition nodes."""
 
+        self.substitution_names = {}
+        """Mapping of case-normalized substitution names to case-sensitive
+        names."""
+
         self.refnames = {}
         """Mapping of names to lists of referencing nodes."""
 
@@ -864,8 +881,8 @@
         self.citation_refs.setdefault(ref['refname'], []).append(ref)
         self.note_refname(ref)
 
-    def note_substitution_def(self, subdef, msgnode=None):
-        name = subdef['name']
+    def note_substitution_def(self, subdef, def_name, msgnode=None):
+        name = subdef['name'] = whitespace_normalize_name(def_name)
         if self.substitution_defs.has_key(name):
             msg = self.reporter.error(
                   'Duplicate substitution definition name: "%s".' % name,
@@ -874,12 +891,14 @@
                 msgnode += msg
             oldnode = self.substitution_defs[name]
             dupname(oldnode)
-        # keep only the last definition
+        # keep only the last definition:
         self.substitution_defs[name] = subdef
+        # case-insensitive mapping:
+        self.substitution_names[fully_normalize_name(name)] = name
 
-    def note_substitution_ref(self, subref):
-        self.substitution_refs.setdefault(
-              subref['refname'], []).append(subref)
+    def note_substitution_ref(self, subref, refname):
+        name = subref['refname'] = whitespace_normalize_name(refname)
+        self.substitution_refs.setdefault(name, []).append(subref)
 
     def note_pending(self, pending, priority=None):
         self.transformer.add_pending(pending, priority)
@@ -908,6 +927,7 @@
 
 class title(Titular, PreBibliographic, TextElement): pass
 class subtitle(Titular, PreBibliographic, TextElement): pass
+class rubric(Titular, TextElement): pass
 
 
 # ========================
@@ -947,13 +967,30 @@
 
     """
     Topics are terminal, "leaf" mini-sections, like block quotes with titles,
-    or textual figures. A topic is just like a section, except that it has no
+    or textual figures.  A topic is just like a section, except that it has no
     subsections, and it doesn't have to conform to section placement rules.
 
     Topics are allowed wherever body elements (list, table, etc.) are allowed,
-    but only at the top level of a section or document. Topics cannot nest
-    inside topics or body elements; you can't have a topic inside a table,
-    list, block quote, etc.
+    but only at the top level of a section or document.  Topics cannot nest
+    inside topics, sidebars, or body elements; you can't have a topic inside a
+    table, list, block quote, etc.
+    """
+
+
+class sidebar(Structural, Element):
+
+    """
+    Sidebars are like miniature, parallel documents that occur inside other
+    documents, providing related or reference material.  A sidebar is
+    typically offset by a border and "floats" to the side of the page; the
+    document's main text may flow around it.  Sidebars can also be likened to
+    super-footnotes; their content is outside of the flow of the document's
+    main text.
+
+    Sidebars are allowed wherever body elements (list, table, etc.) are
+    allowed, but only at the top level of a section or document.  Sidebars
+    cannot nest inside sidebars, topics, or body elements; you can't have a
+    sidebar inside a table, list, block quote, etc.
     """
 
 
@@ -1009,6 +1046,7 @@
 class doctest_block(General, FixedTextElement): pass
 class line_block(General, FixedTextElement): pass
 class block_quote(General, Element): pass
+class attribution(Part, TextElement): pass
 class attention(Admonition, Element): pass
 class caution(Admonition, Element): pass
 class danger(Admonition, Element): pass
@@ -1018,6 +1056,7 @@
 class tip(Admonition, Element): pass
 class hint(Admonition, Element): pass
 class warning(Admonition, Element): pass
+class admonition(Admonition, Element): pass
 class comment(Special, Invisible, PreBibliographic, FixedTextElement): pass
 class substitution_definition(Special, Invisible, TextElement): pass
 class target(Special, Invisible, Inline, TextElement, Targetable): pass
@@ -1050,8 +1089,8 @@
 
     def astext(self):
         line = self.get('line', '')
-        return '%s:%s: (%s/%s) %s' % (self['source'], line, self['type'],
-                                      self['level'], Element.astext(self))
+        return u'%s:%s: (%s/%s) %s' % (self['source'], line, self['type'],
+                                       self['level'], Element.astext(self))
 
 
 class pending(Special, Invisible, PreBibliographic, Element):
@@ -1106,7 +1145,7 @@
                 internals.append('%7s%s:' % ('', key))
                 internals.extend(['%9s%s' % ('', line)
                                   for line in value.pformat().splitlines()])
-            elif value and type(value) == ListType \
+            elif value and isinstance(value, ListType) \
                   and isinstance(value[0], Node):
                 internals.append('%7s%s:' % ('', key))
                 for v in value:
@@ -1146,6 +1185,8 @@
 class title_reference(Inline, TextElement): pass
 class abbreviation(Inline, TextElement): pass
 class acronym(Inline, TextElement): pass
+class superscript(Inline, TextElement): pass
+class subscript(Inline, TextElement): pass
 
 
 class image(General, Inline, TextElement):
@@ -1154,6 +1195,7 @@
         return self.get('alt', '')
 
 
+class inline(Inline, TextElement): pass
 class problematic(Inline, TextElement): pass
 class generated(Inline, TextElement): pass
 
@@ -1164,7 +1206,8 @@
 
 node_class_names = """
     Text
-    abbreviation acronym address attention author authors
+    abbreviation acronym address admonition attention attribution author
+        authors
     block_quote bullet_list
     caption caution citation citation_reference classifier colspec comment
         contact copyright
@@ -1175,15 +1218,15 @@
         footnote footnote_reference
     generated
     header hint
-    image important
+    image important inline
     label legend line_block list_item literal literal_block
     note
     option option_argument option_group option_list option_list_item
         option_string organization
     paragraph pending problematic
-    raw reference revision row
-    section status strong substitution_definition substitution_reference
-        subtitle system_message
+    raw reference revision row rubric
+    section sidebar status strong subscript substitution_definition
+        substitution_reference subtitle superscript system_message
     table target tbody term tgroup thead tip title title_reference topic
         transition
     version
@@ -1248,11 +1291,14 @@
     subclasses), subclass `NodeVisitor` instead.
     """
 
-    # Save typing with dynamic definitions.
-    for name in node_class_names:
-        exec """def visit_%s(self, node): pass\n""" % name
-        exec """def depart_%s(self, node): pass\n""" % name
-    del name
+def _nop(self, node):
+    pass
+
+# Save typing with dynamic assignments:
+for _name in node_class_names:
+    setattr(SparseNodeVisitor, "visit_" + _name, _nop)
+    setattr(SparseNodeVisitor, "depart_" + _name, _nop)
+del _name, _nop
 
 
 class GenericNodeVisitor(NodeVisitor):
@@ -1281,13 +1327,17 @@
         """Override for generic, uniform traversals."""
         raise NotImplementedError
 
-    # Save typing with dynamic definitions.
-    for name in node_class_names:
-        exec """def visit_%s(self, node):
-                    self.default_visit(node)\n""" % name
-        exec """def depart_%s(self, node):
-                    self.default_departure(node)\n""" % name
-    del name
+def _call_default_visit(self, node):
+    self.default_visit(node)
+
+def _call_default_departure(self, node):
+    self.default_departure(node)
+
+# Save typing with dynamic assignments:
+for _name in node_class_names:
+    setattr(GenericNodeVisitor, "visit_" + _name, _call_default_visit)
+    setattr(GenericNodeVisitor, "depart_" + _name, _call_default_departure)
+del _name, _call_default_visit, _call_default_departure
 
 
 class TreeCopyVisitor(GenericNodeVisitor):
@@ -1385,9 +1435,9 @@
     Convert `string` into an identifier and return it.
 
     Docutils identifiers will conform to the regular expression
-    ``[a-z][-a-z0-9]*``. For CSS compatibility, identifiers (the "class" and
-    "id" attributes) should have no underscores, colons, or periods. Hyphens
-    may be used.
+    ``[a-z](-?[a-z0-9]+)*``.  For CSS compatibility, identifiers (the "class"
+    and "id" attributes) should have no underscores, colons, or periods.
+    Hyphens may be used.
 
     - The `HTML 4.01 spec`_ defines identifiers based on SGML tokens:
 
@@ -1410,7 +1460,7 @@
     these characters. They should be replaced with hyphens ("-"). Combined
     with HTML's requirements (the first character must be a letter; no
     "unicode", "latin1", or "escape" characters), this results in the
-    ``[a-z][-a-z0-9]*`` pattern.
+    ``[a-z](-?[a-z0-9]+)*`` pattern.
 
     .. _HTML 4.01 spec: http://www.w3.org/TR/html401
     .. _CSS1 spec: http://www.w3.org/TR/REC-CSS1
@@ -1425,3 +1475,11 @@
 def dupname(node):
     node['dupname'] = node['name']
     del node['name']
+
+def fully_normalize_name(name):
+    """Return a case- and whitespace-normalized name."""
+    return ' '.join(name.lower().split())
+
+def whitespace_normalize_name(name):
+    """Return a whitespace-normalized name."""
+    return ' '.join(name.split())


=== Zope/lib/python/docutils/statemachine.py 1.2 => 1.2.10.1 ===
--- Zope/lib/python/docutils/statemachine.py:1.2	Sat Feb  1 04:26:00 2003
+++ Zope/lib/python/docutils/statemachine.py	Mon Jul 21 12:37:52 2003
@@ -266,7 +266,8 @@
                     transitions = None
                 state = self.get_state(next_state)
         except:
-            self.error()
+            if self.debug:
+                self.error()
             raise
         self.observers = []
         return results
@@ -1294,11 +1295,11 @@
 
     """A `ViewList` with string-specific methods."""
 
-    def strip_indent(self, length, start=0, end=sys.maxint):
+    def trim_left(self, length, start=0, end=sys.maxint):
         """
-        Strip `length` characters off the beginning of each item, in-place,
+        Trim `length` characters off the beginning of each item, in-place,
         from index `start` to `end`.  No whitespace-checking is done on the
-        stripped text.  Does not affect slice parent.
+        trimmed text.  Does not affect slice parent.
         """
         self.data[start:end] = [line[length:]
                                 for line in self.data[start:end]]
@@ -1381,8 +1382,19 @@
         if first_indent is not None and block:
             block.data[0] = block.data[0][first_indent:]
         if indent and strip_indent:
-            block.strip_indent(indent, start=(first_indent is not None))
+            block.trim_left(indent, start=(first_indent is not None))
         return block, indent or 0, blank_finish
+
+    def get_2D_block(self, top, left, bottom, right, strip_indent=1):
+        block = self[top:bottom]
+        indent = right
+        for i in range(len(block.data)):
+            block.data[i] = line = block.data[i][left:right].rstrip()
+            if line:
+                indent = min(indent, len(line) - len(line.lstrip()))
+        if strip_indent and 0 < indent < right:
+            block.data = [line[indent:] for line in block.data]
+        return block
 
 
 class StateMachineError(Exception): pass


=== Zope/lib/python/docutils/utils.py 1.2 => 1.2.10.1 ===
--- Zope/lib/python/docutils/utils.py:1.2	Sat Feb  1 04:26:00 2003
+++ Zope/lib/python/docutils/utils.py	Mon Jul 21 12:37:52 2003
@@ -20,8 +20,9 @@
 
 class SystemMessage(ApplicationError):
 
-    def __init__(self, system_message):
+    def __init__(self, system_message, level):
         Exception.__init__(self, system_message.astext())
+        self.level = level
 
 
 class Reporter:
@@ -75,7 +76,7 @@
     """List of names for system message levels, indexed by level."""
 
     def __init__(self, source, report_level, halt_level, stream=None,
-                 debug=0):
+                 debug=0, encoding='ascii', error_handler='replace'):
         """
         Initialize the `ConditionSet` forthe `Reporter`'s default category.
 
@@ -90,6 +91,8 @@
             - `stream`: Where warning output is sent.  Can be file-like (has a
               ``.write`` method), a string (file name, opened for writing), or
               `None` (implies `sys.stderr`; default).
+            - `encoding`: The encoding for stderr output.
+            - `error_handler`: The error handler for stderr output encoding.
         """
         self.source = source
         """The path to or description of the source data."""
@@ -99,6 +102,12 @@
         elif type(stream) in (StringType, UnicodeType):
             raise NotImplementedError('This should open a file for writing.')
 
+        self.encoding = encoding
+        """The character encoding for the stderr output."""
+
+        self.error_handler = error_handler
+        """The character encoding error handler."""
+
         self.categories = {'': ConditionSet(debug, report_level, halt_level,
                                             stream)}
         """Mapping of category names to conditions. Default category is ''."""
@@ -107,6 +116,9 @@
         """List of bound methods or functions to call with each system_message
         created."""
 
+        self.max_level = -1
+        """The highest level system message generated so far."""
+
     def set_conditions(self, category, report_level, halt_level,
                        stream=None, debug=0):
         if stream is None:
@@ -164,14 +176,16 @@
                                    *children, **attributes)
         debug, report_level, halt_level, stream = self[category].astuple()
         if level >= report_level or debug and level == 0:
+            msgtext = msg.astext().encode(self.encoding, self.error_handler)
             if category:
-                print >>stream, msg.astext(), '[%s]' % category
+                print >>stream, msgtext, '[%s]' % category
             else:
-                print >>stream, msg.astext()
+                print >>stream, msgtext
         if level >= halt_level:
-            raise SystemMessage(msg)
+            raise SystemMessage(msg, level)
         if level > 0 or debug:
             self.notify_observers(msg)
+        self.max_level = max(level, self.max_level)
         return msg
 
     def debug(self, *args, **kwargs):
@@ -368,10 +382,6 @@
         attlist.append((attname.lower(), data))
     return attlist
 
-def normalize_name(name):
-    """Return a case- and whitespace-normalized name."""
-    return ' '.join(name.lower().split())
-
 def new_document(source, settings=None):
     """
     Return a new empty document object.
@@ -385,7 +395,9 @@
     if settings is None:
         settings = frontend.OptionParser().get_default_values()
     reporter = Reporter(source, settings.report_level, settings.halt_level,
-                        settings.warning_stream, settings.debug)
+                        stream=settings.warning_stream, debug=settings.debug,
+                        encoding=settings.error_encoding,
+                        error_handler=settings.error_encoding_error_handler)
     document = nodes.document(settings, reporter, source=source)
     document.note_source(source, -1)
     return document
@@ -401,7 +413,7 @@
 
 def relative_path(source, target):
     """
-    Build and return a path to `target`, relative to `source`.
+    Build and return a path to `target`, relative to `source` (both files).
 
     If there is no common prefix, return the absolute path to `target`.
     """
@@ -426,7 +438,7 @@
 def get_source_line(node):
     """
     Return the "source" and "line" attributes from the `node` given or from
-    it's closest ancestor.
+    its closest ancestor.
     """
     while node:
         if node.source or node.line: