[Python-checkins] python/nondist/peps/docutils .cvsignore,NONE,1.1 __init__.py,NONE,1.1 core.py,NONE,1.1 frontend.py,NONE,1.1 io.py,NONE,1.1 nodes.py,NONE,1.1 optik.py,NONE,1.1 roman.py,NONE,1.1 statemachine.py,NONE,1.1 urischemes.py,NONE,1.1 utils.py,NONE,1.1

goodger@users.sourceforge.net goodger@users.sourceforge.net
Fri, 08 Nov 2002 15:47:53 -0800


Update of /cvsroot/python/python/nondist/peps/docutils
In directory usw-pr-cvs1:/tmp/cvs-serv8387/docutils

Added Files:
	.cvsignore __init__.py core.py frontend.py io.py nodes.py 
	optik.py roman.py statemachine.py urischemes.py utils.py 
Log Message:
local copy of Docutils for PEP processing

--- NEW FILE: .cvsignore ---
*.pyc

--- NEW FILE: __init__.py ---
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.1 $
# Date: $Date: 2002/11/08 23:47:51 $
# Copyright: This module has been placed in the public domain.

"""
This is the Docutils (Python Documentation Utilities) package.

Package Structure
=================

Modules:

- __init__.py: Contains the package docstring only (this text).

- core.py: Contains the ``Publisher`` class and ``publish()`` convenience
  function.

- frontend.py: Command-line and common 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.

- optik.py: Option parsing and command-line help; from Greg Ward's
  http://optik.sf.net/ project, included for convenience.

- roman.py: Conversion to and from Roman numerals. Courtesy of Mark
  Pilgrim (http://diveintopython.org/).

- statemachine.py: A finite state machine specialized for
  regular-expression-based text filters.

- 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.2.8'
"""``major.minor.micro`` version number.  The micro number is bumped any time
there's a change in the API incompatible with one of the front ends.  The
minor number is bumped whenever there is a project release.  The major number
will be bumped when the project is complete, and perhaps if there is a major
change in the design."""


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`; no group title implies no group, just a
    list of single options.  Runtime settings names are derived implicitly
    from long option names ("--a-setting" becomes ``settings.a_setting``) or
    explicitly from the "destination" keyword argument."""

    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."""


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

--- NEW FILE: core.py ---
# Authors: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.1 $
# Date: $Date: 2002/11/08 23:47:51 $
# 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 Component
from docutils import frontend, io, readers, parsers, writers
from docutils.frontend import OptionParser, ConfigParser


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, **defaults):
        #@@@ Add self.source & self.destination to components in future?
        option_parser = OptionParser(
            components=(settings_spec, self.parser, self.reader, self.writer),
            usage=usage, description=description)
        config = ConfigParser()
        config.read_standard_files()
        config_settings = config.get_section('options')
        frontend.make_paths_absolute(config_settings,
                                     option_parser.relative_path_settings)
        defaults.update(config_settings)
        option_parser.set_defaults(**defaults)
        return option_parser

    def get_settings(self, usage=None, description=None,
                     settings_spec=None, **defaults):
        """
        Set and return default settings (overrides in `defaults` keyword
        argument).

        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, **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, **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, **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(self.settings, source=source,
                                        source_path=source_path)

    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(
            self.settings, destination=destination,
            destination_path=destination_path)

    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):
        """
        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,
                                      **(settings_overrides or {}))
        elif settings_overrides:
            self.settings._update(settings_overrides, 'loose')
        self.set_io()
        document = self.reader.read(self.source, self.parser, self.settings)
        self.apply_transforms(document)
        output = self.writer.write(document, self.destination)
        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:
            from pprint import pformat
            print >>sys.stderr, '\n::: Document internals:'
            print >>sys.stderr, pformat(document.__dict__)
        if self.settings.dump_transforms:
            from pprint import pformat
            print >>sys.stderr, '\n::: Transforms applied:'
            print >>sys.stderr, pformat(document.transformer.applied)
        if self.settings.dump_pseudo_xml:
            print >>sys.stderr, '\n::: Pseudo-XML:'
            print >>sys.stderr, document.pformat().encode(
                'raw_unicode_escape')
        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, 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.
    - `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)

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):
    """
    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.
    """
    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)
    if settings_overrides:
        settings._update(settings_overrides, 'loose')
    pub.set_source(source, source_path)
    pub.set_destination(destination, destination_path)
    pub.publish()

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):
    """
    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.

    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.
    """
    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)
    if settings_overrides:
        settings._update(settings_overrides, 'loose')
    pub.set_source(source, source_path)
    pub.set_destination(destination_path=destination_path)
    return pub.publish()

--- NEW FILE: frontend.py ---
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.1 $
# Date: $Date: 2002/11/08 23:47:51 $
# 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.
- `Values`: Runtime settings; objects are simple structs
  (``object.attribute``).
- `ConfigParser`: Standard Docutils config file processing.
"""

