[Zope-Checkins] CVS: Zope/lib/python/docutils -
__init__.py:1.2.10.3 core.py:1.2.10.3 frontend.py:1.2.10.4
io.py:1.2.10.3 nodes.py:1.2.10.3 roman.py:1.2.10.2
statemachine.py:1.2.10.3 urischemes.py:1.2.10.2 utils.py:1.2.10.3
Andreas Jung
cvs-admin at zope.org
Sun Nov 30 11:05:52 EST 2003
Update of /cvs-repository/Zope/lib/python/docutils
In directory cvs.zope.org:/tmp/cvs-serv9303
Added Files:
Tag: Zope-2_7-branch
__init__.py core.py frontend.py io.py nodes.py roman.py
statemachine.py urischemes.py utils.py
Log Message:
updated
=== Zope/lib/python/docutils/__init__.py 1.2.10.2 => 1.2.10.3 ===
--- /dev/null Sun Nov 30 11:05:52 2003
+++ Zope/lib/python/docutils/__init__.py Sun Nov 30 11:05:21 2003
@@ -0,0 +1,144 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision$
+# Date: $Date$
+# Copyright: This module has been placed in the public domain.
+
+"""
+This is the Docutils (Python Documentation Utilities) package.
+
+Package Structure
+=================
+
+Modules:
+
+- __init__.py: Contains component base classes, exception classes, and
+ Docutils `__version__`.
+
+- core.py: Contains the ``Publisher`` class and ``publish_*()`` convenience
+ functions.
+
+- frontend.py: Runtime settings (command-line interface, configuration files)
+ processing, for Docutils front-ends.
+
+- io.py: Provides a uniform API for low-level input and output.
+
+- nodes.py: Docutils document tree (doctree) node class library.
+
+- statemachine.py: A finite state machine specialized for
+ regular-expression-based text filters.
+
+- urischemes.py: Contains a complete mapping of known URI addressing
+ scheme names to descriptions.
+
+- utils.py: Contains the ``Reporter`` system warning class and miscellaneous
+ utilities.
+
+Subpackages:
+
+- languages: Language-specific mappings of terms.
+
+- parsers: Syntax-specific input parser modules or packages.
+
+- readers: Context-specific input handlers which understand the data
+ source and manage a parser.
+
+- transforms: Modules used by readers and writers to modify DPS
+ doctrees.
+
+- writers: Format-specific output translators.
+"""
+
+__docformat__ = 'reStructuredText'
+
+__version__ = '0.3.1'
+"""``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 or
+significant new functionality. The minor number is bumped whenever there is a
+project release. The major number will be bumped when the project is
+feature-complete, and perhaps if there is a major change in the design."""
+
+
+class ApplicationError(StandardError): pass
+class DataError(ApplicationError): pass
+
+
+class SettingsSpec:
+
+ """
+ Runtime setting specification base class.
+
+ SettingsSpec subclass objects used by `docutils.frontend.OptionParser`.
+ """
+
+ settings_spec = ()
+ """Runtime settings specification. Override in subclasses.
+
+ Specifies runtime settings and associated command-line options, as used by
+ `docutils.frontend.OptionParser`. This tuple contains one or more sets of
+ option group title, description, and a list/tuple of tuples: ``('help
+ text', [list of option strings], {keyword arguments})``. Group title
+ and/or description may be `None`; a group title of `None` implies no
+ group, just a list of single options. The "keyword arguments" dictionary
+ contains arguments to the OptionParser/OptionGroup ``add_option`` method,
+ with the addition of a "validator" keyword (see the
+ `docutils.frontend.OptionParser.validators` instance attribute). Runtime
+ settings names are derived implicitly from long option names
+ ("--a-setting" becomes ``settings.a_setting``) or 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
+ defined in other components. Override in subclasses."""
+
+ relative_path_settings = ()
+ """Settings containing filesystem paths. Override in subclasses.
+ Settings listed here are to be interpreted relative to the current working
+ directory."""
+
+ config_section = None
+ """The name of the config file section specific to this component
+ (lowercase, no brackets). Override in subclasses."""
+
+ config_section_dependencies = None
+ """A list of names of config file sections that are to be applied before
+ `config_section`, in order (from general to specific). In other words,
+ the settings in `config_section` are to be overlaid on top of the settings
+ from these sections. The "general" section is assumed implicitly.
+ Override in subclasses."""
+
+
+class TransformSpec:
+
+ """
+ Runtime transform specification base class.
+
+ TransformSpec subclass objects used by `docutils.transforms.Transformer`.
+ """
+
+ default_transforms = ()
+ """Transforms required by this class. Override in subclasses."""
+
+
+class Component(SettingsSpec, TransformSpec):
+
+ """Base class for Docutils components."""
+
+ component_type = None
+ """Override in subclasses."""
+
+ supported = ()
+ """Names for this component. Override in subclasses."""
+
+ def supports(self, format):
+ """
+ Is `format` supported by this component?
+
+ To be used by transforms to ask the dependent component if it supports
+ a certain input context or output format.
+ """
+ return format in self.supported
=== Zope/lib/python/docutils/core.py 1.2.10.2 => 1.2.10.3 ===
--- /dev/null Sun Nov 30 11:05:52 2003
+++ Zope/lib/python/docutils/core.py Sun Nov 30 11:05:21 2003
@@ -0,0 +1,378 @@
+# Authors: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision$
+# Date: $Date$
+# Copyright: This module has been placed in the public domain.
+
+"""
+Calling the ``publish_*`` convenience functions (or instantiating a
+`Publisher` object) with component names will result in default
+behavior. For custom behavior (setting component options), create
+custom component objects first, and pass *them* to
+``publish_*``/`Publisher`.
+"""
+
+__docformat__ = 'reStructuredText'
+
+import sys
+from docutils import __version__, Component, SettingsSpec
+from docutils import frontend, io, utils, readers, parsers, writers
+from docutils.frontend import OptionParser
+
+
+class Publisher:
+
+ """
+ A facade encapsulating the high-level logic of a Docutils system.
+ """
+
+ def __init__(self, reader=None, parser=None, writer=None,
+ source=None, source_class=io.FileInput,
+ destination=None, destination_class=io.FileOutput,
+ settings=None):
+ """
+ Initial setup. If any of `reader`, `parser`, or `writer` are not
+ specified, the corresponding ``set_...`` method should be called with
+ a component name (`set_reader` sets the parser as well).
+ """
+
+ self.reader = reader
+ """A `readers.Reader` instance."""
+
+ self.parser = parser
+ """A `parsers.Parser` instance."""
+
+ self.writer = writer
+ """A `writers.Writer` instance."""
+
+ self.source = source
+ """The source of input data, an `io.Input` instance."""
+
+ self.source_class = source_class
+ """The class for dynamically created source objects."""
+
+ self.destination = destination
+ """The destination for docutils output, an `io.Output` instance."""
+
+ self.destination_class = destination_class
+ """The class for dynamically created destination objects."""
+
+ self.settings = settings
+ """An object containing Docutils settings as instance attributes.
+ Set by `self.process_command_line()` or `self.get_settings()`."""
+
+ def set_reader(self, reader_name, parser, parser_name):
+ """Set `self.reader` by name."""
+ reader_class = readers.get_reader_class(reader_name)
+ self.reader = reader_class(parser, parser_name)
+ self.parser = self.reader.parser
+
+ def set_writer(self, writer_name):
+ """Set `self.writer` by name."""
+ writer_class = writers.get_writer_class(writer_name)
+ self.writer = writer_class()
+
+ def set_components(self, reader_name, parser_name, writer_name):
+ if self.reader is None:
+ self.set_reader(reader_name, self.parser, parser_name)
+ if self.parser is None:
+ if self.reader.parser is None:
+ self.reader.set_parser(parser_name)
+ self.parser = self.reader.parser
+ if self.writer is None:
+ self.set_writer(writer_name)
+
+ def setup_option_parser(self, usage=None, description=None,
+ settings_spec=None, config_section=None,
+ **defaults):
+ if config_section and not settings_spec:
+ settings_spec = SettingsSpec()
+ settings_spec.config_section = config_section
+ parts = config_section.split()
+ if len(parts) > 1 and parts[-1] == 'application':
+ settings_spec.config_section_dependencies = ['applications']
+ #@@@ Add self.source & self.destination to components in future?
+ option_parser = OptionParser(
+ components=(self.parser, self.reader, self.writer, settings_spec),
+ defaults=defaults, read_config_files=1,
+ usage=usage, description=description)
+ return option_parser
+
+ def get_settings(self, usage=None, description=None,
+ settings_spec=None, config_section=None, **defaults):
+ """
+ Set and return default settings (overrides in `defaults` dict).
+
+ Set components first (`self.set_reader` & `self.set_writer`).
+ Explicitly setting `self.settings` disables command line option
+ processing from `self.publish()`.
+ """
+ option_parser = self.setup_option_parser(
+ usage, description, settings_spec, config_section, **defaults)
+ self.settings = option_parser.get_default_values()
+ return self.settings
+
+ def process_command_line(self, argv=None, usage=None, description=None,
+ settings_spec=None, config_section=None,
+ **defaults):
+ """
+ Pass an empty list to `argv` to avoid reading `sys.argv` (the
+ default).
+
+ Set components first (`self.set_reader` & `self.set_writer`).
+ """
+ option_parser = self.setup_option_parser(
+ usage, description, settings_spec, config_section,**defaults)
+ if argv is None:
+ argv = sys.argv[1:]
+ self.settings = option_parser.parse_args(argv)
+
+ def set_io(self, source_path=None, destination_path=None):
+ if self.source is None:
+ self.set_source(source_path=source_path)
+ if self.destination is None:
+ self.set_destination(destination_path=destination_path)
+
+ def set_source(self, source=None, source_path=None):
+ if source_path is None:
+ source_path = self.settings._source
+ else:
+ self.settings._source = source_path
+ self.source = self.source_class(
+ source=source, source_path=source_path,
+ encoding=self.settings.input_encoding)
+
+ def set_destination(self, destination=None, destination_path=None):
+ if destination_path is None:
+ destination_path = self.settings._destination
+ else:
+ self.settings._destination = destination_path
+ self.destination = self.destination_class(
+ destination=destination, destination_path=destination_path,
+ encoding=self.settings.output_encoding,
+ error_handler=self.settings.output_encoding_error_handler)
+
+ def apply_transforms(self, document):
+ document.transformer.populate_from_components(
+ (self.source, self.reader, self.reader.parser, self.writer,
+ self.destination))
+ document.transformer.apply_transforms()
+
+ def publish(self, argv=None, usage=None, description=None,
+ settings_spec=None, settings_overrides=None,
+ config_section=None, enable_exit=None):
+ """
+ Process command line options and arguments (if `self.settings` not
+ already set), run `self.reader` and then `self.writer`. Return
+ `self.writer`'s output.
+ """
+ if self.settings is None:
+ self.process_command_line(
+ argv, usage, description, settings_spec, config_section,
+ **(settings_overrides or {}))
+ elif settings_overrides:
+ self.settings._update(settings_overrides, 'loose')
+ self.set_io()
+ 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 at 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 and document:
+ from pprint import pformat
+ print >>sys.stderr, '\n::: Document internals:'
+ print >>sys.stderr, pformat(document.__dict__)
+ 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 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
+
+
+default_usage = '%prog [options] [<source> [<destination>]]'
+default_description = ('Reads from <source> (default is stdin) and writes to '
+ '<destination> (default is stdout).')
+
+def publish_cmdline(reader=None, reader_name='standalone',
+ parser=None, parser_name='restructuredtext',
+ writer=None, writer_name='pseudoxml',
+ settings=None, settings_spec=None,
+ settings_overrides=None, config_section=None,
+ enable_exit=1, argv=None,
+ usage=default_usage, description=default_description):
+ """
+ Set up & run a `Publisher`. For command-line front ends.
+
+ Parameters:
+
+ - `reader`: A `docutils.readers.Reader` object.
+ - `reader_name`: Name or alias of the Reader class to be instantiated if
+ no `reader` supplied.
+ - `parser`: A `docutils.parsers.Parser` object.
+ - `parser_name`: Name or alias of the Parser class to be instantiated if
+ no `parser` supplied.
+ - `writer`: A `docutils.writers.Writer` object.
+ - `writer_name`: Name or alias of the Writer class to be instantiated if
+ no `writer` supplied.
+ - `settings`: Runtime settings object.
+ - `settings_spec`: Extra settings specification; a `docutils.SettingsSpec`
+ subclass. Used only if no `settings` specified.
+ - `settings_overrides`: A dictionary containing program-specific overrides
+ of component settings.
+ - `config_section`: Name of configuration file section for application.
+ Used only if no `settings` or `settings_spec` specified.
+ - `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.
+ - `description`: Program description, output for the "--help" option
+ (along with command-line option descriptions).
+ """
+ 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,
+ config_section=config_section, 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,
+ config_section=None, enable_exit=None):
+ """
+ Set up & run a `Publisher`. For programmatic use with file-like I/O.
+
+ Parameters:
+
+ - `source`: A file-like object (must have "read" and "close" methods).
+ - `source_path`: Path to the input file. Opened if no `source` supplied.
+ If neither `source` nor `source_path` are supplied, `sys.stdin` is used.
+ - `destination`: A file-like object (must have "write" and "close"
+ methods).
+ - `destination_path`: Path to the input file. Opened if no `destination`
+ supplied. If neither `destination` nor `destination_path` are supplied,
+ `sys.stdout` is used.
+ - `reader`: A `docutils.readers.Reader` object.
+ - `reader_name`: Name or alias of the Reader class to be instantiated if
+ no `reader` supplied.
+ - `parser`: A `docutils.parsers.Parser` object.
+ - `parser_name`: Name or alias of the Parser class to be instantiated if
+ no `parser` supplied.
+ - `writer`: A `docutils.writers.Writer` object.
+ - `writer_name`: Name or alias of the Writer class to be instantiated if
+ no `writer` supplied.
+ - `settings`: Runtime settings object.
+ - `settings_spec`: Extra settings specification; a `docutils.SettingsSpec`
+ subclass. Used only if no `settings` specified.
+ - `settings_overrides`: A dictionary containing program-specific overrides
+ of component settings.
+ - `config_section`: Name of configuration file section for application.
+ Used only if no `settings` or `settings_spec` specified.
+ - `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)
+ if settings is None:
+ settings = pub.get_settings(settings_spec=settings_spec,
+ config_section=config_section)
+ if settings_overrides:
+ settings._update(settings_overrides, 'loose')
+ pub.set_source(source, source_path)
+ pub.set_destination(destination, destination_path)
+ 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, config_section=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. Here's how::
+
+ publish_string(..., settings_overrides={'output_encoding': 'unicode'})
+
+ Similarly for Unicode string input (`source`)::
+
+ publish_string(..., settings_overrides={'input_encoding': 'unicode'})
+
+ Parameters:
+
+ - `source`: An input string; required. This can be an encoded 8-bit
+ string (set the "input_encoding" setting to the correct encoding) or a
+ Unicode string (set the "input_encoding" setting to "unicode").
+ - `source_path`: Path to the file or object that produced `source`;
+ optional. Only used for diagnostic output.
+ - `destination_path`: Path to the file or object which will receive the
+ output; optional. Used for determining relative paths (stylesheets,
+ source links, etc.).
+ - `reader`: A `docutils.readers.Reader` object.
+ - `reader_name`: Name or alias of the Reader class to be instantiated if
+ no `reader` supplied.
+ - `parser`: A `docutils.parsers.Parser` object.
+ - `parser_name`: Name or alias of the Parser class to be instantiated if
+ no `parser` supplied.
+ - `writer`: A `docutils.writers.Writer` object.
+ - `writer_name`: Name or alias of the Writer class to be instantiated if
+ no `writer` supplied.
+ - `settings`: Runtime settings object.
+ - `settings_spec`: Extra settings specification; a `docutils.SettingsSpec`
+ subclass. Used only if no `settings` specified.
+ - `settings_overrides`: A dictionary containing program-specific overrides
+ of component settings.
+ - `config_section`: Name of configuration file section for application.
+ Used only if no `settings` or `settings_spec` specified.
+ - `enable_exit`: Boolean; enable exit status at end of processing?
+ """
+ pub = Publisher(reader, parser, writer, settings=settings,
+ source_class=io.StringInput,
+ destination_class=io.StringOutput)
+ pub.set_components(reader_name, parser_name, writer_name)
+ if settings is None:
+ settings = pub.get_settings(settings_spec=settings_spec,
+ config_section=config_section)
+ if settings_overrides:
+ settings._update(settings_overrides, 'loose')
+ pub.set_source(source, source_path)
+ pub.set_destination(destination_path=destination_path)
+ return pub.publish(enable_exit=enable_exit)
=== Zope/lib/python/docutils/frontend.py 1.2.10.3 => 1.2.10.4 ===
--- /dev/null Sun Nov 30 11:05:52 2003
+++ Zope/lib/python/docutils/frontend.py Sun Nov 30 11:05:21 2003
@@ -0,0 +1,621 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision$
+# Date: $Date$
+# Copyright: This module has been placed in the public domain.
+
+"""
+Command-line and common processing for Docutils front-end tools.
+
+Exports the following classes:
+
+* `OptionParser`: Standard Docutils command-line processing.
+* `Option`: Customized version of `optparse.Option`; validation support.
+* `Values`: Runtime settings; objects are simple structs
+ (``object.attribute``). Supports cumulative list settings (attributes).
+* `ConfigParser`: Standard Docutils config file processing.
+
+Also exports the following functions:
+
+* Option callbacks: `store_multiple`, `read_config_file`.
+* Setting validators (see `OptionParser.validators`: `validate_encoding`,
+ `validate_encoding_error_handler`, `validate_encoding_and_error_handler`,
+ `validate_boolean`, `validate_threshold`,
+ `validate_colon_separated_string_list`.
+* `make_paths_absolute`.
+"""
+
+__docformat__ = 'reStructuredText'
+
+import os
+import os.path
+import sys
+import types
+import copy
+import warnings
+import ConfigParser as CP
+import codecs
+import docutils
+import optparse
+from optparse import Values, SUPPRESS_HELP
+
+
+def store_multiple(option, opt, value, parser, *args, **kwargs):
+ """
+ Store multiple values in `parser.values`. (Option callback.)
+
+ Store `None` for each attribute named in `args`, and store the value for
+ each key (attribute name) in `kwargs`.
+ """
+ for attribute in args:
+ setattr(parser.values, attribute, None)
+ for key, value in kwargs.items():
+ setattr(parser.values, key, value)
+
+def read_config_file(option, opt, value, parser):
+ """
+ Read a configuration file during option processing. (Option callback.)
+ """
+ try:
+ new_settings = parser.get_config_file_settings(value)
+ except ValueError, error:
+ parser.error(error)
+ parser.values.update(new_settings, parser)
+
+def validate_encoding(setting, value, option_parser,
+ config_parser=None, config_section=None):
+ try:
+ codecs.lookup(value)
+ except LookupError:
+ raise (LookupError('setting "%s": unknown encoding: "%s"'
+ % (setting, value)),
+ None, sys.exc_info()[2])
+ return value
+
+def validate_encoding_error_handler(setting, value, option_parser,
+ config_parser=None, config_section=None):
+ 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 validate_encoding_and_error_handler(
+ setting, value, option_parser, config_parser=None, config_section=None):
+ """
+ Side-effect: if an error handler is included in the value, it is inserted
+ into the appropriate place as if it was a separate setting/option.
+ """
+ if ':' in value:
+ encoding, handler = value.split(':')
+ validate_encoding_error_handler(
+ setting + '_error_handler', handler, option_parser,
+ config_parser, config_section)
+ if config_parser:
+ config_parser.set(config_section, setting + '_error_handler',
+ handler)
+ else:
+ setattr(option_parser.values, setting + '_error_handler', handler)
+ else:
+ encoding = value
+ validate_encoding(setting, encoding, option_parser,
+ config_parser, config_section)
+ return encoding
+
+def validate_boolean(setting, value, option_parser,
+ config_parser=None, config_section=None):
+ if isinstance(value, types.StringType):
+ try:
+ return option_parser.booleans[value.strip().lower()]
+ except KeyError:
+ raise (LookupError('unknown boolean value: "%s"' % value),
+ None, sys.exc_info()[2])
+ return value
+
+def validate_threshold(setting, value, option_parser,
+ config_parser=None, config_section=None):
+ try:
+ int(value)
+ return value
+ except ValueError:
+ try:
+ return option_parser.thresholds[value.lower()]
+ except (KeyError, AttributeError):
+ raise (LookupError('unknown threshold: %r.' % value),
+ None, sys.exc_info[2])
+
+def validate_colon_separated_string_list(
+ setting, value, option_parser, config_parser=None, config_section=None):
+ if isinstance(value, types.StringType):
+ value = value.split(':')
+ else:
+ last = value.pop()
+ value.extend(last.split(':'))
+ return value
+
+def make_paths_absolute(pathdict, keys, base_path=None):
+ """
+ Interpret filesystem path settings relative to the `base_path` given.
+
+ Paths are values in `pathdict` whose keys are in `keys`. Get `keys` from
+ `OptionParser.relative_path_settings`.
+ """
+ if base_path is None:
+ base_path = os.getcwd()
+ for key in keys:
+ if pathdict.has_key(key):
+ value = pathdict[key]
+ if isinstance(value, types.ListType):
+ value = [make_one_path_absolute(base_path, path)
+ for path in value]
+ elif value:
+ value = make_one_path_absolute(base_path, value)
+ pathdict[key] = value
+
+def make_one_path_absolute(base_path, path):
+ return os.path.abspath(os.path.join(base_path, path))
+
+
+class Values(optparse.Values):
+
+ """
+ Updates list attributes by extension rather than by replacement.
+ Works in conjunction with the `OptionParser.lists` instance attribute.
+ """
+
+ def update(self, other_dict, option_parser):
+ if isinstance(other_dict, Values):
+ other_dict = other_dict.__dict__
+ other_dict = other_dict.copy()
+ for setting in option_parser.lists.keys():
+ if (hasattr(self, setting) and other_dict.has_key(setting)):
+ value = getattr(self, setting)
+ if value:
+ value += other_dict[setting]
+ del other_dict[setting]
+ self._update_loose(other_dict)
+
+
+class Option(optparse.Option):
+
+ def process(self, opt, value, values, parser):
+ """
+ Call the validator function on applicable settings.
+ Extends `optparse.Option.process`.
+ """
+ result = optparse.Option.process(self, opt, value, values, parser)
+ setting = self.dest
+ if setting:
+ value = getattr(values, setting)
+ validator = parser.validators.get(setting)
+ if validator:
+ try:
+ new_value = validator(setting, value, parser)
+ except Exception, error:
+ raise (optparse.OptionValueError(
+ 'Error in option "%s":\n %s: %s'
+ % (opt, error.__class__.__name__, error)),
+ None, sys.exc_info()[2])
+ setattr(values, setting, new_value)
+ return result
+
+
+class OptionParser(optparse.OptionParser, docutils.SettingsSpec):
+
+ """
+ Parser for command-line and library use. The `settings_spec`
+ specification here and in other Docutils components are merged to build
+ the set of command-line options and runtime settings for this process.
+
+ Common settings (defined below) and component-specific settings must not
+ conflict. Short options are reserved for common settings, and components
+ are restrict to using long options.
+ """
+
+ standard_config_files = [
+ '/etc/docutils.conf', # system-wide
+ './docutils.conf', # project-specific
+ '~/.docutils'] # user-specific
+ """Docutils configuration files, using ConfigParser syntax. Filenames
+ will be tilde-expanded later. Later files override earlier ones."""
+
+ threshold_choices = 'info 1 warning 2 error 3 severe 4 none 5'.split()
+ """Possible inputs for for --report and --halt threshold values."""
+
+ thresholds = {'info': 1, 'warning': 2, 'error': 3, 'severe': 4, 'none': 5}
+ """Lookup table for --report and --halt threshold values."""
+
+ booleans={'1': 1, 'on': 1, 'yes': 1, 'true': 1,
+ '0': 0, 'off': 0, 'no': 0, 'false': 0, '': 0}
+ """Lookup table for boolean configuration file settings."""
+
+ if hasattr(codecs, 'backslashreplace_errors'):
+ default_error_encoding_error_handler = 'backslashreplace'
+ else:
+ default_error_encoding_error_handler = 'replace'
+
+ settings_spec = (
+ 'General Docutils Options',
+ None,
+ (('Include a "Generated by Docutils" credit and link at the end '
+ 'of the document.',
+ ['--generator', '-g'], {'action': 'store_true',
+ 'validator': validate_boolean}),
+ ('Do not include a generator credit.',
+ ['--no-generator'], {'action': 'store_false', 'dest': 'generator'}),
+ ('Include the date at the end of the document (UTC).',
+ ['--date', '-d'], {'action': 'store_const', 'const': '%Y-%m-%d',
+ 'dest': 'datestamp'}),
+ ('Include the time & date at the end of the document (UTC).',
+ ['--time', '-t'], {'action': 'store_const',
+ 'const': '%Y-%m-%d %H:%M UTC',
+ 'dest': 'datestamp'}),
+ ('Do not include a datestamp of any kind.',
+ ['--no-datestamp'], {'action': 'store_const', 'const': None,
+ 'dest': 'datestamp'}),
+ ('Include a "View document source" link (relative to destination).',
+ ['--source-link', '-s'], {'action': 'store_true',
+ 'validator': validate_boolean}),
+ ('Use the supplied <URL> verbatim for a "View document source" '
+ 'link; implies --source-link.',
+ ['--source-url'], {'metavar': '<URL>'}),
+ ('Do not include a "View document source" link.',
+ ['--no-source-link'],
+ {'action': 'callback', 'callback': store_multiple,
+ 'callback_args': ('source_link', 'source_url')}),
+ ('Enable backlinks from section headers to table of contents '
+ 'entries. This is the default.',
+ ['--toc-entry-backlinks'],
+ {'dest': 'toc_backlinks', 'action': 'store_const', 'const': 'entry',
+ 'default': 'entry'}),
+ ('Enable backlinks from section headers to the top of the table of '
+ 'contents.',
+ ['--toc-top-backlinks'],
+ {'dest': 'toc_backlinks', 'action': 'store_const', 'const': 'top'}),
+ ('Disable backlinks to the table of contents.',
+ ['--no-toc-backlinks'],
+ {'dest': 'toc_backlinks', 'action': 'store_false'}),
+ ('Enable backlinks from footnotes and citations to their '
+ 'references. This is the default.',
+ ['--footnote-backlinks'],
+ {'action': 'store_true', 'default': 1,
+ 'validator': validate_boolean}),
+ ('Disable backlinks from footnotes and citations.',
+ ['--no-footnote-backlinks'],
+ {'dest': 'footnote_backlinks', 'action': 'store_false'}),
+ ('Set verbosity threshold; report system messages at or higher than '
+ '<level> (by name or number: "info" or "1", warning/2, error/3, '
+ 'severe/4; also, "none" or "5"). Default is 2 (warning).',
+ ['--report', '-r'], {'choices': threshold_choices, 'default': 2,
+ 'dest': 'report_level', 'metavar': '<level>',
+ 'validator': validate_threshold}),
+ ('Report all system messages, info-level and higher. (Same as '
+ '"--report=info".)',
+ ['--verbose', '-v'], {'action': 'store_const', 'const': 'info',
+ 'dest': 'report_level'}),
+ ('Do not report any system messages. (Same as "--report=none".)',
+ ['--quiet', '-q'], {'action': 'store_const', 'const': 'none',
+ 'dest': 'report_level'}),
+ ('Set the threshold (<level>) at or above which system messages are '
+ 'converted to exceptions, halting execution immediately. Levels '
+ 'as in --report. Default is 4 (severe).',
+ ['--halt'], {'choices': threshold_choices, 'dest': 'halt_level',
+ 'default': 4, 'metavar': '<level>',
+ 'validator': validate_threshold}),
+ ('Same as "--halt=info": halt processing at the slightest problem.',
+ ['--strict'], {'action': 'store_const', 'const': 'info',
+ 'dest': 'halt_level'}),
+ ('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>',
+ 'validator': validate_threshold}),
+ ('Report debug-level system messages and generate diagnostic output.',
+ ['--debug'], {'action': 'store_true', 'validator': validate_boolean}),
+ ('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,
+ 'validator': validate_boolean}),
+ ('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>', 'validator': validate_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[:handler]>', 'default': 'utf-8',
+ 'validator': validate_encoding_and_error_handler}),
+ (SUPPRESS_HELP, # usually handled by --output-encoding
+ ['--output_encoding_error_handler'],
+ {'default': 'strict', 'validator': validate_encoding_error_handler}),
+ ('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'],
+ {'metavar': '<name[:handler]>', 'default': 'ascii',
+ 'validator': validate_encoding_and_error_handler}),
+ (SUPPRESS_HELP, # usually handled by --error-encoding
+ ['--error_encoding_error_handler'],
+ {'default': default_error_encoding_error_handler,
+ 'validator': validate_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',
+ 'metavar': '<name>'}),
+ ('Read configuration settings from <file>, if it exists.',
+ ['--config'], {'metavar': '<file>', 'type': 'string',
+ 'action': 'callback', 'callback': read_config_file}),
+ ("Show this program's version number and exit.",
+ ['--version', '-V'], {'action': 'version'}),
+ ('Show this help message and exit.',
+ ['--help', '-h'], {'action': 'help'}),
+ # Hidden options, for development use only:
+ (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',
+ 'validator': validate_colon_separated_string_list}),))
+ """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',)
+
+ config_section = 'general'
+
+ version_template = '%%prog (Docutils %s)' % docutils.__version__
+ """Default version message."""
+
+ 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.
+ """
+ self.validators = {}
+ """{setting: validation function} mapping, used by `validate_options`.
+ Validation functions take three or five parameters: setting name,
+ value, an `OptionParser` (``self``), and a `ConfigParser` and config
+ file section if activated from a config file. They return a (possibly
+ modified) value, or raise an exception. Populated from the "validator"
+ keyword argument dictionary entries of components' ``settings_spec``
+ attribute."""
+
+ self.lists = {}
+ """Set of list-type settings."""
+
+ optparse.OptionParser.__init__(
+ self, option_class=Option, add_help_option=None,
+ formatter=optparse.TitledHelpFormatter(width=78),
+ *args, **kwargs)
+ if not self.version:
+ self.version = self.version_template
+ # Make an instance copy (it will be modified):
+ self.relative_path_settings = list(self.relative_path_settings)
+ self.components = (self,) + tuple(components)
+ self.populate_from_components(self.components)
+ defaults = defaults or {}
+ if read_config_files and not self.defaults['_disable_config']:
+ try:
+ config_settings = self.get_standard_config_settings()
+ except ValueError, error:
+ self.error(error)
+ defaults.update(config_settings.__dict__)
+ # 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
+ i = 0
+ settings_spec = component.settings_spec
+ self.relative_path_settings.extend(
+ component.relative_path_settings)
+ while i < len(settings_spec):
+ title, description, option_spec = settings_spec[i:i+3]
+ if title:
+ 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:
+ kwargs = kwargs.copy() # to be modified, locally only
+ if kwargs.has_key('validator'):
+ validator = kwargs['validator']
+ del kwargs['validator']
+ else:
+ validator = None
+ option = group.add_option(help=help_text, *option_strings,
+ **kwargs)
+ if validator:
+ self.validators[option.dest] = validator
+ if kwargs.get('action') == 'append':
+ self.lists[option.dest] = 1
+ if component.settings_defaults:
+ self.defaults.update(component.settings_defaults)
+ i += 3
+ for component in components:
+ if component and component.settings_default_overrides:
+ self.defaults.update(component.settings_default_overrides)
+
+ def get_standard_config_files(self):
+ """Return list of config files, from environment or standard."""
+ try:
+ config_files = os.environ['DOCUTILSCONFIG'].split(os.pathsep)
+ except KeyError:
+ config_files = self.standard_config_files
+ return [os.path.expanduser(f) for f in config_files if f.strip()]
+
+ def get_standard_config_settings(self):
+ settings = Values()
+ for filename in self.get_standard_config_files():
+ settings.update(self.get_config_file_settings(filename), self)
+ return settings
+
+ def get_config_file_settings(self, config_file):
+ """Returns a dictionary containing appropriate config file settings."""
+ parser = ConfigParser()
+ parser.read(config_file, self)
+ base_path = os.path.dirname(config_file)
+ applied = {}
+ settings = Values()
+ for component in self.components:
+ if not component:
+ continue
+ for section in (tuple(component.config_section_dependencies or ())
+ + (component.config_section,)):
+ if applied.has_key(section):
+ continue
+ applied[section] = 1
+ settings.update(parser.get_section(section), self)
+ make_paths_absolute(
+ settings.__dict__, self.relative_path_settings, base_path)
+ return settings.__dict__
+
+ def check_values(self, values, args):
+ """Store positional arguments as runtime settings."""
+ values._source, values._destination = self.check_args(args)
+ make_paths_absolute(values.__dict__, self.relative_path_settings,
+ os.getcwd())
+ return values
+
+ def check_args(self, args):
+ 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:
+ self.error('Do not specify the same file for both source and '
+ 'destination. It will clobber the source file.')
+ return source, destination
+
+ def get_default_values(self):
+ """Needed to get custom `Values` instances."""
+ return Values(self.defaults)
+
+
+class ConfigParser(CP.ConfigParser):
+
+ old_settings = {
+ 'pep_stylesheet': ('pep_html writer', 'stylesheet'),
+ 'pep_stylesheet_path': ('pep_html writer', 'stylesheet_path'),
+ 'pep_template': ('pep_html writer', 'template')}
+ """{old setting: (new section, new setting)} mapping, used by
+ `handle_old_config`, to convert settings from the old [options] section."""
+
+ old_warning = """
+The "[option]" section is deprecated. Support for old-format configuration
+files may be removed in a future Docutils release. Please revise your
+configuration files. See <http://docutils.sf.net/docs/config.html>, section
+"Old-Format Configuration Files".
+"""
+
+ def read(self, filenames, option_parser):
+ if type(filenames) in (types.StringType, types.UnicodeType):
+ filenames = [filenames]
+ for filename in filenames:
+ CP.ConfigParser.read(self, filename)
+ if self.has_section('options'):
+ self.handle_old_config(filename)
+ self.validate_settings(filename, option_parser)
+
+ def handle_old_config(self, filename):
+ warnings.warn_explicit(self.old_warning, ConfigDeprecationWarning,
+ filename, 0)
+ options = self.get_section('options')
+ if not self.has_section('general'):
+ self.add_section('general')
+ for key, value in options.items():
+ if self.old_settings.has_key(key):
+ section, setting = self.old_settings[key]
+ if not self.has_section(section):
+ self.add_section(section)
+ else:
+ section = 'general'
+ setting = key
+ if not self.has_option(section, setting):
+ self.set(section, setting, value)
+ self.remove_section('options')
+
+ def validate_settings(self, filename, option_parser):
+ """Call the validator function on all applicable settings."""
+ for section in self.sections():
+ for setting in self.options(section):
+ validator = option_parser.validators.get(setting)
+ if validator:
+ value = self.get(section, setting, raw=1)
+ try:
+ new_value = validator(
+ setting, value, option_parser,
+ config_parser=self, config_section=section)
+ except Exception, error:
+ raise (ValueError(
+ 'Error in config file "%s", section "[%s]":\n'
+ ' %s: %s\n %s = %s'
+ % (filename, section, error.__class__.__name__,
+ error, setting, value)), None, sys.exc_info()[2])
+ self.set(section, setting, new_value)
+
+ def optionxform(self, optionstr):
+ """
+ Transform '-' to '_' so the cmdline form of option names can be used.
+ """
+ return optionstr.lower().replace('-', '_')
+
+ def get_section(self, section):
+ """
+ Return a given section as a dictionary (empty if the section
+ doesn't exist).
+ """
+ section_dict = {}
+ if self.has_section(section):
+ for option in self.options(section):
+ section_dict[option] = self.get(section, option, raw=1)
+ return section_dict
+
+
+class ConfigDeprecationWarning(DeprecationWarning):
+ """Warning for deprecated configuration file features."""
=== Zope/lib/python/docutils/io.py 1.2.10.2 => 1.2.10.3 ===
--- /dev/null Sun Nov 30 11:05:52 2003
+++ Zope/lib/python/docutils/io.py Sun Nov 30 11:05:21 2003
@@ -0,0 +1,296 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision$
+# Date: $Date$
+# Copyright: This module has been placed in the public domain.
+
+"""
+I/O classes provide a uniform API for low-level input and output. Subclasses
+will exist for a variety of input/output mechanisms.
+"""
+
+__docformat__ = 'reStructuredText'
+
+import sys
+import locale
+from types import UnicodeType
+from docutils import TransformSpec
+
+
+class Input(TransformSpec):
+
+ """
+ Abstract base class for input wrappers.
+ """
+
+ component_type = 'input'
+
+ default_source_path = None
+
+ def __init__(self, source=None, source_path=None, encoding=None):
+ self.encoding = encoding
+ """Text encoding for the input source."""
+
+ self.source = source
+ """The source of input data."""
+
+ self.source_path = source_path
+ """A text reference to the source."""
+
+ if not source_path:
+ self.source_path = self.default_source_path
+
+ def __repr__(self):
+ return '%s: source=%r, source_path=%r' % (self.__class__, self.source,
+ self.source_path)
+
+ def read(self):
+ raise NotImplementedError
+
+ def decode(self, data):
+ """
+ Decode a string, `data`, heuristically.
+ Raise UnicodeError if unsuccessful.
+
+ The client application should call ``locale.setlocale`` at the
+ beginning of processing::
+
+ locale.setlocale(locale.LC_ALL, '')
+ """
+ if (self.encoding and self.encoding.lower() == 'unicode'
+ or isinstance(data, UnicodeType)):
+ return unicode(data)
+ encodings = [self.encoding, 'utf-8']
+ try:
+ encodings.append(locale.nl_langinfo(locale.CODESET))
+ except:
+ pass
+ try:
+ encodings.append(locale.getlocale()[1])
+ except:
+ pass
+ try:
+ encodings.append(locale.getdefaultlocale()[1])
+ except:
+ pass
+ encodings.append('latin-1')
+ for enc in encodings:
+ if not enc:
+ continue
+ try:
+ return unicode(data, enc)
+ except (UnicodeError, LookupError):
+ pass
+ raise UnicodeError(
+ 'Unable to decode input data. Tried the following encodings: %s.'
+ % ', '.join([repr(enc) for enc in encodings if enc]))
+
+
+class Output(TransformSpec):
+
+ """
+ Abstract base class for output wrappers.
+ """
+
+ component_type = 'output'
+
+ default_destination_path = None
+
+ def __init__(self, destination=None, destination_path=None,
+ encoding=None, error_handler='strict'):
+ self.encoding = encoding
+ """Text encoding for the output destination."""
+
+ self.error_handler = error_handler or 'strict'
+ """Text encoding error handler."""
+
+ self.destination = destination
+ """The destination for output data."""
+
+ self.destination_path = destination_path
+ """A text reference to the destination."""
+
+ if not destination_path:
+ self.destination_path = self.default_destination_path
+
+ def __repr__(self):
+ return ('%s: destination=%r, destination_path=%r'
+ % (self.__class__, self.destination, self.destination_path))
+
+ def write(self, data):
+ raise NotImplementedError
+
+ def encode(self, data):
+ if self.encoding and self.encoding.lower() == 'unicode':
+ return data
+ else:
+ return data.encode(self.encoding, self.error_handler)
+
+
+class FileInput(Input):
+
+ """
+ Input for single, simple file-like objects.
+ """
+
+ 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
+ `None` (which implies `sys.stdin` if no `source_path` given).
+ - `source_path`: a path to a file, which is opened and then read.
+ - `autoclose`: close automatically after read (boolean); always
+ false if `sys.stdin` is the source.
+ """
+ Input.__init__(self, source, source_path, encoding)
+ self.autoclose = autoclose
+ self.handle_io_errors = handle_io_errors
+ if source is None:
+ if 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
+ if not source_path:
+ try:
+ self.source_path = self.source.name
+ except AttributeError:
+ pass
+
+ def read(self):
+ """Read and decode a single file and return the data."""
+ data = self.source.read()
+ if self.autoclose:
+ self.close()
+ return self.decode(data)
+
+ def close(self):
+ self.source.close()
+
+
+class FileOutput(Output):
+
+ """
+ Output for single, simple file-like objects.
+ """
+
+ 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
+ directly) or `None` (which implies `sys.stdout` if no
+ `destination_path` given).
+ - `destination_path`: a path to a file, which is opened and then
+ written.
+ - `autoclose`: close automatically after write (boolean); always
+ false if `sys.stdout` is the destination.
+ """
+ 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
+ else:
+ self.destination = sys.stdout
+ self.autoclose = None
+ if not destination_path:
+ try:
+ self.destination_path = self.destination.name
+ except AttributeError:
+ pass
+
+ def open(self):
+ 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):
+ """Encode `data`, write it to a single file, and return it."""
+ output = self.encode(data)
+ if not self.opened:
+ self.open()
+ self.destination.write(output)
+ if self.autoclose:
+ self.close()
+ return output
+
+ def close(self):
+ self.destination.close()
+ self.opened = None
+
+
+class StringInput(Input):
+
+ """
+ Direct string input.
+ """
+
+ default_source_path = '<string>'
+
+ def read(self):
+ """Decode and return the source string."""
+ return self.decode(self.source)
+
+
+class StringOutput(Output):
+
+ """
+ Direct string output.
+ """
+
+ default_destination_path = '<string>'
+
+ def write(self, data):
+ """Encode `data`, store it in `self.destination`, and return it."""
+ self.destination = self.encode(data)
+ return self.destination
+
+
+class NullInput(Input):
+
+ """
+ Degenerate input: read nothing.
+ """
+
+ default_source_path = 'null input'
+
+ def read(self):
+ """Return a null string."""
+ return u''
+
+
+class NullOutput(Output):
+
+ """
+ Degenerate output: write nothing.
+ """
+
+ default_destination_path = 'null output'
+
+ def write(self, data):
+ """Do nothing ([don't even] send data to the bit bucket)."""
+ pass
=== Zope/lib/python/docutils/nodes.py 1.2.10.2 => 1.2.10.3 ===
--- /dev/null Sun Nov 30 11:05:52 2003
+++ Zope/lib/python/docutils/nodes.py Sun Nov 30 11:05:22 2003
@@ -0,0 +1,1489 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision$
+# Date: $Date$
+# Copyright: This module has been placed in the public domain.
+
+"""
+Docutils document tree element class library.
+
+Classes in CamelCase are abstract base classes or auxiliary classes. The one
+exception is `Text`, for a text (PCDATA) node; uppercase is used to
+differentiate from element classes. Classes in lower_case_with_underscores
+are element classes, matching the XML element generic identifiers in the DTD_.
+
+The position of each node (the level at which it can occur) is significant and
+is represented by abstract base classes (`Root`, `Structural`, `Body`,
+`Inline`, etc.). Certain transformations will be easier because we can use
+``isinstance(node, base_class)`` to determine the position of the node in the
+hierarchy.
+
+.. _DTD: http://docutils.sourceforge.net/spec/docutils.dtd
+"""
+
+__docformat__ = 'reStructuredText'
+
+import sys
+import os
+import re
+import xml.dom.minidom
+from types import IntType, SliceType, StringType, UnicodeType, \
+ TupleType, ListType
+from UserString import UserString
+
+
+# ==============================
+# Functional Node Base Classes
+# ==============================
+
+class Node:
+
+ """Abstract base class of nodes in a document tree."""
+
+ parent = None
+ """Back-reference to the Node immediately containing this Node."""
+
+ document = None
+ """The `document` node at the root of the tree containing this Node."""
+
+ source = None
+ """Path or description of the input source which generated this Node."""
+
+ line = None
+ """The line number (1-based) of the beginning of this Node in `source`."""
+
+ def __nonzero__(self):
+ """
+ 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):
+ """Return a DOM **fragment** representation of this Node."""
+ domroot = dom.Document()
+ return self._dom_node(domroot)
+
+ def pformat(self, indent=' ', level=0):
+ """Return an indented pseudo-XML representation, for test purposes."""
+ raise NotImplementedError
+
+ def copy(self):
+ """Return a copy of self."""
+ raise NotImplementedError
+
+ def setup_child(self, child):
+ child.parent = self
+ if self.document:
+ child.document = self.document
+ if child.source is None:
+ child.source = self.document.current_source
+ if child.line is None:
+ child.line = self.document.current_line
+
+ def walk(self, visitor):
+ """
+ Traverse a tree of `Node` objects, calling ``visit_...`` methods of
+ `visitor` when entering each node. If there is no
+ ``visit_particular_node`` method for a node of type
+ ``particular_node``, the ``unknown_visit`` method is called. (The
+ `walkabout()` method is similar, except it also calls ``depart_...``
+ methods before exiting each node.)
+
+ This tree traversal supports limited in-place tree
+ modifications. Replacing one node with one or more nodes is
+ OK, as is removing an element. However, if the node removed
+ or replaced occurs after the current node, the old node will
+ still be traversed, and any new nodes will not.
+
+ Within ``visit_...`` methods (and ``depart_...`` methods for
+ `walkabout()`), `TreePruningException` subclasses may be raised
+ (`SkipChildren`, `SkipSiblings`, `SkipNode`, `SkipDeparture`).
+
+ Parameter `visitor`: A `NodeVisitor` object, containing a
+ ``visit_...`` method for each `Node` subclass encountered.
+ """
+ name = 'visit_' + self.__class__.__name__
+ method = getattr(visitor, name, visitor.unknown_visit)
+ visitor.document.reporter.debug(name, category='nodes.Node.walk')
+ try:
+ method(self)
+ except (SkipChildren, SkipNode):
+ return
+ except SkipDeparture: # not applicable; ignore
+ pass
+ children = self.get_children()
+ try:
+ for child in children[:]:
+ child.walk(visitor)
+ except SkipSiblings:
+ pass
+
+ def walkabout(self, visitor):
+ """
+ Perform a tree traversal similarly to `Node.walk()` (which see),
+ except also call ``depart_...`` methods before exiting each node. If
+ there is no ``depart_particular_node`` method for a node of type
+ ``particular_node``, the ``unknown_departure`` method is called.
+
+ Parameter `visitor`: A `NodeVisitor` object, containing ``visit_...``
+ and ``depart_...`` methods for each `Node` subclass encountered.
+ """
+ call_depart = 1
+ name = 'visit_' + self.__class__.__name__
+ method = getattr(visitor, name, visitor.unknown_visit)
+ visitor.document.reporter.debug(name, category='nodes.Node.walkabout')
+ try:
+ try:
+ method(self)
+ except SkipNode:
+ return
+ except SkipDeparture:
+ call_depart = 0
+ children = self.get_children()
+ try:
+ for child in children[:]:
+ child.walkabout(visitor)
+ except SkipSiblings:
+ pass
+ except SkipChildren:
+ pass
+ if call_depart:
+ name = 'depart_' + self.__class__.__name__
+ method = getattr(visitor, name, visitor.unknown_departure)
+ visitor.document.reporter.debug(
+ name, category='nodes.Node.walkabout')
+ method(self)
+
+
+class Text(Node, UserString):
+
+ """
+ Instances are terminal nodes (leaves) containing text only; no child
+ nodes or attributes. Initialize by passing a string to the constructor.
+ Access the text itself with the `astext` method.
+ """
+
+ tagname = '#text'
+
+ def __init__(self, data, rawsource=''):
+ UserString.__init__(self, data)
+
+ self.rawsource = rawsource
+ """The raw text from which this element was constructed."""
+
+ def __repr__(self):
+ data = repr(self.data)
+ if len(data) > 70:
+ 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:
+ data = repr(self.data[:16] + ' ...')
+ return '<%s: %s>' % (self.tagname, data)
+
+ def _dom_node(self, domroot):
+ return domroot.createTextNode(self.data)
+
+ def astext(self):
+ return self.data
+
+ def copy(self):
+ return self.__class__(self.data)
+
+ def pformat(self, indent=' ', level=0):
+ result = []
+ indent = indent * level
+ for line in self.data.splitlines():
+ result.append(indent + line + '\n')
+ return ''.join(result)
+
+ def get_children(self):
+ """Text nodes have no children. Return []."""
+ return []
+
+
+class Element(Node):
+
+ """
+ `Element` is the superclass to all specific elements.
+
+ Elements contain attributes and child nodes. Elements emulate
+ dictionaries for attributes, indexing by attribute name (a string). To
+ set the attribute 'att' to 'value', do::
+
+ element['att'] = 'value'
+
+ Elements also emulate lists for child nodes (element nodes and/or text
+ nodes), indexing by integer. To get the first child node, use::
+
+ element[0]
+
+ Elements may be constructed using the ``+=`` operator. To add one new
+ child node to element, do::
+
+ element += node
+
+ This is equivalent to ``element.append(node)``.
+
+ To add a list of multiple child nodes at once, use the same ``+=``
+ operator::
+
+ element += [node1, node2]
+
+ This is equivalent to ``element.extend([node1, node2])``.
+ """
+
+ tagname = None
+ """The element generic identifier. If None, it is set as an instance
+ attribute to the name of the class."""
+
+ child_text_separator = '\n\n'
+ """Separator for child nodes, used by `astext()` method."""
+
+ def __init__(self, rawsource='', *children, **attributes):
+ self.rawsource = rawsource
+ """The raw text from which this element was constructed."""
+
+ self.children = []
+ """List of child nodes (elements and/or `Text`)."""
+
+ self.extend(children) # maintain parent info
+
+ self.attributes = {}
+ """Dictionary of attribute {name: value}."""
+
+ for att, value in attributes.items():
+ self.attributes[att.lower()] = value
+
+ if self.tagname is None:
+ self.tagname = self.__class__.__name__
+
+ def _dom_node(self, domroot):
+ element = domroot.createElement(self.tagname)
+ for attribute, value in self.attributes.items():
+ 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
+
+ def __repr__(self):
+ data = ''
+ for c in self.children:
+ data += c.shortrepr()
+ if len(data) > 60:
+ data = data[:56] + ' ...'
+ break
+ if self.hasattr('name'):
+ return '<%s "%s": %s>' % (self.__class__.__name__,
+ self.attributes['name'], data)
+ else:
+ return '<%s: %s>' % (self.__class__.__name__, data)
+
+ def shortrepr(self):
+ if self.hasattr('name'):
+ return '<%s "%s"...>' % (self.__class__.__name__,
+ self.attributes['name'])
+ else:
+ return '<%s...>' % self.tagname
+
+ def __str__(self):
+ return self.__unicode__().encode('raw_unicode_escape')
+
+ def __unicode__(self):
+ if self.children:
+ return u'%s%s%s' % (self.starttag(),
+ ''.join([str(c) for c in self.children]),
+ self.endtag())
+ else:
+ return self.emptytag()
+
+ def starttag(self):
+ parts = [self.tagname]
+ for name, value in self.attlist():
+ if value is None: # boolean attribute
+ parts.append(name)
+ elif isinstance(value, ListType):
+ values = ['%s' % v for v in value]
+ parts.append('%s="%s"' % (name, ' '.join(values)))
+ else:
+ parts.append('%s="%s"' % (name, value))
+ return '<%s>' % ' '.join(parts)
+
+ def endtag(self):
+ return '</%s>' % self.tagname
+
+ def emptytag(self):
+ return u'<%s/>' % ' '.join([self.tagname] +
+ ['%s="%s"' % (n, v)
+ for n, v in self.attlist()])
+
+ def __len__(self):
+ return len(self.children)
+
+ def __getitem__(self, key):
+ if isinstance(key, UnicodeType) or isinstance(key, StringType):
+ return self.attributes[key]
+ elif isinstance(key, IntType):
+ return self.children[key]
+ elif isinstance(key, SliceType):
+ assert key.step in (None, 1), 'cannot handle slice with stride'
+ return self.children[key.start:key.stop]
+ else:
+ raise TypeError, ('element index must be an integer, a slice, or '
+ 'an attribute name string')
+
+ def __setitem__(self, key, item):
+ if isinstance(key, UnicodeType) or isinstance(key, StringType):
+ self.attributes[str(key)] = item
+ elif isinstance(key, IntType):
+ self.setup_child(item)
+ self.children[key] = item
+ elif isinstance(key, SliceType):
+ assert key.step in (None, 1), 'cannot handle slice with stride'
+ for node in item:
+ self.setup_child(node)
+ self.children[key.start:key.stop] = item
+ else:
+ raise TypeError, ('element index must be an integer, a slice, or '
+ 'an attribute name string')
+
+ def __delitem__(self, key):
+ if isinstance(key, UnicodeType) or isinstance(key, StringType):
+ del self.attributes[key]
+ elif isinstance(key, IntType):
+ del self.children[key]
+ elif isinstance(key, SliceType):
+ assert key.step in (None, 1), 'cannot handle slice with stride'
+ del self.children[key.start:key.stop]
+ else:
+ raise TypeError, ('element index must be an integer, a simple '
+ 'slice, or an attribute name string')
+
+ def __add__(self, other):
+ return self.children + other
+
+ def __radd__(self, other):
+ return other + self.children
+
+ def __iadd__(self, other):
+ """Append a node or a list of nodes to `self.children`."""
+ if isinstance(other, Node):
+ self.setup_child(other)
+ self.children.append(other)
+ elif other is not None:
+ for node in other:
+ self.setup_child(node)
+ self.children.extend(other)
+ return self
+
+ def astext(self):
+ return self.child_text_separator.join(
+ [child.astext() for child in self.children])
+
+ def attlist(self):
+ attlist = self.attributes.items()
+ attlist.sort()
+ return attlist
+
+ def get(self, key, failobj=None):
+ return self.attributes.get(key, failobj)
+
+ def hasattr(self, attr):
+ return self.attributes.has_key(attr)
+
+ def delattr(self, attr):
+ if self.attributes.has_key(attr):
+ del self.attributes[attr]
+
+ def setdefault(self, key, failobj=None):
+ return self.attributes.setdefault(key, failobj)
+
+ has_key = hasattr
+
+ def append(self, item):
+ self.setup_child(item)
+ self.children.append(item)
+
+ def extend(self, item):
+ for node in item:
+ self.setup_child(node)
+ self.children.extend(item)
+
+ def insert(self, index, item):
+ if isinstance(item, Node):
+ self.setup_child(item)
+ self.children.insert(index, item)
+ elif item is not None:
+ self[index:index] = item
+
+ def pop(self, i=-1):
+ return self.children.pop(i)
+
+ def remove(self, item):
+ self.children.remove(item)
+
+ def index(self, item):
+ return self.children.index(item)
+
+ def replace(self, old, new):
+ """Replace one child `Node` with another child or children."""
+ index = self.index(old)
+ if isinstance(new, Node):
+ self.setup_child(new)
+ self[index] = new
+ elif new is not None:
+ self[index:index+1] = new
+
+ def first_child_matching_class(self, childclass, start=0, end=sys.maxint):
+ """
+ Return the index of the first child whose class exactly matches.
+
+ Parameters:
+
+ - `childclass`: A `Node` subclass to search for, or a tuple of `Node`
+ classes. If a tuple, any of the classes may match.
+ - `start`: Initial index to check.
+ - `end`: Initial index to *not* check.
+ """
+ if not isinstance(childclass, TupleType):
+ childclass = (childclass,)
+ for index in range(start, min(len(self), end)):
+ for c in childclass:
+ if isinstance(self[index], c):
+ return index
+ return None
+
+ def first_child_not_matching_class(self, childclass, start=0,
+ end=sys.maxint):
+ """
+ Return the index of the first child whose class does *not* match.
+
+ Parameters:
+
+ - `childclass`: A `Node` subclass to skip, or a tuple of `Node`
+ classes. If a tuple, none of the classes may match.
+ - `start`: Initial index to check.
+ - `end`: Initial index to *not* check.
+ """
+ if not isinstance(childclass, TupleType):
+ childclass = (childclass,)
+ for index in range(start, min(len(self), end)):
+ match = 0
+ for c in childclass:
+ if isinstance(self.children[index], c):
+ match = 1
+ break
+ if not match:
+ return index
+ return None
+
+ def pformat(self, indent=' ', level=0):
+ return ''.join(['%s%s\n' % (indent * level, self.starttag())] +
+ [child.pformat(indent, level+1)
+ for child in self.children])
+
+ def get_children(self):
+ """Return this element's children."""
+ return self.children
+
+ def copy(self):
+ return self.__class__(**self.attributes)
+
+ def set_class(self, name):
+ """Add a new name to the "class" attribute."""
+ self.attributes['class'] = (self.attributes.get('class', '') + ' '
+ + name.lower()).strip()
+
+
+class TextElement(Element):
+
+ """
+ An element which directly contains text.
+
+ Its children are all `Text` or `TextElement` subclass nodes. You can
+ check whether an element's context is inline simply by checking whether
+ its immediate parent is a `TextElement` instance (including subclasses).
+ This is handy for nodes like `image` that can appear both inline and as
+ standalone body elements.
+ """
+
+ child_text_separator = ''
+ """Separator for child nodes, used by `astext()` method."""
+
+ def __init__(self, rawsource='', text='', *children, **attributes):
+ if text != '':
+ textnode = Text(text)
+ Element.__init__(self, rawsource, textnode, *children,
+ **attributes)
+ else:
+ Element.__init__(self, rawsource, *children, **attributes)
+
+
+class FixedTextElement(TextElement):
+
+ """An element which directly contains preformatted text."""
+
+ def __init__(self, rawsource='', text='', *children, **attributes):
+ TextElement.__init__(self, rawsource, text, *children, **attributes)
+ self.attributes['xml:space'] = 'preserve'
+
+
+# ========
+# Mixins
+# ========
+
+class Resolvable:
+
+ resolved = 0
+
+
+class BackLinkable:
+
+ def add_backref(self, refid):
+ self.setdefault('backrefs', []).append(refid)
+
+
+# ====================
+# Element Categories
+# ====================
+
+class Root: pass
+
+class Titular: pass
+
+class PreDecorative:
+ """Category of Node which may occur before Decorative Nodes."""
+
+class PreBibliographic(PreDecorative):
+ """Category of Node which may occur before Bibliographic Nodes."""
+
+class Bibliographic(PreDecorative): pass
+
+class Decorative: pass
+
+class Structural: pass
+
+class Body: pass
+
+class General(Body): pass
+
+class Sequential(Body): pass
+
+class Admonition(Body): pass
+
+class Special(Body):
+ """Special internal body elements."""
+
+class Invisible:
+ """Internal elements that don't appear in output."""
+
+class Part: pass
+
+class Inline: pass
+
+class Referential(Resolvable): pass
+
+class Targetable(Resolvable):
+
+ referenced = 0
+
+class Labeled:
+ """Contains a `label` as its first element."""
+
+
+# ==============
+# Root Element
+# ==============
+
+class document(Root, Structural, Element):
+
+ def __init__(self, settings, reporter, *args, **kwargs):
+ Element.__init__(self, *args, **kwargs)
+
+ self.current_source = None
+ """Path to or description of the input source being processed."""
+
+ self.current_line = None
+ """Line number (1-based) of `current_source`."""
+
+ self.settings = settings
+ """Runtime settings data record."""
+
+ self.reporter = reporter
+ """System message generator."""
+
+ self.external_targets = []
+ """List of external target nodes."""
+
+ self.internal_targets = []
+ """List of internal target nodes."""
+
+ self.indirect_targets = []
+ """List of indirect target nodes."""
+
+ 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."""
+
+ self.refids = {}
+ """Mapping of ids to lists of referencing nodes."""
+
+ self.nameids = {}
+ """Mapping of names to unique id's."""
+
+ self.nametypes = {}
+ """Mapping of names to hyperlink type (boolean: True => explicit,
+ False => implicit."""
+
+ self.ids = {}
+ """Mapping of ids to nodes."""
+
+ self.substitution_refs = {}
+ """Mapping of substitution names to lists of substitution_reference
+ nodes."""
+
+ self.footnote_refs = {}
+ """Mapping of footnote labels to lists of footnote_reference nodes."""
+
+ self.citation_refs = {}
+ """Mapping of citation labels to lists of citation_reference nodes."""
+
+ self.anonymous_targets = []
+ """List of anonymous target nodes."""
+
+ self.anonymous_refs = []
+ """List of anonymous reference nodes."""
+
+ self.autofootnotes = []
+ """List of auto-numbered footnote nodes."""
+
+ self.autofootnote_refs = []
+ """List of auto-numbered footnote_reference nodes."""
+
+ self.symbol_footnotes = []
+ """List of symbol footnote nodes."""
+
+ self.symbol_footnote_refs = []
+ """List of symbol footnote_reference nodes."""
+
+ self.footnotes = []
+ """List of manually-numbered footnote nodes."""
+
+ self.citations = []
+ """List of citation nodes."""
+
+ self.autofootnote_start = 1
+ """Initial auto-numbered footnote number."""
+
+ self.symbol_footnote_start = 0
+ """Initial symbol footnote symbol index."""
+
+ self.id_start = 1
+ """Initial ID number."""
+
+ self.parse_messages = []
+ """System messages generated while parsing."""
+
+ self.transform_messages = []
+ """System messages generated while applying transforms."""
+
+ import docutils.transforms
+ self.transformer = docutils.transforms.Transformer(self)
+ """Storage for transforms to be applied to this document."""
+
+ self.document = self
+
+ def asdom(self, dom=xml.dom.minidom):
+ """Return a DOM representation of this document."""
+ domroot = dom.Document()
+ domroot.appendChild(self._dom_node(domroot))
+ return domroot
+
+ def set_id(self, node, msgnode=None):
+ if node.has_key('id'):
+ id = node['id']
+ if self.ids.has_key(id) and self.ids[id] is not node:
+ msg = self.reporter.severe('Duplicate ID: "%s".' % id)
+ if msgnode != None:
+ msgnode += msg
+ else:
+ if node.has_key('name'):
+ id = make_id(node['name'])
+ else:
+ id = ''
+ while not id or self.ids.has_key(id):
+ id = 'id%s' % self.id_start
+ self.id_start += 1
+ node['id'] = id
+ self.ids[id] = node
+ return id
+
+ def set_name_id_map(self, node, id, msgnode=None, explicit=None):
+ """
+ `self.nameids` maps names to IDs, while `self.nametypes` maps names to
+ booleans representing hyperlink type (True==explicit,
+ False==implicit). This method updates the mappings.
+
+ The following state transition table shows how `self.nameids` ("ids")
+ and `self.nametypes` ("types") change with new input (a call to this
+ method), and what actions are performed:
+
+ ==== ===== ======== ======== ======= ==== ===== =====
+ Old State Input Action New State Notes
+ ----------- -------- ----------------- ----------- -----
+ ids types new type sys.msg. dupname ids types
+ ==== ===== ======== ======== ======= ==== ===== =====
+ -- -- explicit -- -- new True
+ -- -- implicit -- -- new False
+ None False explicit -- -- new True
+ old False explicit implicit old new True
+ None True explicit explicit new None True
+ old True explicit explicit new,old None True [#]_
+ None False implicit implicit new None False
+ old False implicit implicit new,old None False
+ None True implicit implicit new None True
+ old True implicit implicit new old True
+ ==== ===== ======== ======== ======= ==== ===== =====
+
+ .. [#] Do not clear the name-to-id map or invalidate the old target if
+ both old and new targets are external and refer to identical URIs.
+ The new target is invalidated regardless.
+ """
+ if node.has_key('name'):
+ name = node['name']
+ if self.nameids.has_key(name):
+ self.set_duplicate_name_id(node, id, name, msgnode, explicit)
+ else:
+ self.nameids[name] = id
+ self.nametypes[name] = explicit
+
+ def set_duplicate_name_id(self, node, id, name, msgnode, explicit):
+ old_id = self.nameids[name]
+ old_explicit = self.nametypes[name]
+ self.nametypes[name] = old_explicit or explicit
+ if explicit:
+ if old_explicit:
+ level = 2
+ if old_id is not None:
+ old_node = self.ids[old_id]
+ if node.has_key('refuri'):
+ refuri = node['refuri']
+ if old_node.has_key('name') \
+ and old_node.has_key('refuri') \
+ and old_node['refuri'] == refuri:
+ level = 1 # just inform if refuri's identical
+ if level > 1:
+ dupname(old_node)
+ self.nameids[name] = None
+ msg = self.reporter.system_message(
+ level, 'Duplicate explicit target name: "%s".' % name,
+ backrefs=[id], base_node=node)
+ if msgnode != None:
+ msgnode += msg
+ dupname(node)
+ else:
+ self.nameids[name] = id
+ if old_id is not None:
+ old_node = self.ids[old_id]
+ dupname(old_node)
+ else:
+ if old_id is not None and not old_explicit:
+ self.nameids[name] = None
+ old_node = self.ids[old_id]
+ dupname(old_node)
+ dupname(node)
+ if not explicit or (not old_explicit and old_id is not None):
+ msg = self.reporter.info(
+ 'Duplicate implicit target name: "%s".' % name,
+ backrefs=[id], base_node=node)
+ if msgnode != None:
+ msgnode += msg
+
+ def has_name(self, name):
+ return self.nameids.has_key(name)
+
+ def note_implicit_target(self, target, msgnode=None):
+ id = self.set_id(target, msgnode)
+ self.set_name_id_map(target, id, msgnode, explicit=None)
+
+ def note_explicit_target(self, target, msgnode=None):
+ id = self.set_id(target, msgnode)
+ self.set_name_id_map(target, id, msgnode, explicit=1)
+
+ def note_refname(self, node):
+ self.refnames.setdefault(node['refname'], []).append(node)
+
+ def note_refid(self, node):
+ self.refids.setdefault(node['refid'], []).append(node)
+
+ def note_external_target(self, target):
+ self.external_targets.append(target)
+
+ def note_internal_target(self, target):
+ self.internal_targets.append(target)
+
+ def note_indirect_target(self, target):
+ self.indirect_targets.append(target)
+ if target.has_key('name'):
+ self.note_refname(target)
+
+ def note_anonymous_target(self, target):
+ self.set_id(target)
+ self.anonymous_targets.append(target)
+
+ def note_anonymous_ref(self, ref):
+ self.anonymous_refs.append(ref)
+
+ def note_autofootnote(self, footnote):
+ self.set_id(footnote)
+ self.autofootnotes.append(footnote)
+
+ def note_autofootnote_ref(self, ref):
+ self.set_id(ref)
+ self.autofootnote_refs.append(ref)
+
+ def note_symbol_footnote(self, footnote):
+ self.set_id(footnote)
+ self.symbol_footnotes.append(footnote)
+
+ def note_symbol_footnote_ref(self, ref):
+ self.set_id(ref)
+ self.symbol_footnote_refs.append(ref)
+
+ def note_footnote(self, footnote):
+ self.set_id(footnote)
+ self.footnotes.append(footnote)
+
+ def note_footnote_ref(self, ref):
+ self.set_id(ref)
+ self.footnote_refs.setdefault(ref['refname'], []).append(ref)
+ self.note_refname(ref)
+
+ def note_citation(self, citation):
+ self.citations.append(citation)
+
+ def note_citation_ref(self, ref):
+ self.set_id(ref)
+ self.citation_refs.setdefault(ref['refname'], []).append(ref)
+ self.note_refname(ref)
+
+ 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,
+ base_node=subdef)
+ if msgnode != None:
+ msgnode += msg
+ oldnode = self.substitution_defs[name]
+ dupname(oldnode)
+ # 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, 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)
+
+ def note_parse_message(self, message):
+ self.parse_messages.append(message)
+
+ def note_transform_message(self, message):
+ self.transform_messages.append(message)
+
+ def note_source(self, source, offset):
+ self.current_source = source
+ if offset is None:
+ self.current_line = offset
+ else:
+ self.current_line = offset + 1
+
+ def copy(self):
+ return self.__class__(self.settings, self.reporter,
+ **self.attributes)
+
+
+# ================
+# Title Elements
+# ================
+
+class title(Titular, PreBibliographic, TextElement): pass
+class subtitle(Titular, PreBibliographic, TextElement): pass
+class rubric(Titular, TextElement): pass
+
+
+# ========================
+# Bibliographic Elements
+# ========================
+
+class docinfo(Bibliographic, Element): pass
+class author(Bibliographic, TextElement): pass
+class authors(Bibliographic, Element): pass
+class organization(Bibliographic, TextElement): pass
+class address(Bibliographic, FixedTextElement): pass
+class contact(Bibliographic, TextElement): pass
+class version(Bibliographic, TextElement): pass
+class revision(Bibliographic, TextElement): pass
+class status(Bibliographic, TextElement): pass
+class date(Bibliographic, TextElement): pass
+class copyright(Bibliographic, TextElement): pass
+
+
+# =====================
+# Decorative Elements
+# =====================
+
+class decoration(Decorative, Element): pass
+class header(Decorative, Element): pass
+class footer(Decorative, Element): pass
+
+
+# =====================
+# Structural Elements
+# =====================
+
+class section(Structural, Element): pass
+
+
+class topic(Structural, Element):
+
+ """
+ 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
+ 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, 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.
+ """
+
+
+class transition(Structural, Element): pass
+
+
+# ===============
+# Body Elements
+# ===============
+
+class paragraph(General, TextElement): pass
+class bullet_list(Sequential, Element): pass
+class enumerated_list(Sequential, Element): pass
+class list_item(Part, Element): pass
+class definition_list(Sequential, Element): pass
+class definition_list_item(Part, Element): pass
+class term(Part, TextElement): pass
+class classifier(Part, TextElement): pass
+class definition(Part, Element): pass
+class field_list(Sequential, Element): pass
+class field(Part, Element): pass
+class field_name(Part, TextElement): pass
+class field_body(Part, Element): pass
+
+
+class option(Part, Element):
+
+ child_text_separator = ''
+
+
+class option_argument(Part, TextElement):
+
+ def astext(self):
+ return self.get('delimiter', ' ') + TextElement.astext(self)
+
+
+class option_group(Part, Element):
+
+ child_text_separator = ', '
+
+
+class option_list(Sequential, Element): pass
+
+
+class option_list_item(Part, Element):
+
+ child_text_separator = ' '
+
+
+class option_string(Part, TextElement): pass
+class description(Part, Element): pass
+class literal_block(General, FixedTextElement): pass
+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
+class error(Admonition, Element): pass
+class important(Admonition, Element): pass
+class note(Admonition, Element): pass
+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
+class footnote(General, Element, Labeled, BackLinkable): pass
+class citation(General, Element, Labeled, BackLinkable): pass
+class label(Part, TextElement): pass
+class figure(General, Element): pass
+class caption(Part, TextElement): pass
+class legend(Part, Element): pass
+class table(General, Element): pass
+class tgroup(Part, Element): pass
+class colspec(Part, Element): pass
+class thead(Part, Element): pass
+class tbody(Part, Element): pass
+class row(Part, Element): pass
+class entry(Part, Element): pass
+
+
+class system_message(Special, PreBibliographic, Element, BackLinkable):
+
+ def __init__(self, message=None, *children, **attributes):
+ if message:
+ p = paragraph('', message)
+ children = (p,) + children
+ try:
+ Element.__init__(self, '', *children, **attributes)
+ except:
+ print 'system_message: children=%r' % (children,)
+ raise
+
+ def astext(self):
+ line = self.get('line', '')
+ return u'%s:%s: (%s/%s) %s' % (self['source'], line, self['type'],
+ self['level'], Element.astext(self))
+
+
+class pending(Special, Invisible, PreBibliographic, Element):
+
+ """
+ The "pending" element is used to encapsulate a pending operation: the
+ operation (transform), the point at which to apply it, and any data it
+ requires. Only the pending operation's location within the document is
+ stored in the public document tree (by the "pending" object itself); the
+ operation and its data are stored in the "pending" object's internal
+ instance attributes.
+
+ For example, say you want a table of contents in your reStructuredText
+ document. The easiest way to specify where to put it is from within the
+ document, with a directive::
+
+ .. contents::
+
+ But the "contents" directive can't do its work until the entire document
+ has been parsed and possibly transformed to some extent. So the directive
+ code leaves a placeholder behind that will trigger the second phase of the
+ its processing, something like this::
+
+ <pending ...public attributes...> + internal attributes
+
+ Use `document.note_pending()` so that the
+ `docutils.transforms.Transformer` stage of processing can run all pending
+ transforms.
+ """
+
+ def __init__(self, transform, details=None,
+ rawsource='', *children, **attributes):
+ Element.__init__(self, rawsource, *children, **attributes)
+
+ self.transform = transform
+ """The `docutils.transforms.Transform` class implementing the pending
+ operation."""
+
+ self.details = details or {}
+ """Detail data (dictionary) required by the pending operation."""
+
+ def pformat(self, indent=' ', level=0):
+ internals = [
+ '.. internal attributes:',
+ ' .transform: %s.%s' % (self.transform.__module__,
+ self.transform.__name__),
+ ' .details:']
+ details = self.details.items()
+ details.sort()
+ for key, value in details:
+ if isinstance(value, Node):
+ internals.append('%7s%s:' % ('', key))
+ internals.extend(['%9s%s' % ('', line)
+ for line in value.pformat().splitlines()])
+ elif value and isinstance(value, ListType) \
+ and isinstance(value[0], Node):
+ internals.append('%7s%s:' % ('', key))
+ for v in value:
+ internals.extend(['%9s%s' % ('', line)
+ for line in v.pformat().splitlines()])
+ else:
+ internals.append('%7s%s: %r' % ('', key, value))
+ return (Element.pformat(self, indent, level)
+ + ''.join([(' %s%s\n' % (indent * level, line))
+ for line in internals]))
+
+ def copy(self):
+ return self.__class__(self.transform, self.details, self.rawsource,
+ **self.attribuates)
+
+
+class raw(Special, Inline, PreBibliographic, FixedTextElement):
+
+ """
+ Raw data that is to be passed untouched to the Writer.
+ """
+
+ pass
+
+
+# =================
+# Inline Elements
+# =================
+
+class emphasis(Inline, TextElement): pass
+class strong(Inline, TextElement): pass
+class literal(Inline, TextElement): pass
+class reference(Inline, Referential, TextElement): pass
+class footnote_reference(Inline, Referential, TextElement): pass
+class citation_reference(Inline, Referential, TextElement): pass
+class substitution_reference(Inline, TextElement): pass
+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):
+
+ def astext(self):
+ return self.get('alt', '')
+
+
+class inline(Inline, TextElement): pass
+class problematic(Inline, TextElement): pass
+class generated(Inline, TextElement): pass
+
+
+# ========================================
+# Auxiliary Classes, Functions, and Data
+# ========================================
+
+node_class_names = """
+ Text
+ abbreviation acronym address admonition attention attribution author
+ authors
+ block_quote bullet_list
+ caption caution citation citation_reference classifier colspec comment
+ contact copyright
+ danger date decoration definition definition_list definition_list_item
+ description docinfo doctest_block document
+ emphasis entry enumerated_list error
+ field field_body field_list field_name figure footer
+ footnote footnote_reference
+ generated
+ header hint
+ 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 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
+ warning""".split()
+"""A list of names of all concrete Node subclasses."""
+
+
+class NodeVisitor:
+
+ """
+ "Visitor" pattern [GoF95]_ abstract superclass implementation for document
+ tree traversals.
+
+ Each node class has corresponding methods, doing nothing by default;
+ override individual methods for specific and useful behaviour. The
+ "``visit_`` + node class name" method is called by `Node.walk()` upon
+ entering a node. `Node.walkabout()` also calls the "``depart_`` + node
+ class name" method before exiting a node.
+
+ This is a base class for visitors whose ``visit_...`` & ``depart_...``
+ methods should be implemented for *all* node types encountered (such as
+ for `docutils.writers.Writer` subclasses). Unimplemented methods will
+ raise exceptions.
+
+ For sparse traversals, where only certain node types are of interest,
+ subclass `SparseNodeVisitor` instead. When (mostly or entirely) uniform
+ processing is desired, subclass `GenericNodeVisitor`.
+
+ .. [GoF95] Gamma, Helm, Johnson, Vlissides. *Design Patterns: Elements of
+ Reusable Object-Oriented Software*. Addison-Wesley, Reading, MA, USA,
+ 1995.
+ """
+
+ def __init__(self, document):
+ self.document = document
+
+ def unknown_visit(self, node):
+ """
+ Called when entering unknown `Node` types.
+
+ Raise an exception unless overridden.
+ """
+ raise NotImplementedError('visiting unknown node type: %s'
+ % node.__class__.__name__)
+
+ def unknown_departure(self, node):
+ """
+ Called before exiting unknown `Node` types.
+
+ Raise exception unless overridden.
+ """
+ raise NotImplementedError('departing unknown node type: %s'
+ % node.__class__.__name__)
+
+
+class SparseNodeVisitor(NodeVisitor):
+
+ """
+ Base class for sparse traversals, where only certain node types are of
+ interest. When ``visit_...`` & ``depart_...`` methods should be
+ implemented for *all* node types (such as for `docutils.writers.Writer`
+ subclasses), subclass `NodeVisitor` instead.
+ """
+
+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):
+
+ """
+ Generic "Visitor" abstract superclass, for simple traversals.
+
+ Unless overridden, each ``visit_...`` method calls `default_visit()`, and
+ each ``depart_...`` method (when using `Node.walkabout()`) calls
+ `default_departure()`. `default_visit()` (and `default_departure()`) must
+ be overridden in subclasses.
+
+ Define fully generic visitors by overriding `default_visit()` (and
+ `default_departure()`) only. Define semi-generic visitors by overriding
+ individual ``visit_...()`` (and ``depart_...()``) methods also.
+
+ `NodeVisitor.unknown_visit()` (`NodeVisitor.unknown_departure()`) should
+ be overridden for default behavior.
+ """
+
+ def default_visit(self, node):
+ """Override for generic, uniform traversals."""
+ raise NotImplementedError
+
+ def default_departure(self, node):
+ """Override for generic, uniform traversals."""
+ raise NotImplementedError
+
+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):
+
+ """
+ Make a complete copy of a tree or branch, including element attributes.
+ """
+
+ def __init__(self, document):
+ GenericNodeVisitor.__init__(self, document)
+ self.parent_stack = []
+ self.parent = []
+
+ def get_tree_copy(self):
+ return self.parent[0]
+
+ def default_visit(self, node):
+ """Copy the current node, and make it the new acting parent."""
+ newnode = node.copy()
+ self.parent.append(newnode)
+ self.parent_stack.append(self.parent)
+ self.parent = newnode
+
+ def default_departure(self, node):
+ """Restore the previous acting parent."""
+ self.parent = self.parent_stack.pop()
+
+
+class TreePruningException(Exception):
+
+ """
+ Base class for `NodeVisitor`-related tree pruning exceptions.
+
+ Raise subclasses from within ``visit_...`` or ``depart_...`` methods
+ called from `Node.walk()` and `Node.walkabout()` tree traversals to prune
+ the tree traversed.
+ """
+
+ pass
+
+
+class SkipChildren(TreePruningException):
+
+ """
+ Do not visit any children of the current node. The current node's
+ siblings and ``depart_...`` method are not affected.
+ """
+
+ pass
+
+
+class SkipSiblings(TreePruningException):
+
+ """
+ Do not visit any more siblings (to the right) of the current node. The
+ current node's children and its ``depart_...`` method are not affected.
+ """
+
+ pass
+
+
+class SkipNode(TreePruningException):
+
+ """
+ Do not visit the current node's children, and do not call the current
+ node's ``depart_...`` method.
+ """
+
+ pass
+
+
+class SkipDeparture(TreePruningException):
+
+ """
+ Do not call the current node's ``depart_...`` method. The current node's
+ children and siblings are not affected.
+ """
+
+ pass
+
+
+class NodeFound(TreePruningException):
+
+ """
+ Raise to indicate that the target of a search has been found. This
+ exception must be caught by the client; it is not caught by the traversal
+ code.
+ """
+
+ pass
+
+
+def make_id(string):
+ """
+ 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.
+
+ - The `HTML 4.01 spec`_ defines identifiers based on SGML tokens:
+
+ ID and NAME tokens must begin with a letter ([A-Za-z]) and may be
+ followed by any number of letters, digits ([0-9]), hyphens ("-"),
+ underscores ("_"), colons (":"), and periods (".").
+
+ - However the `CSS1 spec`_ defines identifiers based on the "name" token,
+ a tighter interpretation ("flex" tokenizer notation; "latin1" and
+ "escape" 8-bit characters have been replaced with entities)::
+
+ unicode \\[0-9a-f]{1,4}
+ latin1 [¡-ÿ]
+ escape {unicode}|\\[ -~¡-ÿ]
+ nmchar [-a-z0-9]|{latin1}|{escape}
+ name {nmchar}+
+
+ The CSS1 "nmchar" rule does not include underscores ("_"), colons (":"),
+ or periods ("."), therefore "class" and "id" attributes should not contain
+ 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.
+
+ .. _HTML 4.01 spec: http://www.w3.org/TR/html401
+ .. _CSS1 spec: http://www.w3.org/TR/REC-CSS1
+ """
+ id = _non_id_chars.sub('-', ' '.join(string.lower().split()))
+ id = _non_id_at_ends.sub('', id)
+ return str(id)
+
+_non_id_chars = re.compile('[^a-z0-9]+')
+_non_id_at_ends = re.compile('^[-0-9]+|-+$')
+
+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/roman.py 1.2.10.1 => 1.2.10.2 ===
--- /dev/null Sun Nov 30 11:05:52 2003
+++ Zope/lib/python/docutils/roman.py Sun Nov 30 11:05:22 2003
@@ -0,0 +1,81 @@
+"""Convert to and from Roman numerals"""
+
+__author__ = "Mark Pilgrim (f8dy at diveintopython.org)"
+__version__ = "1.4"
+__date__ = "8 August 2001"
+__copyright__ = """Copyright (c) 2001 Mark Pilgrim
+
+This program is part of "Dive Into Python", a free Python tutorial for
+experienced programmers. Visit http://diveintopython.org/ for the
+latest version.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the Python 2.1.1 license, available at
+http://www.python.org/2.1.1/license.html
+"""
+
+import re
+
+#Define exceptions
+class RomanError(Exception): pass
+class OutOfRangeError(RomanError): pass
+class NotIntegerError(RomanError): pass
+class InvalidRomanNumeralError(RomanError): pass
+
+#Define digit mapping
+romanNumeralMap = (('M', 1000),
+ ('CM', 900),
+ ('D', 500),
+ ('CD', 400),
+ ('C', 100),
+ ('XC', 90),
+ ('L', 50),
+ ('XL', 40),
+ ('X', 10),
+ ('IX', 9),
+ ('V', 5),
+ ('IV', 4),
+ ('I', 1))
+
+def toRoman(n):
+ """convert integer to Roman numeral"""
+ if not (0 < n < 5000):
+ raise OutOfRangeError, "number out of range (must be 1..4999)"
+ if int(n) <> n:
+ raise NotIntegerError, "decimals can not be converted"
+
+ result = ""
+ for numeral, integer in romanNumeralMap:
+ while n >= integer:
+ result += numeral
+ n -= integer
+ return result
+
+#Define pattern to detect valid Roman numerals
+romanNumeralPattern = re.compile("""
+ ^ # beginning of string
+ M{0,4} # thousands - 0 to 4 M's
+ (CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's),
+ # or 500-800 (D, followed by 0 to 3 C's)
+ (XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's),
+ # or 50-80 (L, followed by 0 to 3 X's)
+ (IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's),
+ # or 5-8 (V, followed by 0 to 3 I's)
+ $ # end of string
+ """ ,re.VERBOSE)
+
+def fromRoman(s):
+ """convert Roman numeral to integer"""
+ if not s:
+ raise InvalidRomanNumeralError, 'Input can not be blank'
+ if not romanNumeralPattern.search(s):
+ raise InvalidRomanNumeralError, 'Invalid Roman numeral: %s' % s
+
+ result = 0
+ index = 0
+ for numeral, integer in romanNumeralMap:
+ while s[index:index+len(numeral)] == numeral:
+ result += integer
+ index += len(numeral)
+ return result
+
=== Zope/lib/python/docutils/statemachine.py 1.2.10.2 => 1.2.10.3 ===
--- /dev/null Sun Nov 30 11:05:52 2003
+++ Zope/lib/python/docutils/statemachine.py Sun Nov 30 11:05:22 2003
@@ -0,0 +1,1462 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision$
+# Date: $Date$
+# Copyright: This module has been placed in the public domain.
+
+"""
+A finite state machine specialized for regular-expression-based text filters,
+this module defines the following classes:
+
+- `StateMachine`, a state machine
+- `State`, a state superclass
+- `StateMachineWS`, a whitespace-sensitive version of `StateMachine`
+- `StateWS`, a state superclass for use with `StateMachineWS`
+- `SearchStateMachine`, uses `re.search()` instead of `re.match()`
+- `SearchStateMachineWS`, uses `re.search()` instead of `re.match()`
+- `ViewList`, extends standard Python lists.
+- `StringList`, string-specific ViewList.
+
+Exception classes:
+
+- `StateMachineError`
+- `UnknownStateError`
+- `DuplicateStateError`
+- `UnknownTransitionError`
+- `DuplicateTransitionError`
+- `TransitionPatternNotFound`
+- `TransitionMethodNotFound`
+- `UnexpectedIndentationError`
+- `TransitionCorrection`: Raised to switch to another transition.
+- `StateCorrection`: Raised to switch to another state & transition.
+
+Functions:
+
+- `string2lines()`: split a multi-line string into a list of one-line strings
+
+
+How To Use This Module
+======================
+(See the individual classes, methods, and attributes for details.)
+
+1. Import it: ``import statemachine`` or ``from statemachine import ...``.
+ You will also need to ``import re``.
+
+2. Derive a subclass of `State` (or `StateWS`) for each state in your state
+ machine::
+
+ class MyState(statemachine.State):
+
+ Within the state's class definition:
+
+ a) Include a pattern for each transition, in `State.patterns`::
+
+ patterns = {'atransition': r'pattern', ...}
+
+ b) Include a list of initial transitions to be set up automatically, in
+ `State.initial_transitions`::
+
+ initial_transitions = ['atransition', ...]
+
+ c) Define a method for each transition, with the same name as the
+ transition pattern::
+
+ def atransition(self, match, context, next_state):
+ # do something
+ result = [...] # a list
+ return context, next_state, result
+ # context, next_state may be altered
+
+ Transition methods may raise an `EOFError` to cut processing short.
+
+ d) You may wish to override the `State.bof()` and/or `State.eof()` implicit
+ transition methods, which handle the beginning- and end-of-file.
+
+ e) In order to handle nested processing, you may wish to override the
+ attributes `State.nested_sm` and/or `State.nested_sm_kwargs`.
+
+ If you are using `StateWS` as a base class, in order to handle nested
+ indented blocks, you may wish to:
+
+ - override the attributes `StateWS.indent_sm`,
+ `StateWS.indent_sm_kwargs`, `StateWS.known_indent_sm`, and/or
+ `StateWS.known_indent_sm_kwargs`;
+ - override the `StateWS.blank()` method; and/or
+ - override or extend the `StateWS.indent()`, `StateWS.known_indent()`,
+ and/or `StateWS.firstknown_indent()` methods.
+
+3. Create a state machine object::
+
+ sm = StateMachine(state_classes=[MyState, ...],
+ initial_state='MyState')
+
+4. Obtain the input text, which needs to be converted into a tab-free list of
+ one-line strings. For example, to read text from a file called
+ 'inputfile'::
+
+ input_string = open('inputfile').read()
+ input_lines = statemachine.string2lines(input_string)
+
+5. Run the state machine on the input text and collect the results, a list::
+
+ results = sm.run(input_lines)
+
+6. Remove any lingering circular references::
+
+ sm.unlink()
+"""
+
+__docformat__ = 'restructuredtext'
+
+import sys
+import re
+from types import SliceType as _SliceType
+
+
+class StateMachine:
+
+ """
+ A finite state machine for text filters using regular expressions.
+
+ The input is provided in the form of a list of one-line strings (no
+ newlines). States are subclasses of the `State` class. Transitions consist
+ of regular expression patterns and transition methods, and are defined in
+ each state.
+
+ The state machine is started with the `run()` method, which returns the
+ results of processing in a list.
+ """
+
+ def __init__(self, state_classes, initial_state, debug=0):
+ """
+ Initialize a `StateMachine` object; add state objects.
+
+ Parameters:
+
+ - `state_classes`: a list of `State` (sub)classes.
+ - `initial_state`: a string, the class name of the initial state.
+ - `debug`: a boolean; produce verbose output if true (nonzero).
+ """
+
+ self.input_lines = None
+ """`StringList` of input lines (without newlines).
+ Filled by `self.run()`."""
+
+ self.input_offset = 0
+ """Offset of `self.input_lines` from the beginning of the file."""
+
+ self.line = None
+ """Current input line."""
+
+ self.line_offset = -1
+ """Current input line offset from beginning of `self.input_lines`."""
+
+ self.debug = debug
+ """Debugging mode on/off."""
+
+ self.initial_state = initial_state
+ """The name of the initial state (key to `self.states`)."""
+
+ self.current_state = initial_state
+ """The name of the current state (key to `self.states`)."""
+
+ self.states = {}
+ """Mapping of {state_name: State_object}."""
+
+ self.add_states(state_classes)
+
+ self.observers = []
+ """List of bound methods or functions to call whenever the current
+ line changes. Observers are called with one argument, ``self``.
+ Cleared at the end of `run()`."""
+
+ def unlink(self):
+ """Remove circular references to objects no longer required."""
+ for state in self.states.values():
+ state.unlink()
+ self.states = None
+
+ def run(self, input_lines, input_offset=0, context=None,
+ input_source=None):
+ """
+ Run the state machine on `input_lines`. Return results (a list).
+
+ Reset `self.line_offset` and `self.current_state`. Run the
+ beginning-of-file transition. Input one line at a time and check for a
+ matching transition. If a match is found, call the transition method
+ and possibly change the state. Store the context returned by the
+ transition method to be passed on to the next transition matched.
+ Accumulate the results returned by the transition methods in a list.
+ Run the end-of-file transition. Finally, return the accumulated
+ results.
+
+ Parameters:
+
+ - `input_lines`: a list of strings without newlines, or `StringList`.
+ - `input_offset`: the line offset of `input_lines` from the beginning
+ of the file.
+ - `context`: application-specific storage.
+ - `input_source`: name or path of source of `input_lines`.
+ """
+ self.runtime_init()
+ if isinstance(input_lines, StringList):
+ self.input_lines = input_lines
+ else:
+ self.input_lines = StringList(input_lines, source=input_source)
+ self.input_offset = input_offset
+ self.line_offset = -1
+ self.current_state = self.initial_state
+ if self.debug:
+ print >>sys.stderr, (
+ '\nStateMachine.run: input_lines (line_offset=%s):\n| %s'
+ % (self.line_offset, '\n| '.join(self.input_lines)))
+ transitions = None
+ results = []
+ state = self.get_state()
+ try:
+ if self.debug:
+ print >>sys.stderr, ('\nStateMachine.run: bof transition')
+ context, result = state.bof(context)
+ results.extend(result)
+ while 1:
+ try:
+ try:
+ self.next_line()
+ if self.debug:
+ source, offset = self.input_lines.info(
+ self.line_offset)
+ print >>sys.stderr, (
+ '\nStateMachine.run: line (source=%r, '
+ 'offset=%r):\n| %s'
+ % (source, offset, self.line))
+ context, next_state, result = self.check_line(
+ context, state, transitions)
+ except EOFError:
+ if self.debug:
+ print >>sys.stderr, (
+ '\nStateMachine.run: %s.eof transition'
+ % state.__class__.__name__)
+ result = state.eof(context)
+ results.extend(result)
+ break
+ else:
+ results.extend(result)
+ except TransitionCorrection, exception:
+ self.previous_line() # back up for another try
+ transitions = (exception.args[0],)
+ if self.debug:
+ print >>sys.stderr, (
+ '\nStateMachine.run: TransitionCorrection to '
+ 'state "%s", transition %s.'
+ % (state.__class__.__name__, transitions[0]))
+ continue
+ except StateCorrection, exception:
+ self.previous_line() # back up for another try
+ next_state = exception.args[0]
+ if len(exception.args) == 1:
+ transitions = None
+ else:
+ transitions = (exception.args[1],)
+ if self.debug:
+ print >>sys.stderr, (
+ '\nStateMachine.run: StateCorrection to state '
+ '"%s", transition %s.'
+ % (next_state, transitions[0]))
+ else:
+ transitions = None
+ state = self.get_state(next_state)
+ except:
+ if self.debug:
+ self.error()
+ raise
+ self.observers = []
+ return results
+
+ def get_state(self, next_state=None):
+ """
+ Return current state object; set it first if `next_state` given.
+
+ Parameter `next_state`: a string, the name of the next state.
+
+ Exception: `UnknownStateError` raised if `next_state` unknown.
+ """
+ if next_state:
+ if self.debug and next_state != self.current_state:
+ print >>sys.stderr, \
+ ('\nStateMachine.get_state: Changing state from '
+ '"%s" to "%s" (input line %s).'
+ % (self.current_state, next_state,
+ self.abs_line_number()))
+ self.current_state = next_state
+ try:
+ return self.states[self.current_state]
+ except KeyError:
+ raise UnknownStateError(self.current_state)
+
+ def next_line(self, n=1):
+ """Load `self.line` with the `n`'th next line and return it."""
+ try:
+ try:
+ self.line_offset += n
+ self.line = self.input_lines[self.line_offset]
+ except IndexError:
+ self.line = None
+ raise EOFError
+ return self.line
+ finally:
+ self.notify_observers()
+
+ def is_next_line_blank(self):
+ """Return 1 if the next line is blank or non-existant."""
+ try:
+ return not self.input_lines[self.line_offset + 1].strip()
+ except IndexError:
+ return 1
+
+ def at_eof(self):
+ """Return 1 if the input is at or past end-of-file."""
+ return self.line_offset >= len(self.input_lines) - 1
+
+ def at_bof(self):
+ """Return 1 if the input is at or before beginning-of-file."""
+ return self.line_offset <= 0
+
+ def previous_line(self, n=1):
+ """Load `self.line` with the `n`'th previous line and return it."""
+ self.line_offset -= n
+ if self.line_offset < 0:
+ self.line = None
+ else:
+ self.line = self.input_lines[self.line_offset]
+ self.notify_observers()
+ return self.line
+
+ def goto_line(self, line_offset):
+ """Jump to absolute line offset `line_offset`, load and return it."""
+ try:
+ try:
+ self.line_offset = line_offset - self.input_offset
+ self.line = self.input_lines[self.line_offset]
+ except IndexError:
+ self.line = None
+ raise EOFError
+ return self.line
+ finally:
+ self.notify_observers()
+
+ def abs_line_offset(self):
+ """Return line offset of current line, from beginning of file."""
+ return self.line_offset + self.input_offset
+
+ def abs_line_number(self):
+ """Return line number of current line (counting from 1)."""
+ return self.line_offset + self.input_offset + 1
+
+ def insert_input(self, input_lines, source):
+ self.input_lines.insert(self.line_offset + 1, '',
+ source='internal padding')
+ self.input_lines.insert(self.line_offset + 1, '',
+ source='internal padding')
+ self.input_lines.insert(self.line_offset + 2,
+ StringList(input_lines, source))
+
+ def get_text_block(self, flush_left=0):
+ """
+ Return a contiguous block of text.
+
+ If `flush_left` is true, raise `UnexpectedIndentationError` if an
+ indented line is encountered before the text block ends (with a blank
+ line).
+ """
+ try:
+ block = self.input_lines.get_text_block(self.line_offset,
+ flush_left)
+ self.next_line(len(block) - 1)
+ return block
+ except UnexpectedIndentationError, error:
+ block, source, lineno = error
+ self.next_line(len(block) - 1) # advance to last line of block
+ raise
+
+ def check_line(self, context, state, transitions=None):
+ """
+ Examine one line of input for a transition match & execute its method.
+
+ Parameters:
+
+ - `context`: application-dependent storage.
+ - `state`: a `State` object, the current state.
+ - `transitions`: an optional ordered list of transition names to try,
+ instead of ``state.transition_order``.
+
+ Return the values returned by the transition method:
+
+ - context: possibly modified from the parameter `context`;
+ - next state name (`State` subclass name);
+ - the result output of the transition, a list.
+
+ When there is no match, ``state.no_match()`` is called and its return
+ value is returned.
+ """
+ if transitions is None:
+ transitions = state.transition_order
+ state_correction = None
+ if self.debug:
+ print >>sys.stderr, (
+ '\nStateMachine.check_line: state="%s", transitions=%r.'
+ % (state.__class__.__name__, transitions))
+ for name in transitions:
+ pattern, method, next_state = state.transitions[name]
+ match = self.match(pattern)
+ if match:
+ if self.debug:
+ print >>sys.stderr, (
+ '\nStateMachine.check_line: Matched transition '
+ '"%s" in state "%s".'
+ % (name, state.__class__.__name__))
+ return method(match, context, next_state)
+ else:
+ if self.debug:
+ print >>sys.stderr, (
+ '\nStateMachine.check_line: No match in state "%s".'
+ % state.__class__.__name__)
+ return state.no_match(context, transitions)
+
+ def match(self, pattern):
+ """
+ Return the result of a regular expression match.
+
+ Parameter `pattern`: an `re` compiled regular expression.
+ """
+ return pattern.match(self.line)
+
+ def add_state(self, state_class):
+ """
+ Initialize & add a `state_class` (`State` subclass) object.
+
+ Exception: `DuplicateStateError` raised if `state_class` was already
+ added.
+ """
+ statename = state_class.__name__
+ if self.states.has_key(statename):
+ raise DuplicateStateError(statename)
+ self.states[statename] = state_class(self, self.debug)
+
+ def add_states(self, state_classes):
+ """
+ Add `state_classes` (a list of `State` subclasses).
+ """
+ for state_class in state_classes:
+ self.add_state(state_class)
+
+ def runtime_init(self):
+ """
+ Initialize `self.states`.
+ """
+ for state in self.states.values():
+ state.runtime_init()
+
+ def error(self):
+ """Report error details."""
+ type, value, module, line, function = _exception_data()
+ print >>sys.stderr, '%s: %s' % (type, value)
+ print >>sys.stderr, 'input line %s' % (self.abs_line_number())
+ print >>sys.stderr, ('module %s, line %s, function %s'
+ % (module, line, function))
+
+ def attach_observer(self, observer):
+ """
+ The `observer` parameter is a function or bound method which takes two
+ arguments, the source and offset of the current line.
+ """
+ self.observers.append(observer)
+
+ def detach_observer(self, observer):
+ self.observers.remove(observer)
+
+ def notify_observers(self):
+ for observer in self.observers:
+ try:
+ info = self.input_lines.info(self.line_offset)
+ except IndexError:
+ info = (None, None)
+ observer(*info)
+
+
+class State:
+
+ """
+ State superclass. Contains a list of transitions, and transition methods.
+
+ Transition methods all have the same signature. They take 3 parameters:
+
+ - An `re` match object. ``match.string`` contains the matched input line,
+ ``match.start()`` gives the start index of the match, and
+ ``match.end()`` gives the end index.
+ - A context object, whose meaning is application-defined (initial value
+ ``None``). It can be used to store any information required by the state
+ machine, and the retured context is passed on to the next transition
+ method unchanged.
+ - The name of the next state, a string, taken from the transitions list;
+ normally it is returned unchanged, but it may be altered by the
+ transition method if necessary.
+
+ Transition methods all return a 3-tuple:
+
+ - A context object, as (potentially) modified by the transition method.
+ - The next state name (a return value of ``None`` means no state change).
+ - The processing result, a list, which is accumulated by the state
+ machine.
+
+ Transition methods may raise an `EOFError` to cut processing short.
+
+ There are two implicit transitions, and corresponding transition methods
+ are defined: `bof()` handles the beginning-of-file, and `eof()` handles
+ the end-of-file. These methods have non-standard signatures and return
+ values. `bof()` returns the initial context and results, and may be used
+ to return a header string, or do any other processing needed. `eof()`
+ should handle any remaining context and wrap things up; it returns the
+ final processing result.
+
+ Typical applications need only subclass `State` (or a subclass), set the
+ `patterns` and `initial_transitions` class attributes, and provide
+ corresponding transition methods. The default object initialization will
+ take care of constructing the list of transitions.
+ """
+
+ patterns = None
+ """
+ {Name: pattern} mapping, used by `make_transition()`. Each pattern may
+ be a string or a compiled `re` pattern. Override in subclasses.
+ """
+
+ initial_transitions = None
+ """
+ A list of transitions to initialize when a `State` is instantiated.
+ Each entry is either a transition name string, or a (transition name, next
+ state name) pair. See `make_transitions()`. Override in subclasses.
+ """
+
+ nested_sm = None
+ """
+ The `StateMachine` class for handling nested processing.
+
+ If left as ``None``, `nested_sm` defaults to the class of the state's
+ controlling state machine. Override it in subclasses to avoid the default.
+ """
+
+ nested_sm_kwargs = None
+ """
+ Keyword arguments dictionary, passed to the `nested_sm` constructor.
+
+ Two keys must have entries in the dictionary:
+
+ - Key 'state_classes' must be set to a list of `State` classes.
+ - Key 'initial_state' must be set to the name of the initial state class.
+
+ If `nested_sm_kwargs` is left as ``None``, 'state_classes' defaults to the
+ class of the current state, and 'initial_state' defaults to the name of
+ the class of the current state. Override in subclasses to avoid the
+ defaults.
+ """
+
+ def __init__(self, state_machine, debug=0):
+ """
+ Initialize a `State` object; make & add initial transitions.
+
+ Parameters:
+
+ - `statemachine`: the controlling `StateMachine` object.
+ - `debug`: a boolean; produce verbose output if true (nonzero).
+ """
+
+ self.transition_order = []
+ """A list of transition names in search order."""
+
+ self.transitions = {}
+ """
+ A mapping of transition names to 3-tuples containing
+ (compiled_pattern, transition_method, next_state_name). Initialized as
+ an instance attribute dynamically (instead of as a class attribute)
+ because it may make forward references to patterns and methods in this
+ or other classes.
+ """
+
+ self.add_initial_transitions()
+
+ self.state_machine = state_machine
+ """A reference to the controlling `StateMachine` object."""
+
+ self.debug = debug
+ """Debugging mode on/off."""
+
+ if self.nested_sm is None:
+ self.nested_sm = self.state_machine.__class__
+ if self.nested_sm_kwargs is None:
+ self.nested_sm_kwargs = {'state_classes': [self.__class__],
+ 'initial_state': self.__class__.__name__}
+
+ def runtime_init(self):
+ """
+ Initialize this `State` before running the state machine; called from
+ `self.state_machine.run()`.
+ """
+ pass
+
+ def unlink(self):
+ """Remove circular references to objects no longer required."""
+ self.state_machine = None
+
+ def add_initial_transitions(self):
+ """Make and add transitions listed in `self.initial_transitions`."""
+ if self.initial_transitions:
+ names, transitions = self.make_transitions(
+ self.initial_transitions)
+ self.add_transitions(names, transitions)
+
+ def add_transitions(self, names, transitions):
+ """
+ Add a list of transitions to the start of the transition list.
+
+ Parameters:
+
+ - `names`: a list of transition names.
+ - `transitions`: a mapping of names to transition tuples.
+
+ Exceptions: `DuplicateTransitionError`, `UnknownTransitionError`.
+ """
+ for name in names:
+ if self.transitions.has_key(name):
+ raise DuplicateTransitionError(name)
+ if not transitions.has_key(name):
+ raise UnknownTransitionError(name)
+ self.transition_order[:0] = names
+ self.transitions.update(transitions)
+
+ def add_transition(self, name, transition):
+ """
+ Add a transition to the start of the transition list.
+
+ Parameter `transition`: a ready-made transition 3-tuple.
+
+ Exception: `DuplicateTransitionError`.
+ """
+ if self.transitions.has_key(name):
+ raise DuplicateTransitionError(name)
+ self.transition_order[:0] = [name]
+ self.transitions[name] = transition
+
+ def remove_transition(self, name):
+ """
+ Remove a transition by `name`.
+
+ Exception: `UnknownTransitionError`.
+ """
+ try:
+ del self.transitions[name]
+ self.transition_order.remove(name)
+ except:
+ raise UnknownTransitionError(name)
+
+ def make_transition(self, name, next_state=None):
+ """
+ Make & return a transition tuple based on `name`.
+
+ This is a convenience function to simplify transition creation.
+
+ Parameters:
+
+ - `name`: a string, the name of the transition pattern & method. This
+ `State` object must have a method called '`name`', and a dictionary
+ `self.patterns` containing a key '`name`'.
+ - `next_state`: a string, the name of the next `State` object for this
+ transition. A value of ``None`` (or absent) implies no state change
+ (i.e., continue with the same state).
+
+ Exceptions: `TransitionPatternNotFound`, `TransitionMethodNotFound`.
+ """
+ if next_state is None:
+ next_state = self.__class__.__name__
+ try:
+ pattern = self.patterns[name]
+ if not hasattr(pattern, 'match'):
+ pattern = re.compile(pattern)
+ except KeyError:
+ raise TransitionPatternNotFound(
+ '%s.patterns[%r]' % (self.__class__.__name__, name))
+ try:
+ method = getattr(self, name)
+ except AttributeError:
+ raise TransitionMethodNotFound(
+ '%s.%s' % (self.__class__.__name__, name))
+ return (pattern, method, next_state)
+
+ def make_transitions(self, name_list):
+ """
+ Return a list of transition names and a transition mapping.
+
+ Parameter `name_list`: a list, where each entry is either a transition
+ name string, or a 1- or 2-tuple (transition name, optional next state
+ name).
+ """
+ stringtype = type('')
+ names = []
+ transitions = {}
+ for namestate in name_list:
+ if type(namestate) is stringtype:
+ transitions[namestate] = self.make_transition(namestate)
+ names.append(namestate)
+ else:
+ transitions[namestate[0]] = self.make_transition(*namestate)
+ names.append(namestate[0])
+ return names, transitions
+
+ def no_match(self, context, transitions):
+ """
+ Called when there is no match from `StateMachine.check_line()`.
+
+ Return the same values returned by transition methods:
+
+ - context: unchanged;
+ - next state name: ``None``;
+ - empty result list.
+
+ Override in subclasses to catch this event.
+ """
+ return context, None, []
+
+ def bof(self, context):
+ """
+ Handle beginning-of-file. Return unchanged `context`, empty result.
+
+ Override in subclasses.
+
+ Parameter `context`: application-defined storage.
+ """
+ return context, []
+
+ def eof(self, context):
+ """
+ Handle end-of-file. Return empty result.
+
+ Override in subclasses.
+
+ Parameter `context`: application-defined storage.
+ """
+ return []
+
+ def nop(self, match, context, next_state):
+ """
+ A "do nothing" transition method.
+
+ Return unchanged `context` & `next_state`, empty result. Useful for
+ simple state changes (actionless transitions).
+ """
+ return context, next_state, []
+
+
+class StateMachineWS(StateMachine):
+
+ """
+ `StateMachine` subclass specialized for whitespace recognition.
+
+ There are three methods provided for extracting indented text blocks:
+
+ - `get_indented()`: use when the indent is unknown.
+ - `get_known_indented()`: use when the indent is known for all lines.
+ - `get_first_known_indented()`: use when only the first line's indent is
+ known.
+ """
+
+ def get_indented(self, until_blank=0, strip_indent=1):
+ """
+ Return a block of indented lines of text, and info.
+
+ Extract an indented block where the indent is unknown for all lines.
+
+ :Parameters:
+ - `until_blank`: Stop collecting at the first blank line if true
+ (1).
+ - `strip_indent`: Strip common leading indent if true (1,
+ default).
+
+ :Return:
+ - the indented block (a list of lines of text),
+ - its indent,
+ - its first line offset from BOF, and
+ - whether or not it finished with a blank line.
+ """
+ offset = self.abs_line_offset()
+ indented, indent, blank_finish = self.input_lines.get_indented(
+ self.line_offset, until_blank, strip_indent)
+ if indented:
+ self.next_line(len(indented) - 1) # advance to last indented line
+ while indented and not indented[0].strip():
+ indented.trim_start()
+ offset += 1
+ return indented, indent, offset, blank_finish
+
+ def get_known_indented(self, indent, until_blank=0, strip_indent=1):
+ """
+ Return an indented block and info.
+
+ Extract an indented block where the indent is known for all lines.
+ Starting with the current line, extract the entire text block with at
+ least `indent` indentation (which must be whitespace, except for the
+ first line).
+
+ :Parameters:
+ - `indent`: The number of indent columns/characters.
+ - `until_blank`: Stop collecting at the first blank line if true
+ (1).
+ - `strip_indent`: Strip `indent` characters of indentation if true
+ (1, default).
+
+ :Return:
+ - the indented block,
+ - its first line offset from BOF, and
+ - whether or not it finished with a blank line.
+ """
+ offset = self.abs_line_offset()
+ indented, indent, blank_finish = self.input_lines.get_indented(
+ self.line_offset, until_blank, strip_indent,
+ block_indent=indent)
+ self.next_line(len(indented) - 1) # advance to last indented line
+ while indented and not indented[0].strip():
+ indented.trim_start()
+ offset += 1
+ return indented, offset, blank_finish
+
+ def get_first_known_indented(self, indent, until_blank=0, strip_indent=1,
+ strip_top=1):
+ """
+ Return an indented block and info.
+
+ Extract an indented block where the indent is known for the first line
+ and unknown for all other lines.
+
+ :Parameters:
+ - `indent`: The first line's indent (# of columns/characters).
+ - `until_blank`: Stop collecting at the first blank line if true
+ (1).
+ - `strip_indent`: Strip `indent` characters of indentation if true
+ (1, default).
+ - `strip_top`: Strip blank lines from the beginning of the block.
+
+ :Return:
+ - the indented block,
+ - its indent,
+ - its first line offset from BOF, and
+ - whether or not it finished with a blank line.
+ """
+ offset = self.abs_line_offset()
+ indented, indent, blank_finish = self.input_lines.get_indented(
+ self.line_offset, until_blank, strip_indent,
+ first_indent=indent)
+ self.next_line(len(indented) - 1) # advance to last indented line
+ if strip_top:
+ while indented and not indented[0].strip():
+ indented.trim_start()
+ offset += 1
+ return indented, indent, offset, blank_finish
+
+
+class StateWS(State):
+
+ """
+ State superclass specialized for whitespace (blank lines & indents).
+
+ Use this class with `StateMachineWS`. The transitions 'blank' (for blank
+ lines) and 'indent' (for indented text blocks) are added automatically,
+ before any other transitions. The transition method `blank()` handles
+ blank lines and `indent()` handles nested indented blocks. Indented
+ blocks trigger a new state machine to be created by `indent()` and run.
+ The class of the state machine to be created is in `indent_sm`, and the
+ constructor keyword arguments are in the dictionary `indent_sm_kwargs`.
+
+ The methods `known_indent()` and `firstknown_indent()` are provided for
+ indented blocks where the indent (all lines' and first line's only,
+ respectively) is known to the transition method, along with the attributes
+ `known_indent_sm` and `known_indent_sm_kwargs`. Neither transition method
+ is triggered automatically.
+ """
+
+ indent_sm = None
+ """
+ The `StateMachine` class handling indented text blocks.
+
+ If left as ``None``, `indent_sm` defaults to the value of
+ `State.nested_sm`. Override it in subclasses to avoid the default.
+ """
+
+ indent_sm_kwargs = None
+ """
+ Keyword arguments dictionary, passed to the `indent_sm` constructor.
+
+ If left as ``None``, `indent_sm_kwargs` defaults to the value of
+ `State.nested_sm_kwargs`. Override it in subclasses to avoid the default.
+ """
+
+ known_indent_sm = None
+ """
+ The `StateMachine` class handling known-indented text blocks.
+
+ If left as ``None``, `known_indent_sm` defaults to the value of
+ `indent_sm`. Override it in subclasses to avoid the default.
+ """
+
+ known_indent_sm_kwargs = None
+ """
+ Keyword arguments dictionary, passed to the `known_indent_sm` constructor.
+
+ If left as ``None``, `known_indent_sm_kwargs` defaults to the value of
+ `indent_sm_kwargs`. Override it in subclasses to avoid the default.
+ """
+
+ ws_patterns = {'blank': ' *$',
+ 'indent': ' +'}
+ """Patterns for default whitespace transitions. May be overridden in
+ subclasses."""
+
+ ws_initial_transitions = ('blank', 'indent')
+ """Default initial whitespace transitions, added before those listed in
+ `State.initial_transitions`. May be overridden in subclasses."""
+
+ def __init__(self, state_machine, debug=0):
+ """
+ Initialize a `StateSM` object; extends `State.__init__()`.
+
+ Check for indent state machine attributes, set defaults if not set.
+ """
+ State.__init__(self, state_machine, debug)
+ if self.indent_sm is None:
+ self.indent_sm = self.nested_sm
+ if self.indent_sm_kwargs is None:
+ self.indent_sm_kwargs = self.nested_sm_kwargs
+ if self.known_indent_sm is None:
+ self.known_indent_sm = self.indent_sm
+ if self.known_indent_sm_kwargs is None:
+ self.known_indent_sm_kwargs = self.indent_sm_kwargs
+
+ def add_initial_transitions(self):
+ """
+ Add whitespace-specific transitions before those defined in subclass.
+
+ Extends `State.add_initial_transitions()`.
+ """
+ State.add_initial_transitions(self)
+ if self.patterns is None:
+ self.patterns = {}
+ self.patterns.update(self.ws_patterns)
+ names, transitions = self.make_transitions(
+ self.ws_initial_transitions)
+ self.add_transitions(names, transitions)
+
+ def blank(self, match, context, next_state):
+ """Handle blank lines. Does nothing. Override in subclasses."""
+ return self.nop(match, context, next_state)
+
+ def indent(self, match, context, next_state):
+ """
+ Handle an indented text block. Extend or override in subclasses.
+
+ Recursively run the registered state machine for indented blocks
+ (`self.indent_sm`).
+ """
+ indented, indent, line_offset, blank_finish = \
+ self.state_machine.get_indented()
+ sm = self.indent_sm(debug=self.debug, **self.indent_sm_kwargs)
+ results = sm.run(indented, input_offset=line_offset)
+ return context, next_state, results
+
+ def known_indent(self, match, context, next_state):
+ """
+ Handle a known-indent text block. Extend or override in subclasses.
+
+ Recursively run the registered state machine for known-indent indented
+ blocks (`self.known_indent_sm`). The indent is the length of the
+ match, ``match.end()``.
+ """
+ indented, line_offset, blank_finish = \
+ self.state_machine.get_known_indented(match.end())
+ sm = self.known_indent_sm(debug=self.debug,
+ **self.known_indent_sm_kwargs)
+ results = sm.run(indented, input_offset=line_offset)
+ return context, next_state, results
+
+ def first_known_indent(self, match, context, next_state):
+ """
+ Handle an indented text block (first line's indent known).
+
+ Extend or override in subclasses.
+
+ Recursively run the registered state machine for known-indent indented
+ blocks (`self.known_indent_sm`). The indent is the length of the
+ match, ``match.end()``.
+ """
+ indented, line_offset, blank_finish = \
+ self.state_machine.get_first_known_indented(match.end())
+ sm = self.known_indent_sm(debug=self.debug,
+ **self.known_indent_sm_kwargs)
+ results = sm.run(indented, input_offset=line_offset)
+ return context, next_state, results
+
+
+class _SearchOverride:
+
+ """
+ Mix-in class to override `StateMachine` regular expression behavior.
+
+ Changes regular expression matching, from the default `re.match()`
+ (succeeds only if the pattern matches at the start of `self.line`) to
+ `re.search()` (succeeds if the pattern matches anywhere in `self.line`).
+ When subclassing a `StateMachine`, list this class **first** in the
+ inheritance list of the class definition.
+ """
+
+ def match(self, pattern):
+ """
+ Return the result of a regular expression search.
+
+ Overrides `StateMachine.match()`.
+
+ Parameter `pattern`: `re` compiled regular expression.
+ """
+ return pattern.search(self.line)
+
+
+class SearchStateMachine(_SearchOverride, StateMachine):
+ """`StateMachine` which uses `re.search()` instead of `re.match()`."""
+ pass
+
+
+class SearchStateMachineWS(_SearchOverride, StateMachineWS):
+ """`StateMachineWS` which uses `re.search()` instead of `re.match()`."""
+ pass
+
+
+class ViewList:
+
+ """
+ List with extended functionality: slices of ViewList objects are child
+ lists, linked to their parents. Changes made to a child list also affect
+ the parent list. A child list is effectively a "view" (in the SQL sense)
+ of the parent list. Changes to parent lists, however, do *not* affect
+ active child lists. If a parent list is changed, any active child lists
+ should be recreated.
+
+ The start and end of the slice can be trimmed using the `trim_start()` and
+ `trim_end()` methods, without affecting the parent list. The link between
+ child and parent lists can be broken by calling `disconnect()` on the
+ child list.
+
+ Also, ViewList objects keep track of the source & offset of each item.
+ This information is accessible via the `source()`, `offset()`, and
+ `info()` methods.
+ """
+
+ def __init__(self, initlist=None, source=None, items=None,
+ parent=None, parent_offset=None):
+ self.data = []
+ """The actual list of data, flattened from various sources."""
+
+ self.items = []
+ """A list of (source, offset) pairs, same length as `self.data`: the
+ source of each line and the offset of each line from the beginning of
+ its source."""
+
+ self.parent = parent
+ """The parent list."""
+
+ self.parent_offset = parent_offset
+ """Offset of this list from the beginning of the parent list."""
+
+ if isinstance(initlist, ViewList):
+ self.data = initlist.data[:]
+ self.items = initlist.items[:]
+ elif initlist is not None:
+ self.data = list(initlist)
+ if items:
+ self.items = items
+ else:
+ self.items = [(source, i) for i in range(len(initlist))]
+ assert len(self.data) == len(self.items), 'data mismatch'
+
+ def __str__(self):
+ return str(self.data)
+
+ def __repr__(self):
+ return '%s(%s, items=%s)' % (self.__class__.__name__,
+ self.data, self.items)
+
+ def __lt__(self, other): return self.data < self.__cast(other)
+ def __le__(self, other): return self.data <= self.__cast(other)
+ def __eq__(self, other): return self.data == self.__cast(other)
+ def __ne__(self, other): return self.data != self.__cast(other)
+ def __gt__(self, other): return self.data > self.__cast(other)
+ def __ge__(self, other): return self.data >= self.__cast(other)
+ def __cmp__(self, other): return cmp(self.data, self.__cast(other))
+
+ def __cast(self, other):
+ if isinstance(other, ViewList):
+ return other.data
+ else:
+ return other
+
+ def __contains__(self, item): return item in self.data
+ def __len__(self): return len(self.data)
+
+ # The __getitem__()/__setitem__() methods check whether the index
+ # is a slice first, since native list objects start supporting
+ # them directly in Python 2.3 (no exception is raised when
+ # indexing a list with a slice object; they just work).
+
+ def __getitem__(self, i):
+ if isinstance(i, _SliceType):
+ assert i.step in (None, 1), 'cannot handle slice with stride'
+ return self.__class__(self.data[i.start:i.stop],
+ items=self.items[i.start:i.stop],
+ parent=self, parent_offset=i.start)
+ else:
+ return self.data[i]
+
+ def __setitem__(self, i, item):
+ if isinstance(i, _SliceType):
+ assert i.step in (None, 1), 'cannot handle slice with stride'
+ if not isinstance(item, ViewList):
+ raise TypeError('assigning non-ViewList to ViewList slice')
+ self.data[i.start:i.stop] = item.data
+ self.items[i.start:i.stop] = item.items
+ assert len(self.data) == len(self.items), 'data mismatch'
+ if self.parent:
+ self.parent[i.start + self.parent_offset
+ : i.stop + self.parent_offset] = item
+ else:
+ self.data[i] = item
+ if self.parent:
+ self.parent[i + self.parent_offset] = item
+
+ def __delitem__(self, i):
+ try:
+ del self.data[i]
+ del self.items[i]
+ if self.parent:
+ del self.parent[i + self.parent_offset]
+ except TypeError:
+ assert i.step is None, 'cannot handle slice with stride'
+ del self.data[i.start:i.stop]
+ del self.items[i.start:i.stop]
+ if self.parent:
+ del self.parent[i.start + self.parent_offset
+ : i.stop + self.parent_offset]
+
+ def __add__(self, other):
+ if isinstance(other, ViewList):
+ return self.__class__(self.data + other.data,
+ items=(self.items + other.items))
+ else:
+ raise TypeError('adding non-ViewList to a ViewList')
+
+ def __radd__(self, other):
+ if isinstance(other, ViewList):
+ return self.__class__(other.data + self.data,
+ items=(other.items + self.items))
+ else:
+ raise TypeError('adding ViewList to a non-ViewList')
+
+ def __iadd__(self, other):
+ if isinstance(other, ViewList):
+ self.data += other.data
+ else:
+ raise TypeError('argument to += must be a ViewList')
+ return self
+
+ def __mul__(self, n):
+ return self.__class__(self.data * n, items=(self.items * n))
+
+ __rmul__ = __mul__
+
+ def __imul__(self, n):
+ self.data *= n
+ self.items *= n
+ return self
+
+ def extend(self, other):
+ if not isinstance(other, ViewList):
+ raise TypeError('extending a ViewList with a non-ViewList')
+ if self.parent:
+ self.parent.insert(len(self.data) + self.parent_offset, other)
+ self.data.extend(other.data)
+ self.items.extend(other.items)
+
+ def append(self, item, source=None, offset=0):
+ if source is None:
+ self.extend(item)
+ else:
+ if self.parent:
+ self.parent.insert(len(self.data) + self.parent_offset, item,
+ source, offset)
+ self.data.append(item)
+ self.items.append((source, offset))
+
+ def insert(self, i, item, source=None, offset=0):
+ if source is None:
+ if not isinstance(item, ViewList):
+ raise TypeError('inserting non-ViewList with no source given')
+ self.data[i:i] = item.data
+ self.items[i:i] = item.items
+ if self.parent:
+ index = (len(self.data) + i) % len(self.data)
+ self.parent.insert(index + self.parent_offset, item)
+ else:
+ self.data.insert(i, item)
+ self.items.insert(i, (source, offset))
+ if self.parent:
+ index = (len(self.data) + i) % len(self.data)
+ self.parent.insert(index + self.parent_offset, item,
+ source, offset)
+
+ def pop(self, i=-1):
+ if self.parent:
+ index = (len(self.data) + i) % len(self.data)
+ self.parent.pop(index + self.parent_offset)
+ self.items.pop(i)
+ return self.data.pop(i)
+
+ def trim_start(self, n=1):
+ """
+ Remove items from the start of the list, without touching the parent.
+ """
+ if n > len(self.data):
+ raise IndexError("Size of trim too large; can't trim %s items "
+ "from a list of size %s." % (n, len(self.data)))
+ elif n < 0:
+ raise IndexError('Trim size must be >= 0.')
+ del self.data[:n]
+ del self.items[:n]
+ if self.parent:
+ self.parent_offset += n
+
+ def trim_end(self, n=1):
+ """
+ Remove items from the end of the list, without touching the parent.
+ """
+ if n > len(self.data):
+ raise IndexError("Size of trim too large; can't trim %s items "
+ "from a list of size %s." % (n, len(self.data)))
+ elif n < 0:
+ raise IndexError('Trim size must be >= 0.')
+ del self.data[-n:]
+ del self.items[-n:]
+
+ def remove(self, item):
+ index = self.index(item)
+ del self[index]
+
+ def count(self, item): return self.data.count(item)
+ def index(self, item): return self.data.index(item)
+
+ def reverse(self):
+ self.data.reverse()
+ self.items.reverse()
+ self.parent = None
+
+ def sort(self, *args):
+ tmp = zip(self.data, self.items)
+ tmp.sort(*args)
+ self.data = [entry[0] for entry in tmp]
+ self.items = [entry[1] for entry in tmp]
+ self.parent = None
+
+ def info(self, i):
+ """Return source & offset for index `i`."""
+ try:
+ return self.items[i]
+ except IndexError:
+ if i == len(self.data): # Just past the end
+ return self.items[i - 1][0], None
+ else:
+ raise
+
+ def source(self, i):
+ """Return source for index `i`."""
+ return self.info(i)[0]
+
+ def offset(self, i):
+ """Return offset for index `i`."""
+ return self.info(i)[1]
+
+ def disconnect(self):
+ """Break link between this list and parent list."""
+ self.parent = None
+
+
+class StringList(ViewList):
+
+ """A `ViewList` with string-specific methods."""
+
+ def trim_left(self, length, start=0, end=sys.maxint):
+ """
+ Trim `length` characters off the beginning of each item, in-place,
+ from index `start` to `end`. No whitespace-checking is done on the
+ trimmed text. Does not affect slice parent.
+ """
+ self.data[start:end] = [line[length:]
+ for line in self.data[start:end]]
+
+ def get_text_block(self, start, flush_left=0):
+ """
+ Return a contiguous block of text.
+
+ If `flush_left` is true, raise `UnexpectedIndentationError` if an
+ indented line is encountered before the text block ends (with a blank
+ line).
+ """
+ end = start
+ last = len(self.data)
+ while end < last:
+ line = self.data[end]
+ if not line.strip():
+ break
+ if flush_left and (line[0] == ' '):
+ source, offset = self.info(end)
+ raise UnexpectedIndentationError(self[start:end], source,
+ offset + 1)
+ end += 1
+ return self[start:end]
+
+ def get_indented(self, start=0, until_blank=0, strip_indent=1,
+ block_indent=None, first_indent=None):
+ """
+ Extract and return a StringList of indented lines of text.
+
+ Collect all lines with indentation, determine the minimum indentation,
+ remove the minimum indentation from all indented lines (unless
+ `strip_indent` is false), and return them. All lines up to but not
+ including the first unindented line will be returned.
+
+ :Parameters:
+ - `start`: The index of the first line to examine.
+ - `until_blank`: Stop collecting at the first blank line if true.
+ - `strip_indent`: Strip common leading indent if true (default).
+ - `block_indent`: The indent of the entire block, if known.
+ - `first_indent`: The indent of the first line, if known.
+
+ :Return:
+ - a StringList of indented lines with mininum indent removed;
+ - the amount of the indent;
+ - a boolean: did the indented block finish with a blank line or EOF?
+ """
+ indent = block_indent # start with None if unknown
+ end = start
+ if block_indent is not None and first_indent is None:
+ first_indent = block_indent
+ if first_indent is not None:
+ end += 1
+ last = len(self.data)
+ while end < last:
+ line = self.data[end]
+ if line and (line[0] != ' '
+ or (block_indent is not None
+ and line[:block_indent].strip())):
+ # Line not indented or insufficiently indented.
+ # Block finished properly iff the last indented line blank:
+ blank_finish = ((end > start)
+ and not self.data[end - 1].strip())
+ break
+ stripped = line.lstrip()
+ if not stripped: # blank line
+ if until_blank:
+ blank_finish = 1
+ break
+ elif block_indent is None:
+ line_indent = len(line) - len(stripped)
+ if indent is None:
+ indent = line_indent
+ else:
+ indent = min(indent, line_indent)
+ end += 1
+ else:
+ blank_finish = 1 # block ends at end of lines
+ block = self[start:end]
+ if first_indent is not None and block:
+ block.data[0] = block.data[0][first_indent:]
+ if indent and strip_indent:
+ 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
+class UnknownStateError(StateMachineError): pass
+class DuplicateStateError(StateMachineError): pass
+class UnknownTransitionError(StateMachineError): pass
+class DuplicateTransitionError(StateMachineError): pass
+class TransitionPatternNotFound(StateMachineError): pass
+class TransitionMethodNotFound(StateMachineError): pass
+class UnexpectedIndentationError(StateMachineError): pass
+
+
+class TransitionCorrection(Exception):
+
+ """
+ Raise from within a transition method to switch to another transition.
+
+ Raise with one argument, the new transition name.
+ """
+
+
+class StateCorrection(Exception):
+
+ """
+ Raise from within a transition method to switch to another state.
+
+ Raise with one or two arguments: new state name, and an optional new
+ transition name.
+ """
+
+
+def string2lines(astring, tab_width=8, convert_whitespace=0,
+ whitespace=re.compile('[\v\f]')):
+ """
+ Return a list of one-line strings with tabs expanded and no newlines.
+
+ Each tab is expanded with between 1 and `tab_width` spaces, so that the
+ next character's index becomes a multiple of `tab_width` (8 by default).
+
+ Parameters:
+
+ - `astring`: a multi-line string.
+ - `tab_width`: the number of columns between tab stops.
+ - `convert_whitespace`: convert form feeds and vertical tabs to spaces?
+ """
+ if convert_whitespace:
+ astring = whitespace.sub(' ', astring)
+ return [s.expandtabs(tab_width) for s in astring.splitlines()]
+
+def _exception_data():
+ """
+ Return exception information:
+
+ - the exception's class name;
+ - the exception object;
+ - the name of the file containing the offending code;
+ - the line number of the offending code;
+ - the function name of the offending code.
+ """
+ type, value, traceback = sys.exc_info()
+ while traceback.tb_next:
+ traceback = traceback.tb_next
+ code = traceback.tb_frame.f_code
+ return (type.__name__, value, code.co_filename, traceback.tb_lineno,
+ code.co_name)
=== Zope/lib/python/docutils/urischemes.py 1.2.10.1 => 1.2.10.2 ===
--- /dev/null Sun Nov 30 11:05:52 2003
+++ Zope/lib/python/docutils/urischemes.py Sun Nov 30 11:05:22 2003
@@ -0,0 +1,105 @@
+"""
+`schemes` is a dictionary with lowercase URI addressing schemes as
+keys and descriptions as values. It was compiled from the index at
+http://www.w3.org/Addressing/schemes.html (revised 2001-08-20).
+"""
+
+# Many values are blank and should be filled in with useful descriptions.
+
+schemes = {
+ 'about': 'provides information on Navigator',
+ 'acap': 'Application Configuration Access Protocol',
+ 'addbook': "To add vCard entries to Communicator's Address Book",
+ 'afp': 'Apple Filing Protocol',
+ 'afs': 'Andrew File System global file names',
+ 'aim': 'AOL Instant Messenger',
+ 'callto': 'for NetMeeting links',
+ 'castanet': 'Castanet Tuner URLs for Netcaster',
+ 'chttp': 'cached HTTP supported by RealPlayer',
+ 'cid': 'content identifier',
+ 'data': ('allows inclusion of small data items as "immediate" data; '
+ 'RFC 2397'),
+ 'dav': 'Distributed Authoring and Versioning Protocol; RFC 2518',
+ 'dns': 'Domain Name System resources',
+ 'eid': ('External ID; non-URL data; general escape mechanism to allow '
+ 'access to information for applications that are too '
+ 'specialized to justify their own schemes'),
+ 'fax': ('a connection to a terminal that can handle telefaxes '
+ '(facsimiles); RFC 2806'),
+ 'file': 'Host-specific file names',
+ 'finger': '',
+ 'freenet': '',
+ 'ftp': 'File Transfer Protocol',
+ 'gopher': 'The Gopher Protocol',
+ 'gsm-sms': ('Global System for Mobile Communications Short Message '
+ 'Service'),
+ 'h323': 'video (audiovisual) communication on local area networks',
+ 'h324': ('video and audio communications over low bitrate connections '
+ 'such as POTS modem connections'),
+ 'hdl': 'CNRI handle system',
+ 'hnews': 'an HTTP-tunneling variant of the NNTP news protocol',
+ 'http': 'Hypertext Transfer Protocol',
+ 'https': 'HTTP over SSL',
+ 'iioploc': 'Internet Inter-ORB Protocol Location?',
+ 'ilu': 'Inter-Language Unification',
+ 'imap': 'Internet Message Access Protocol',
+ 'ior': 'CORBA interoperable object reference',
+ 'ipp': 'Internet Printing Protocol',
+ 'irc': 'Internet Relay Chat',
+ 'jar': 'Java archive',
+ 'javascript': ('JavaScript code; evaluates the expression after the '
+ 'colon'),
+ 'jdbc': '',
+ 'ldap': 'Lightweight Directory Access Protocol',
+ 'lifn': '',
+ 'livescript': '',
+ 'lrq': '',
+ 'mailbox': 'Mail folder access',
+ 'mailserver': 'Access to data available from mail servers',
+ 'mailto': 'Electronic mail address',
+ 'md5': '',
+ 'mid': 'message identifier',
+ 'mocha': '',
+ 'modem': ('a connection to a terminal that can handle incoming data '
+ 'calls; RFC 2806'),
+ 'news': 'USENET news',
+ 'nfs': 'Network File System protocol',
+ 'nntp': 'USENET news using NNTP access',
+ 'opaquelocktoken': '',
+ 'phone': '',
+ 'pop': 'Post Office Protocol',
+ 'pop3': 'Post Office Protocol v3',
+ 'printer': '',
+ 'prospero': 'Prospero Directory Service',
+ 'res': '',
+ 'rtsp': 'real time streaming protocol',
+ 'rvp': '',
+ 'rwhois': '',
+ 'rx': 'Remote Execution',
+ 'sdp': '',
+ 'service': 'service location',
+ 'shttp': 'secure hypertext transfer protocol',
+ 'sip': 'Session Initiation Protocol',
+ 'smb': '',
+ 'snews': 'For NNTP postings via SSL',
+ 't120': 'real time data conferencing (audiographics)',
+ 'tcp': '',
+ 'tel': ('a connection to a terminal that handles normal voice '
+ 'telephone calls, a voice mailbox or another voice messaging '
+ 'system or a service that can be operated using DTMF tones; '
+ 'RFC 2806.'),
+ 'telephone': 'telephone',
+ 'telnet': 'Reference to interactive sessions',
+ 'tip': 'Transaction Internet Protocol',
+ 'tn3270': 'Interactive 3270 emulation sessions',
+ 'tv': '',
+ 'urn': 'Uniform Resource Name',
+ 'uuid': '',
+ 'vemmi': 'versatile multimedia interface',
+ 'videotex': '',
+ 'view-source': 'displays HTML code that was generated with JavaScript',
+ 'wais': 'Wide Area Information Servers',
+ 'whodp': '',
+ 'whois++': 'Distributed directory service.',
+ 'z39.50r': 'Z39.50 Retrieval',
+ 'z39.50s': 'Z39.50 Session',}
=== Zope/lib/python/docutils/utils.py 1.2.10.2 => 1.2.10.3 ===
--- /dev/null Sun Nov 30 11:05:52 2003
+++ Zope/lib/python/docutils/utils.py Sun Nov 30 11:05:22 2003
@@ -0,0 +1,447 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision$
+# Date: $Date$
+# Copyright: This module has been placed in the public domain.
+
+"""
+Miscellaneous utilities for the documentation utilities.
+"""
+
+__docformat__ = 'reStructuredText'
+
+import sys
+import os
+import os.path
+from types import StringType, UnicodeType
+from docutils import ApplicationError, DataError
+from docutils import frontend, nodes
+
+
+class SystemMessage(ApplicationError):
+
+ def __init__(self, system_message, level):
+ Exception.__init__(self, system_message.astext())
+ self.level = level
+
+
+class Reporter:
+
+ """
+ Info/warning/error reporter and ``system_message`` element generator.
+
+ Five levels of system messages are defined, along with corresponding
+ methods: `debug()`, `info()`, `warning()`, `error()`, and `severe()`.
+
+ There is typically one Reporter object per process. A Reporter object is
+ instantiated with thresholds for reporting (generating warnings) and
+ halting processing (raising exceptions), a switch to turn debug output on
+ or off, and an I/O stream for warnings. These are stored in the default
+ reporting category, '' (zero-length string).
+
+ Multiple reporting categories [#]_ may be set, each with its own reporting
+ and halting thresholds, debugging switch, and warning stream
+ (collectively a `ConditionSet`). Categories are hierarchical dotted-name
+ strings that look like attribute references: 'spam', 'spam.eggs',
+ 'neeeow.wum.ping'. The 'spam' category is the ancestor of
+ 'spam.bacon.eggs'. Unset categories inherit stored conditions from their
+ closest ancestor category that has been set.
+
+ When a system message is generated, the stored conditions from its
+ category (or ancestor if unset) are retrieved. The system message level
+ is compared to the thresholds stored in the category, and a warning or
+ error is generated as appropriate. Debug messages are produced iff the
+ stored debug switch is on. Message output is sent to the stored warning
+ stream.
+
+ The default category is '' (empty string). By convention, Writers should
+ retrieve reporting conditions from the 'writer' category (which, unless
+ explicitly set, defaults to the conditions of the default category).
+
+ The Reporter class also employs a modified form of the "Observer" pattern
+ [GoF95]_ to track system messages generated. The `attach_observer` method
+ should be called before parsing, with a bound method or function which
+ accepts system messages. The observer can be removed with
+ `detach_observer`, and another added in its place.
+
+ .. [#] The concept of "categories" was inspired by the log4j project:
+ http://jakarta.apache.org/log4j/.
+
+ .. [GoF95] Gamma, Helm, Johnson, Vlissides. *Design Patterns: Elements of
+ Reusable Object-Oriented Software*. Addison-Wesley, Reading, MA, USA,
+ 1995.
+ """
+
+ levels = 'DEBUG INFO WARNING ERROR SEVERE'.split()
+ """List of names for system message levels, indexed by level."""
+
+ def __init__(self, source, report_level, halt_level, stream=None,
+ debug=0, encoding='ascii', error_handler='replace'):
+ """
+ Initialize the `ConditionSet` forthe `Reporter`'s default category.
+
+ :Parameters:
+
+ - `source`: The path to or description of the source data.
+ - `report_level`: The level at or above which warning output will
+ be sent to `stream`.
+ - `halt_level`: The level at or above which `SystemMessage`
+ exceptions will be raised, halting execution.
+ - `debug`: Show debug (level=0) system messages?
+ - `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."""
+
+ if stream is None:
+ stream = sys.stderr
+ 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 ''."""
+
+ self.observers = []
+ """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:
+ stream = sys.stderr
+ self.categories[category] = ConditionSet(debug, report_level,
+ halt_level, stream)
+
+ def unset_conditions(self, category):
+ if category and self.categories.has_key(category):
+ del self.categories[category]
+
+ __delitem__ = unset_conditions
+
+ def get_conditions(self, category):
+ while not self.categories.has_key(category):
+ category = category[:category.rfind('.') + 1][:-1]
+ return self.categories[category]
+
+ __getitem__ = get_conditions
+
+ def attach_observer(self, observer):
+ """
+ The `observer` parameter is a function or bound method which takes one
+ argument, a `nodes.system_message` instance.
+ """
+ self.observers.append(observer)
+
+ def detach_observer(self, observer):
+ self.observers.remove(observer)
+
+ def notify_observers(self, message):
+ for observer in self.observers:
+ observer(message)
+
+ def system_message(self, level, message, *children, **kwargs):
+ """
+ Return a system_message object.
+
+ Raise an exception or generate a warning if appropriate.
+ """
+ attributes = kwargs.copy()
+ category = kwargs.get('category', '')
+ if kwargs.has_key('category'):
+ del attributes['category']
+ if kwargs.has_key('base_node'):
+ source, line = get_source_line(kwargs['base_node'])
+ del attributes['base_node']
+ if source is not None:
+ attributes.setdefault('source', source)
+ if line is not None:
+ attributes.setdefault('line', line)
+ attributes.setdefault('source', self.source)
+ msg = nodes.system_message(message, level=level,
+ type=self.levels[level],
+ *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, msgtext, '[%s]' % category
+ else:
+ print >>stream, msgtext
+ if level >= halt_level:
+ 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):
+ """
+ Level-0, "DEBUG": an internal reporting issue. Typically, there is no
+ effect on the processing. Level-0 system messages are handled
+ separately from the others.
+ """
+ return self.system_message(0, *args, **kwargs)
+
+ def info(self, *args, **kwargs):
+ """
+ Level-1, "INFO": a minor issue that can be ignored. Typically there is
+ no effect on processing, and level-1 system messages are not reported.
+ """
+ return self.system_message(1, *args, **kwargs)
+
+ def warning(self, *args, **kwargs):
+ """
+ Level-2, "WARNING": an issue that should be addressed. If ignored,
+ there may be unpredictable problems with the output.
+ """
+ return self.system_message(2, *args, **kwargs)
+
+ def error(self, *args, **kwargs):
+ """
+ Level-3, "ERROR": an error that should be addressed. If ignored, the
+ output will contain errors.
+ """
+ return self.system_message(3, *args, **kwargs)
+
+ def severe(self, *args, **kwargs):
+ """
+ Level-4, "SEVERE": a severe error that must be addressed. If ignored,
+ the output will contain severe errors. Typically level-4 system
+ messages are turned into exceptions which halt processing.
+ """
+ return self.system_message(4, *args, **kwargs)
+
+
+class ConditionSet:
+
+ """
+ A set of two thresholds (`report_level` & `halt_level`), a switch
+ (`debug`), and an I/O stream (`stream`), corresponding to one `Reporter`
+ category.
+ """
+
+ def __init__(self, debug, report_level, halt_level, stream):
+ self.debug = debug
+ self.report_level = report_level
+ self.halt_level = halt_level
+ self.stream = stream
+
+ def astuple(self):
+ return (self.debug, self.report_level, self.halt_level,
+ self.stream)
+
+
+class ExtensionOptionError(DataError): pass
+class BadOptionError(ExtensionOptionError): pass
+class BadOptionDataError(ExtensionOptionError): pass
+class DuplicateOptionError(ExtensionOptionError): pass
+
+
+def extract_extension_options(field_list, options_spec):
+ """
+ Return a dictionary mapping extension option names to converted values.
+
+ :Parameters:
+ - `field_list`: A flat field list without field arguments, where each
+ field body consists of a single paragraph only.
+ - `options_spec`: Dictionary mapping known option names to a
+ conversion function such as `int` or `float`.
+
+ :Exceptions:
+ - `KeyError` for unknown option names.
+ - `ValueError` for invalid option values (raised by the conversion
+ function).
+ - `DuplicateOptionError` for duplicate options.
+ - `BadOptionError` for invalid fields.
+ - `BadOptionDataError` for invalid option data (missing name,
+ missing data, bad quotes, etc.).
+ """
+ option_list = extract_options(field_list)
+ option_dict = assemble_option_dict(option_list, options_spec)
+ return option_dict
+
+def extract_options(field_list):
+ """
+ Return a list of option (name, value) pairs from field names & bodies.
+
+ :Parameter:
+ `field_list`: A flat field list, where each field name is a single
+ word and each field body consists of a single paragraph only.
+
+ :Exceptions:
+ - `BadOptionError` for invalid fields.
+ - `BadOptionDataError` for invalid option data (missing name,
+ missing data, bad quotes, etc.).
+ """
+ option_list = []
+ for field in field_list:
+ if len(field[0].astext().split()) != 1:
+ raise BadOptionError(
+ 'extension option field name may not contain multiple words')
+ name = str(field[0].astext().lower())
+ body = field[1]
+ if len(body) == 0:
+ data = None
+ elif len(body) > 1 or not isinstance(body[0], nodes.paragraph) \
+ or len(body[0]) != 1 or not isinstance(body[0][0], nodes.Text):
+ raise BadOptionDataError(
+ 'extension option field body may contain\n'
+ 'a single paragraph only (option "%s")' % name)
+ else:
+ data = body[0][0].astext()
+ option_list.append((name, data))
+ return option_list
+
+def assemble_option_dict(option_list, options_spec):
+ """
+ Return a mapping of option names to values.
+
+ :Parameters:
+ - `option_list`: A list of (name, value) pairs (the output of
+ `extract_options()`).
+ - `options_spec`: Dictionary mapping known option names to a
+ conversion function such as `int` or `float`.
+
+ :Exceptions:
+ - `KeyError` for unknown option names.
+ - `DuplicateOptionError` for duplicate options.
+ - `ValueError` for invalid option values (raised by conversion
+ function).
+ """
+ options = {}
+ for name, value in option_list:
+ convertor = options_spec[name] # raises KeyError if unknown
+ if options.has_key(name):
+ raise DuplicateOptionError('duplicate option "%s"' % name)
+ try:
+ options[name] = convertor(value)
+ except (ValueError, TypeError), detail:
+ raise detail.__class__('(option: "%s"; value: %r)\n%s'
+ % (name, value, detail))
+ return options
+
+
+class NameValueError(DataError): pass
+
+
+def extract_name_value(line):
+ """
+ Return a list of (name, value) from a line of the form "name=value ...".
+
+ :Exception:
+ `NameValueError` for invalid input (missing name, missing data, bad
+ quotes, etc.).
+ """
+ attlist = []
+ while line:
+ equals = line.find('=')
+ if equals == -1:
+ raise NameValueError('missing "="')
+ attname = line[:equals].strip()
+ if equals == 0 or not attname:
+ raise NameValueError(
+ 'missing attribute name before "="')
+ line = line[equals+1:].lstrip()
+ if not line:
+ raise NameValueError(
+ 'missing value after "%s="' % attname)
+ if line[0] in '\'"':
+ endquote = line.find(line[0], 1)
+ if endquote == -1:
+ raise NameValueError(
+ 'attribute "%s" missing end quote (%s)'
+ % (attname, line[0]))
+ if len(line) > endquote + 1 and line[endquote + 1].strip():
+ raise NameValueError(
+ 'attribute "%s" end quote (%s) not followed by '
+ 'whitespace' % (attname, line[0]))
+ data = line[1:endquote]
+ line = line[endquote+1:].lstrip()
+ else:
+ space = line.find(' ')
+ if space == -1:
+ data = line
+ line = ''
+ else:
+ data = line[:space]
+ line = line[space+1:].lstrip()
+ attlist.append((attname.lower(), data))
+ return attlist
+
+def new_document(source, settings=None):
+ """
+ Return a new empty document object.
+
+ :Parameters:
+ `source` : string
+ The path to or description of the source text of the document.
+ `settings` : optparse.Values object
+ Runtime settings. If none provided, a default set will be used.
+ """
+ if settings is None:
+ settings = frontend.OptionParser().get_default_values()
+ reporter = Reporter(source, settings.report_level, settings.halt_level,
+ 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
+
+def clean_rcs_keywords(paragraph, keyword_substitutions):
+ if len(paragraph) == 1 and isinstance(paragraph[0], nodes.Text):
+ textnode = paragraph[0]
+ for pattern, substitution in keyword_substitutions:
+ match = pattern.match(textnode.data)
+ if match:
+ textnode.data = pattern.sub(substitution, textnode.data)
+ return
+
+def relative_path(source, target):
+ """
+ Build and return a path to `target`, relative to `source` (both files).
+
+ If there is no common prefix, return the absolute path to `target`.
+ """
+ source_parts = os.path.abspath(source or 'dummy_file').split(os.sep)
+ target_parts = os.path.abspath(target).split(os.sep)
+ # Check first 2 parts because '/dir'.split('/') == ['', 'dir']:
+ if source_parts[:2] != target_parts[:2]:
+ # Nothing in common between paths.
+ # Return absolute path, using '/' for URLs:
+ return '/'.join(target_parts)
+ source_parts.reverse()
+ target_parts.reverse()
+ while (source_parts and target_parts
+ and source_parts[-1] == target_parts[-1]):
+ # Remove path components in common:
+ source_parts.pop()
+ target_parts.pop()
+ target_parts.reverse()
+ parts = ['..'] * (len(source_parts) - 1) + target_parts
+ return '/'.join(parts)
+
+def get_source_line(node):
+ """
+ Return the "source" and "line" attributes from the `node` given or from
+ its closest ancestor.
+ """
+ while node:
+ if node.source or node.line:
+ return node.source, node.line
+ node = node.parent
+ return None, None
More information about the Zope-Checkins
mailing list