__docformat__ = 'reStructuredText'

import os
import os.path
import ConfigParser as CP
import docutils
from docutils import optik
from docutils.optik import Values


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.)
    """
    config_parser = ConfigParser()
    config_parser.read(value)
    settings = config_parser.get_section('options')
    make_paths_absolute(settings, parser.relative_path_settings,
                        os.path.dirname(value))
    parser.values.__dict__.update(settings)

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) and pathdict[key]:
            pathdict[key] = os.path.normpath(
                os.path.abspath(os.path.join(base_path, pathdict[key])))


class OptionParser(optik.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.
    """

    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."""

    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'}),
         ('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'}),
         ('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}),
         ('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>'}),
         ('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>'}),
         ('Same as "--halt=info": halt processing at the slightest problem.',
          ['--strict'], {'action': 'store_const', 'const': 'info',
                         'dest': 'halt_level'}),
         ('Report debug-level system messages.',
          ['--debug'], {'action': 'store_true'}),
         ('Do not report debug-level system messages.',
          ['--no-debug'], {'action': 'store_false', 'dest': 'debug'}),
         ('Send the output of system messages (warnings) to <file>.',
          ['--warnings'], {'dest': 'warning_stream', 'metavar': '<file>'}),
         ('Specify the encoding of input text.  Default is locale-dependent.',
          ['--input-encoding', '-i'], {'metavar': '<name>'}),
         ('Specify the encoding for output.  Default is UTF-8.',
          ['--output-encoding', '-o'],
          {'metavar': '<name>', 'default': 'utf-8'}),
         ('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:
         (optik.SUPPRESS_HELP,
          ['--dump-settings'],
          {'action': 'store_true'}),
         (optik.SUPPRESS_HELP,
          ['--dump-internals'],
          {'action': 'store_true'}),
         (optik.SUPPRESS_HELP,
          ['--dump-transforms'],
          {'action': 'store_true'}),
         (optik.SUPPRESS_HELP,
          ['--dump-pseudo-xml'],
          {'action': 'store_true'}),
         (optik.SUPPRESS_HELP,
          ['--expose-internal-attribute'],
          {'action': 'append', 'dest': 'expose_internals'}),))
    """Runtime settings and command-line options common to all Docutils front
    ends.  Setting specs specific to individual Docutils components are also
    used (see `populate_from_components()`)."""

    relative_path_settings = ('warning_stream',)

    version_template = '%%prog (Docutils %s)' % docutils.__version__
    """Default version message."""

    def __init__(self, components=(), *args, **kwargs):
        """
        `components` is a list of Docutils components each containing a
        ``.settings_spec`` attribute.  `defaults` is a mapping of setting
        default overrides.
        """
        optik.OptionParser.__init__(
            self, help=None,
            format=optik.Titled(),
            # Needed when Optik is updated (replaces above 2 lines):
            #self, add_help=None,
            #formatter=optik.TitledHelpFormatter(width=78),
            *args, **kwargs)
        if not self.version:
            self.version = self.version_template
        # Internal settings with no defaults from settings specifications;
        # initialize manually:
        self.set_defaults(_source=None, _destination=None)
        # Make an instance copy (it will be modified):
        self.relative_path_settings = list(self.relative_path_settings)
        self.populate_from_components(tuple(components) + (self,))

    def populate_from_components(self, components):
        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 = optik.OptionGroup(self, title, description)
                    self.add_option_group(group)
                else:
                    group = self        # single options
                for (help_text, option_strings, kwargs) in option_spec:
                    group.add_option(help=help_text, *option_strings,
                                     **kwargs)
                i += 3
        for component in components:
            if component and component.settings_default_overrides:
                self.defaults.update(component.settings_default_overrides)

    def check_values(self, values, args):
        if hasattr(values, 'report_level'):
            values.report_level = self.check_threshold(values.report_level)
        if hasattr(values, 'halt_level'):
            values.halt_level = self.check_threshold(values.halt_level)
        values._source, values._destination = self.check_args(args)
        make_paths_absolute(values.__dict__, self.relative_path_settings,
                            os.getcwd())
        return values

    def check_threshold(self, level):
        try:
            return int(level)
        except ValueError:
            try:
                return self.thresholds[level.lower()]
            except (KeyError, AttributeError):
                self.error('Unknown threshold: %r.' % level)

    def check_args(self, args):
        source = destination = None
        if args:
            source = args.pop(0)
        if args:
            destination = args.pop(0)
        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


class ConfigParser(CP.ConfigParser):

    standard_config_files = (
        '/etc/docutils.conf',               # system-wide
        './docutils.conf',                  # project-specific
        os.path.expanduser('~/.docutils'))  # user-specific
    """Docutils configuration files, using ConfigParser syntax (section
    'options').  Later files override earlier ones."""

    def read_standard_files(self):
        self.read(self.standard_config_files)

    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, raw=0, vars=None):
        """
        Return a given section as a dictionary (empty if the section
        doesn't exist).

        All % interpolations are expanded in the return values, based on the
        defaults passed into the constructor, unless the optional argument
        `raw` is true.  Additional substitutions may be provided using the
        `vars` argument, which must be a dictionary whose contents overrides
        any pre-existing defaults.

        The section DEFAULT is special.
        """
        try:
            sectdict = self._ConfigParser__sections[section].copy()
        except KeyError:
            sectdict = {}
        d = self._ConfigParser__defaults.copy()
        d.update(sectdict)
        # Update with the entry specific variables
        if vars:
            d.update(vars)
        if raw:
            return sectdict
        # do the string interpolation
        for option in sectdict.keys():
            rawval = sectdict[option]
            value = rawval              # Make it a pretty variable name
            depth = 0
            while depth < 10:           # Loop through this until it's done
                depth += 1
                if value.find("%(") >= 0:
                    try:
                        value = value % d
                    except KeyError, key:
                        raise CP.InterpolationError(key, option, section,
                                                    rawval)
                else:
                    break
            if value.find("%(") >= 0:
                raise CP.InterpolationDepthError(option, section, rawval)
            sectdict[option] = value
        return sectdict

--- NEW FILE: io.py ---
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.1 $
# Date: $Date: 2002/11/08 23:47:51 $
# 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 docutils import TransformSpec


class Input(TransformSpec):

    """
    Abstract base class for input wrappers.
    """

    component_type = 'input'

    default_source_path = None

    def __init__(self, settings, source=None, source_path=None):
        self.settings = settings
        """A settings object with "input_encoding" and "output_encoding"
        attributes (typically a `docutils.optik.Values` object)."""

        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, reader):
        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.settings.input_encoding \
               and self.settings.input_encoding.lower() == 'unicode':
            return unicode(data)
        encodings = [self.settings.input_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:
                decoded = unicode(data, enc)
                return decoded
            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, settings, destination=None, destination_path=None):
        self.settings = settings
        """A settings object with "input_encoding" and "output_encoding"
        attributes (typically a `docutils.optik.Values` object)."""

        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.settings.output_encoding \
               and self.settings.output_encoding.lower() == 'unicode':
            return data
        else:
            return data.encode(self.settings.output_encoding or '')


class FileInput(Input):

    """
    Input for single, simple file-like objects.
    """

    def __init__(self, settings, source=None, source_path=None, autoclose=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, settings, source, source_path)
        self.autoclose = autoclose
        if source is None:
            if source_path:
                self.source = open(source_path)
            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, reader):
        """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, settings, destination=None, destination_path=None,
                 autoclose=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, settings, destination, destination_path)
        self.opened = 1
        self.autoclose = autoclose
        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):
        self.destination = open(self.destination_path, 'w')
        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, reader):
        """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, reader):
        """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

--- NEW FILE: nodes.py ---
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.1 $
# Date: $Date: 2002/11/08 23:47:51 $
# 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.
[...1394 lines suppressed...]
    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']

--- NEW FILE: optik.py ---
# This is *not* the official distribution of Optik.  See
# http://optik.sourceforge.net/ for the official distro.
#
# This combined module was converted from the "optik" package for Docutils use
# by David Goodger (2002-06-12).  Optik is slated for inclusion in the Python
# standard library as OptionParser.py.  Once Optik itself becomes a single
# module, Docutils will include the official module and kill off this
# temporary fork.

"""optik

A powerful, extensible, and easy-to-use command-line parser for Python.

By Greg Ward <gward@python.net>

See http://optik.sourceforge.net/
"""

# Copyright (c) 2001 Gregory P. Ward.  All rights reserved.
[...1315 lines suppressed...]
    """
    # Is there an exact match?
    if wordmap.has_key(s):
        return s
    else:
        # Isolate all words with s as a prefix.
        possibilities = [word for word in wordmap.keys()
                         if word.startswith(s)]
        # No exact match, so there had better be just one possibility.
        if len(possibilities) == 1:
            return possibilities[0]
        elif not possibilities:
            raise BadOptionError("no such option: %s" % s)
        else:
            # More than one possible completion: ambiguous prefix.
            raise BadOptionError("ambiguous option: %s (%s?)"
                                 % (s, ", ".join(possibilities)))

def get_prog_name ():
    return os.path.basename(sys.argv[0])

--- NEW FILE: roman.py ---
"""Convert to and from Roman numerals"""

__author__ = "Mark Pilgrim (f8dy@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


--- NEW FILE: statemachine.py ---
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.1 $
# Date: $Date: 2002/11/08 23:47:51 $
# 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.

[...1405 lines suppressed...]
    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)

--- NEW FILE: urischemes.py ---
"""
`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',}

--- NEW FILE: utils.py ---
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.1 $
# Date: $Date: 2002/11/08 23:47:51 $
# 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):
        Exception.__init__(self, system_message.astext())


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):
        """
        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).
        """
        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.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."""

    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:
            if category:
                print >>stream, msg.astext(), '[%s]' % category
            else:
                print >>stream, msg.astext()
        if level >= halt_level:
            raise SystemMessage(msg)
        if level > 0 or debug:
            self.notify_observers(msg)
        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 normalize_name(name):
    """Return a case- and whitespace-normalized name."""
    return ' '.join(name.lower().split())

def new_document(source, settings=None):
    if settings is None:
        settings = frontend.OptionParser().get_default_values()
    reporter = Reporter(source, settings.report_level, settings.halt_level,
                        settings.warning_stream, settings.debug)
    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`.

    If there is no common prefix, return the absolute path to `target`.
    """
    source_parts = os.path.abspath(source or '').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
    it's closest ancestor.
    """
    while node:
        if node.source or node.line:
            return node.source, node.line
        node = node.parent
    return None, None