Commit 4820802b authored by Andreas Jung's avatar Andreas Jung

moved to lib/python/third_party

parent 8d7ae166
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.5 $
# Date: $Date: 2004/06/19 06:33:31 $
# 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.4'
"""``major.minor.micro`` version number. The micro number is bumped for API
changes, for new functionality, and for interim project releases. The minor
number is bumped whenever there is a significant 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.
Defines runtime settings and associated command-line options, as used by
`docutils.frontend.OptionParser`. This is a tuple of:
- Option group title (string or `None` which implies no group, just a list
of single options).
- Description (string or `None`).
- A sequence of option tuples. Each consists of:
- Help text (string)
- List of option strings (e.g. ``['-Q', '--quux']``).
- Dictionary of keyword arguments. It contains arguments to the
OptionParser/OptionGroup ``add_option`` method, possibly 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. See optparse docs for more details.
- More triples of group title, description, options, as many times as
needed. Thus, `settings_spec` tuples can be simply concatenated.
"""
settings_defaults = None
"""A dictionary of defaults for settings not in `settings_spec` (internal
settings, intended to be inaccessible by command-line and config file).
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."""
unknown_reference_resolvers = ()
"""List of functions to try to resolve unknown references. Unknown
references have a 'refname' attribute which doesn't correspond to any
target in the document. Called when FinalCheckVisitor is unable to find a
correct target. The list should contain functions which will try to
resolve unknown references, with the following signature::
def reference_resolver(node):
'''Returns boolean: true if resolved, false if not.'''
If the function is able to resolve the reference, it should also remove
the 'refname' attribute and mark the node as resolved::
del node['refname']
node.resolved = 1
Each function must have a "priority" attribute which will affect the order
the unknown_reference_resolvers are run::
reference_resolver.priority = 100
Override in subclasses."""
class Component(SettingsSpec, TransformSpec):
"""Base class for Docutils components."""
component_type = None
"""Name of the component type ('reader', 'parser', 'writer'). 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
# Authors: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:38 $
# 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`. See `The Docutils Publisher`_.
.. _The Docutils Publisher: http://docutils.sf.net/docs/api/publisher.html
"""
__docformat__ = 'reStructuredText'
import sys
import pprint
from docutils import __version__, SettingsSpec
from docutils import frontend, io, utils, readers, 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 `docutils.readers.Reader` instance."""
self.parser = parser
"""A `docutils.parsers.Parser` instance."""
self.writer = writer
"""A `docutils.writers.Writer` instance."""
self.source = source
"""The source of input data, a `docutils.io.Input` instance."""
self.source_class = source_class
"""The class for dynamically created source objects."""
self.destination = destination
"""The destination for docutils output, a `docutils.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:
if 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_programmatic_settings(self, settings_spec,
settings_overrides,
config_section):
if self.settings is None:
defaults = (settings_overrides or {}).copy()
# Propagate exceptions by default when used programmatically:
defaults.setdefault('traceback', 1)
self.get_settings(settings_spec=settings_spec,
config_section=config_section,
**defaults)
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_status=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 {}))
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)
self.writer.assemble_parts()
except Exception, error:
if self.settings.traceback: # propagate exceptions?
raise
self.report_Exception(error)
exit = 1
self.debugging_dumps(document)
if (enable_exit_status and document
and (document.reporter.max_level
>= self.settings.exit_status_level)):
sys.exit(document.reporter.max_level + 10)
elif exit:
sys.exit(1)
return output
def debugging_dumps(self, document):
if self.settings.dump_settings:
print >>sys.stderr, '\n::: Runtime settings:'
print >>sys.stderr, pprint.pformat(self.settings.__dict__)
if self.settings.dump_internals and document:
print >>sys.stderr, '\n::: Document internals:'
print >>sys.stderr, pprint.pformat(document.__dict__)
if self.settings.dump_transforms and document:
print >>sys.stderr, '\n::: Transforms applied:'
print >>sys.stderr, pprint.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')
def report_Exception(self, error):
if isinstance(error, utils.SystemMessage):
self.report_SystemMessage(error)
elif isinstance(error, UnicodeError):
self.report_UnicodeError(error)
else:
print >>sys.stderr, '%s: %s' % (error.__class__.__name__, error)
print >>sys.stderr, ("""\
Exiting due to error. Use "--traceback" to diagnose.
Please report errors to <docutils-users@lists.sf.net>.
Include "--traceback" output, Docutils version (%s),
Python version (%s), your OS type & version, and the
command line used.""" % (__version__, sys.version.split()[0]))
def report_SystemMessage(self, error):
print >>sys.stderr, ('Exiting due to level-%s (%s) system message.'
% (error.level,
utils.Reporter.levels[error.level]))
def report_UnicodeError(self, error):
sys.stderr.write(
'%s: %s\n'
'\n'
'The specified output encoding (%s) cannot\n'
'handle all of the output.\n'
'Try setting "--output-encoding-error-handler" to\n'
'\n'
'* "xmlcharrefreplace" (for HTML & XML output);\n'
% (error.__class__.__name__, error,
self.settings.output_encoding))
try:
data = error.object[error.start:error.end]
sys.stderr.write(
' the output will contain "%s" and should be usable.\n'
'* "backslashreplace" (for other output formats, Python 2.3+);\n'
' look for "%s" in the output.\n'
% (data.encode('ascii', 'xmlcharrefreplace'),
data.encode('ascii', 'backslashreplace')))
except AttributeError:
sys.stderr.write(' the output should be usable as-is.\n')
sys.stderr.write(
'* "replace"; look for "?" in the output.\n'
'\n'
'"--output-encoding-error-handler" is currently set to "%s".\n'
'\n'
'Exiting due to error. Use "--traceback" to diagnose.\n'
'If the advice above doesn\'t eliminate the error,\n'
'please report it to <docutils-users@lists.sf.net>.\n'
'Include "--traceback" output, Docutils version (%s),\n'
'Python version (%s), your OS type & version, and the\n'
'command line used.\n'
% (self.settings.output_encoding_error_handler,
__version__, sys.version.split()[0]))
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_status=1, argv=None,
usage=default_usage, description=default_description):
"""
Set up & run a `Publisher` for command-line-based file I/O (input and
output file paths taken automatically from the command line). Return the
encoded string output also.
Parameters: see `publish_programmatically` for the remainder.
- `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)
output = pub.publish(
argv, usage, description, settings_spec, settings_overrides,
config_section=config_section, enable_exit_status=enable_exit_status)
return output
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_status=None):
"""
Set up & run a `Publisher` for programmatic use with file-like I/O.
Return the encoded string output also.
Parameters: see `publish_programmatically`.
"""
output, pub = publish_programmatically(
source_class=io.FileInput, source=source, source_path=source_path,
destination_class=io.FileOutput,
destination=destination, destination_path=destination_path,
reader=reader, reader_name=reader_name,
parser=parser, parser_name=parser_name,
writer=writer, writer_name=writer_name,
settings=settings, settings_spec=settings_spec,
settings_overrides=settings_overrides,
config_section=config_section,
enable_exit_status=enable_exit_status)
return output
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_status=None):
"""
Set up & run a `Publisher` for programmatic use with string I/O. Return
the encoded string or Unicode string output.
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 one way::
publish_string(..., settings_overrides={'output_encoding': 'unicode'})
Similarly for Unicode string input (`source`)::
publish_string(..., settings_overrides={'input_encoding': 'unicode'})
Parameters: see `publish_programmatically`.
"""
output, pub = publish_programmatically(
source_class=io.StringInput, source=source, source_path=source_path,
destination_class=io.StringOutput,
destination=None, destination_path=destination_path,
reader=reader, reader_name=reader_name,
parser=parser, parser_name=parser_name,
writer=writer, writer_name=writer_name,
settings=settings, settings_spec=settings_spec,
settings_overrides=settings_overrides,
config_section=config_section,
enable_exit_status=enable_exit_status)
return output
def publish_parts(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_status=None):
"""
Set up & run a `Publisher`, and return a dictionary of document parts.
Dictionary keys are the names of parts, and values are Unicode strings;
encoding is up to the client. For programmatic use with string I/O.
For encoded string input, be sure to set the 'input_encoding' setting to
the desired encoding. Set it to 'unicode' for unencoded Unicode string
input. Here's how::
publish_string(..., settings_overrides={'input_encoding': 'unicode'})
Parameters: see `publish_programmatically`.
"""
output, pub = publish_programmatically(
source_class=io.StringInput, source=source, source_path=source_path,
destination_class=io.StringOutput,
destination=None, destination_path=destination_path,
reader=reader, reader_name=reader_name,
parser=parser, parser_name=parser_name,
writer=writer, writer_name=writer_name,
settings=settings, settings_spec=settings_spec,
settings_overrides=settings_overrides,
config_section=config_section,
enable_exit_status=enable_exit_status)
return pub.writer.parts
def publish_programmatically(source_class, source, source_path,
destination_class, destination, destination_path,
reader, reader_name,
parser, parser_name,
writer, writer_name,
settings, settings_spec,
settings_overrides, config_section,
enable_exit_status):
"""
Set up & run a `Publisher` for custom programmatic use. Return the
encoded string output and the Publisher object.
Applications should not need to call this function directly. If it does
seem to be necessary to call this function directly, please write to the
docutils-develop@lists.sourceforge.net mailing list.
Parameters:
* `source_class` **required**: The class for dynamically created source
objects. Typically `io.FileInput` or `io.StringInput`.
* `source`: Type depends on `source_class`:
- `io.FileInput`: Either a file-like object (must have 'read' and
'close' methods), or ``None`` (`source_path` is opened). If neither
`source` nor `source_path` are supplied, `sys.stdin` is used.
- `io.StringInput` **required**: The input string, either 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`: Type depends on `source_class`:
- `io.FileInput`: Path to the input file, opened if no `source`
supplied.
- `io.StringInput`: Optional. Path to the file or object that produced
`source`. Only used for diagnostic output.
* `destination_class` **required**: The class for dynamically created
destination objects. Typically `io.FileOutput` or `io.StringOutput`.
* `destination`: Type depends on `destination_class`:
- `io.FileOutput`: Either a file-like object (must have 'write' and
'close' methods), or ``None`` (`destination_path` is opened). If
neither `destination` nor `destination_path` are supplied,
`sys.stdout` is used.
- `io.StringOutput`: Not used; pass ``None``.
* `destination_path`: Type depends on `destination_class`:
- `io.FileOutput`: Path to the output file. Opened if no `destination`
supplied.
- `io.StringOutput`: 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`: A runtime settings (`docutils.frontend.Values`) object, for
dotted-attribute access to runtime settings. It's the end result of the
`SettingsSpec`, config file, and option processing. If `settings` is
passed, it's assumed to be complete and no further setting/config/option
processing is done.
* `settings_spec`: A `docutils.SettingsSpec` subclass or object. Provides
extra application-specific settings definitions independently of
components. In other words, the application becomes a component, and
its settings data is processed along with that of the other components.
Used only if no `settings` specified.
* `settings_overrides`: A dictionary containing application-specific
settings defaults that override the defaults of other components.
Used only if no `settings` specified.
* `config_section`: A string, the name of the configuration file section
for this application. Overrides the ``config_section`` attribute
defined by `settings_spec`. Used only if no `settings` specified.
* `enable_exit_status`: Boolean; enable exit status at end of processing?
"""
pub = Publisher(reader, parser, writer, settings=settings,
source_class=source_class,
destination_class=destination_class)
pub.set_components(reader_name, parser_name, writer_name)
pub.process_programmatic_settings(
settings_spec, settings_overrides, config_section)
pub.set_source(source, source_path)
pub.set_destination(destination, destination_path)
output = pub.publish(enable_exit_status=enable_exit_status)
return output, pub
# Authors: David Goodger
# Contact: goodger@python.org
# Revision: $Revision: 1.1.2.1 $
# Date: $Date: 2004/05/12 19:57:38 $
# Copyright: This module has been placed in the public domain.
"""
This module contains practical examples of Docutils client code.
Importing this module is not recommended; its contents are subject to change
in future Docutils releases. Instead, it is recommended that you copy and
paste the parts you need into your own code, modifying as necessary.
"""
from docutils import core
def html_parts(input_string, source_path=None, destination_path=None,
input_encoding='unicode', doctitle=1, initial_header_level=1):
"""
Given an input string, returns a dictionary of HTML document parts.
Dictionary keys are the names of parts, and values are Unicode strings;
encoding is up to the client.
Parameters:
- `input_string`: A multi-line text string; required.
- `source_path`: Path to the source file or object. Optional, but useful
for diagnostic output (system messages).
- `destination_path`: Path to the file or object which will receive the
output; optional. Used for determining relative paths (stylesheets,
source links, etc.).
- `input_encoding`: The encoding of `input_string`. If it is an encoded
8-bit string, provide the correct encoding. If it is a Unicode string,
use "unicode", the default.
- `doctitle`: Disable the promotion of a lone top-level section title to
document title (and subsequent section title to document subtitle
promotion); enabled by default.
- `initial_header_level`: The initial level for header elements (e.g. 1
for "<h1>").
"""
overrides = {'input_encoding': input_encoding,
'doctitle_xform': doctitle,
'initial_header_level': initial_header_level}
parts = core.publish_parts(
source=input_string, source_path=source_path,
destination_path=destination_path,
writer_name='html', settings_overrides=overrides)
return parts
def html_fragment(input_string, source_path=None, destination_path=None,
input_encoding='unicode', output_encoding='unicode',
doctitle=1, initial_header_level=1):
"""
Given an input string, returns an HTML fragment as a string.
The return value is the contents of the <body> tag, less the title,
subtitle, and docinfo.
Parameters (see `html_parts()` for the remainder):
- `output_encoding`: The desired encoding of the output. If a Unicode
string is desired, use the default value of "unicode" .
"""
parts = html_parts(
input_string=input_string, source_path=source_path,
destination_path=destination_path,
input_encoding=input_encoding, doctitle=doctitle,
initial_header_level=initial_header_level)
fragment = parts['fragment']
if output_encoding != 'unicode':
fragment = fragment.encode(output_encoding)
return fragment
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.4.8.1 $
# Date: $Date: 2004/05/12 19:57:38 $
# 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', 'xmlcharrefreplace'):
raise (LookupError(
'unknown encoding error handler: "%s" (choices: '
'"strict", "ignore", "replace", or "xmlcharrefreplace")' % 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:
return int(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 validate_url_trailing_slash(
setting, value, option_parser, config_parser=None, config_section=None):
if not value:
return './'
elif value.endswith('/'):
return value
else:
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'}),
('Disable Docutils section numbering',
['--no-section-numbering'],
{'action': 'store_false', 'dest': 'sectnum_xform',
'default': 1, 'validator': validate_boolean}),
('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 by '
'exiting (or propagating the exception if --traceback set). '
'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-status'], {'choices': threshold_choices,
'dest': 'exit_status_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 halt-level system messages and '
'other exceptions occur. Useful for debugging, and essential for '
'issue reports.',
['--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 error handler for unencodable '
'characters, after a colon (":"); default is "strict". (See '
'"--output-encoding-error-encoding".)',
['--output-encoding', '-o'],
{'metavar': '<name[:handler]>', 'default': 'utf-8',
'validator': validate_encoding_and_error_handler}),
('Specify the error handler for unencodable characters in '
'the output. Acceptable values include "strict", "ignore", '
'"replace", "xmlcharrefreplace", and '
'"backslashreplace" (in Python 2.3+). Default is "strict". '
'Usually specified as part of --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 error handler for unencodable '
'characters, after a colon (":"); default is "%s". (See '
'"--output-encoding-error-encoding".'
% default_error_encoding_error_handler,
['--error-encoding', '-e'],
{'metavar': '<name[:handler]>', 'default': 'ascii',
'validator': validate_encoding_and_error_handler}),
('Specify the error handler for unencodable characters in '
'error output. See --output-encoding-error-handler for acceptable '
'values. Default is "%s". Usually specified as part of '
'--error-encoding.' % default_error_encoding_error_handler,
['--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,
'_source': None,
'_destination': 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)
self.set_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)
self.set_defaults(**config_settings.__dict__)
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
settings_spec = component.settings_spec
self.relative_path_settings.extend(
component.relative_path_settings)
for i in range(0, len(settings_spec), 3):
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)
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/user/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."""
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:38 $
# 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
try:
import locale
except:
pass
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):
"""`data` is a Unicode string, to be encoded by `self.encode`."""
raise NotImplementedError
def encode(self, data):
if self.encoding and self.encoding.lower() == 'unicode':
return data
else:
try:
return data.encode(self.encoding, self.error_handler)
except ValueError:
# ValueError is raised if there are unencodable chars
# in data and the error_handler isn't found.
if self.error_handler == 'xmlcharrefreplace':
# We are using xmlcharrefreplace with a Python
# version that doesn't support it (2.1 or 2.2), so
# we emulate its behavior.
return ''.join([self.xmlcharref_encode(char) for char in data])
else:
raise
def xmlcharref_encode(self, char):
"""Emulate Python 2.3's 'xmlcharrefreplace' encoding error handler."""
try:
return char.encode(self.encoding, 'strict')
except UnicodeError:
return '&#%i;' % ord(char)
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 (%r). 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 (Unicode string).
"""
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 '
'(%r). Exiting.' % self.destination_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
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:42 $
# Copyright: This module has been placed in the public domain.
# Internationalization details are documented in
# <http://docutils.sf.net/spec/howto/i18n.html>.
"""
This package contains modules for language-dependent features of Docutils.
"""
__docformat__ = 'reStructuredText'
_languages = {}
def get_language(language_code):
if _languages.has_key(language_code):
return _languages[language_code]
try:
module = __import__(language_code, globals(), locals())
except ImportError:
return None
_languages[language_code] = module
return module
# Author: Jannie Hofmeyr
# Contact: jhsh@sun.ac.za
# Revision: $Revision: 1.1.2.3.8.1 $
# Date: $Date: 2004/05/12 19:57:42 $
# Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
Afrikaans-language mappings for language-dependent features of Docutils.
"""
__docformat__ = 'reStructuredText'
labels = {
'author': 'Auteur',
'authors': 'Auteurs',
'organization': 'Organisasie',
'address': 'Adres',
'contact': 'Kontak',
'version': 'Weergawe',
'revision': 'Revisie',
'status': 'Status',
'date': 'Datum',
'copyright': 'Kopiereg',
'dedication': 'Opdrag',
'abstract': 'Opsomming',
'attention': 'Aandag!',
'caution': 'Wees versigtig!',
'danger': '!GEVAAR!',
'error': 'Fout',
'hint': 'Wenk',
'important': 'Belangrik',
'note': 'Nota',
'tip': 'Tip', # hint and tip both have the same translation: wenk
'warning': 'Waarskuwing',
'contents': 'Inhoud'}
"""Mapping of node class name to label text."""
bibliographic_fields = {
'auteur': 'author',
'auteurs': 'authors',
'organisasie': 'organization',
'adres': 'address',
'kontak': 'contact',
'weergawe': 'version',
'revisie': 'revision',
'status': 'status',
'datum': 'date',
'kopiereg': 'copyright',
'opdrag': 'dedication',
'opsomming': 'abstract'}
"""Afrikaans (lowcased) to canonical name mapping for bibliographic fields."""
author_separators = [';', ',']
"""List of separator strings for the 'Authors' bibliographic field. Tried in
order."""
# Author: Marek Blaha
# Contact: mb@dat.cz
# Revision: $Revision: 1.1.2.1 $
# Date: $Date: 2004/05/12 19:57:42 $
# Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
Czech-language mappings for language-dependent features of Docutils.
"""
__docformat__ = 'reStructuredText'
labels = {
# fixed: language-dependent
'author': u'Autor',
'authors': u'Auto\u0159i',
'organization': u'Organizace',
'address': u'Adresa',
'contact': u'Kontakt',
'version': u'Verze',
'revision': u'Revize',
'status': u'Stav',
'date': u'Datum',
'copyright': u'Copyright',
'dedication': u'V\u011Bnov\u00E1n\u00ED',
'abstract': u'Abstrakt',
'attention': u'Pozor!',
'caution': u'Opatrn\u011B!',
'danger': u'!NEBEZPE\u010C\u00CD!',
'error': u'Chyba',
'hint': u'Rada',
'important': u'D\u016Fle\u017Eit\u00E9',
'note': u'Pozn\u00E1mka',
'tip': u'Tip',
'warning': u'Varov\u00E1n\u00ED',
'contents': u'Obsah'}
"""Mapping of node class name to label text."""
bibliographic_fields = {
# language-dependent: fixed
u'autor': 'author',
u'auto\u0159i': 'authors',
u'organizace': 'organization',
u'adresa': 'address',
u'kontakt': 'contact',
u'verze': 'version',
u'revize': 'revision',
u'stav': 'status',
u'datum': 'date',
u'copyright': 'copyright',
u'v\u011Bnov\u00E1n\u00ED': 'dedication',
u'abstrakt': 'abstract'}
"""Czech (lowcased) to canonical name mapping for bibliographic fields."""
author_separators = [';', ',']
"""List of separator strings for the 'Authors' bibliographic field. Tried in
order."""
# Authors: David Goodger; Gunnar Schwant
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:42 $
# Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
German language mappings for language-dependent features of Docutils.
"""
__docformat__ = 'reStructuredText'
labels = {
'author': 'Autor',
'authors': 'Autoren',
'organization': 'Organisation',
'address': 'Adresse',
'contact': 'Kontakt',
'version': 'Version',
'revision': 'Revision',
'status': 'Status',
'date': 'Datum',
'dedication': 'Widmung',
'copyright': 'Copyright',
'abstract': 'Zusammenfassung',
'attention': 'Achtung!',
'caution': 'Vorsicht!',
'danger': '!GEFAHR!',
'error': 'Fehler',
'hint': 'Hinweis',
'important': 'Wichtig',
'note': 'Bemerkung',
'tip': 'Tipp',
'warning': 'Warnung',
'contents': 'Inhalt'}
"""Mapping of node class name to label text."""
bibliographic_fields = {
'autor': 'author',
'autoren': 'authors',
'organisation': 'organization',
'adresse': 'address',
'kontakt': 'contact',
'version': 'version',
'revision': 'revision',
'status': 'status',
'datum': 'date',
'copyright': 'copyright',
'widmung': 'dedication',
'zusammenfassung': 'abstract'}
"""German (lowcased) to canonical name mapping for bibliographic fields."""
author_separators = [';', ',']
"""List of separator strings for the 'Authors' bibliographic field. Tried in
order."""
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:42 $
# Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
English-language mappings for language-dependent features of Docutils.
"""
__docformat__ = 'reStructuredText'
labels = {
# fixed: language-dependent
'author': 'Author',
'authors': 'Authors',
'organization': 'Organization',
'address': 'Address',
'contact': 'Contact',
'version': 'Version',
'revision': 'Revision',
'status': 'Status',
'date': 'Date',
'copyright': 'Copyright',
'dedication': 'Dedication',
'abstract': 'Abstract',
'attention': 'Attention!',
'caution': 'Caution!',
'danger': '!DANGER!',
'error': 'Error',
'hint': 'Hint',
'important': 'Important',
'note': 'Note',
'tip': 'Tip',
'warning': 'Warning',
'contents': 'Contents'}
"""Mapping of node class name to label text."""
bibliographic_fields = {
# language-dependent: fixed
'author': 'author',
'authors': 'authors',
'organization': 'organization',
'address': 'address',
'contact': 'contact',
'version': 'version',
'revision': 'revision',
'status': 'status',
'date': 'date',
'copyright': 'copyright',
'dedication': 'dedication',
'abstract': 'abstract'}
"""English (lowcased) to canonical name mapping for bibliographic fields."""
author_separators = [';', ',']
"""List of separator strings for the 'Authors' bibliographic field. Tried in
order."""
# Author: Marcelo Huerta San Martin
# Contact: richieadler@users.sourceforge.net
# Revision: $Revision: 1.1.2.1.8.1 $
# Date: $Date: 2004/05/12 19:57:42 $
# Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
Esperanto-language mappings for language-dependent features of Docutils.
"""
__docformat__ = 'reStructuredText'
labels = {
# fixed: language-dependent
'author': u'A\u016dtoro',
'authors': u'A\u016dtoroj',
'organization': u'Organizo',
'address': u'Adreso',
'contact': u'Kontakto',
'version': u'Versio',
'revision': u'Revido',
'status': u'Stato',
'date': u'Dato',
# 'copyright': u'Kopirajto',
'copyright': u'A\u016dtorrajto',
'dedication': u'Dedi\u0109o',
'abstract': u'Resumo',
'attention': u'Atentu!',
'caution': u'Zorgu!',
'danger': u'DAN\u011cERO!',
'error': u'Eraro',
'hint': u'Spuro',
'important': u'Grava',
'note': u'Noto',
'tip': u'Helpeto',
'warning': u'Averto',
'contents': u'Enhavo'}
"""Mapping of node class name to label text."""
bibliographic_fields = {
# language-dependent: fixed
'a\u016dtoro': 'author',
'a\u016dtoroj': 'authors',
'organizo': 'organization',
'adreso': 'address',
'kontakto': 'contact',
'versio': 'version',
'revido': 'revision',
'stato': 'status',
'dato': 'date',
'a\u016dtorrajto': 'copyright',
'dedi\u0109o': 'dedication',
'resumo': 'abstract'}
"""Esperanto (lowcased) to canonical name mapping for bibliographic fields."""
author_separators = [';', ',']
"""List of separator strings for the 'Authors' bibliographic field. Tried in
order."""
# -*- coding: iso-8859-1 -*-
# Author: Marcelo Huerta San Martn
# Contact: mghsm@uol.com.ar
# Revision: $Revision: 1.1.2.3.8.1 $
# Date: $Date: 2004/05/12 19:57:42 $
# Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
Spanish-language mappings for language-dependent features of Docutils.
"""
__docformat__ = 'reStructuredText'
labels = {
'author': u'Autor',
'authors': u'Autores',
'organization': u'Organizaci\u00f3n',
'address': u'Direcci\u00f3n',
'contact': u'Contacto',
'version': u'Versi\u00f3n',
'revision': u'Revisi\u00f3n',
'status': u'Estado',
'date': u'Fecha',
'copyright': u'Copyright',
'dedication': u'Dedicatoria',
'abstract': u'Resumen',
'attention': u'\u00a1Atenci\u00f3n!',
'caution': u'\u00a1Precauci\u00f3n!',
'danger': u'\u00a1PELIGRO!',
'error': u'Error',
'hint': u'Sugerencia',
'important': u'Importante',
'note': u'Nota',
'tip': u'Consejo',
'warning': u'Advertencia',
'contents': u'Contenido'}
"""Mapping of node class name to label text."""
bibliographic_fields = {
u'autor': 'author',
u'autores': 'authors',
u'organizaci\u00f3n': 'organization',
u'direcci\u00f3n': 'address',
u'contacto': 'contact',
u'versi\u00f3n': 'version',
u'revisi\u00f3n': 'revision',
u'estado': 'status',
u'fecha': 'date',
u'copyright': 'copyright',
u'dedicatoria': 'dedication',
u'resumen': 'abstract'}
"""Spanish (lowcased) to canonical name mapping for bibliographic fields."""
author_separators = [';', ',']
"""List of separator strings for the 'Authors' bibliographic field. Tried in
order."""
# Author: Stefane Fermigier
# Contact: sf@fermigier.com
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:42 $
# Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
French-language mappings for language-dependent features of Docutils.
"""
__docformat__ = 'reStructuredText'
labels = {
u'author': u'Auteur',
u'authors': u'Auteurs',
u'organization': u'Organisation',
u'address': u'Adresse',
u'contact': u'Contact',
u'version': u'Version',
u'revision': u'R\u00e9vision',
u'status': u'Statut',
u'date': u'Date',
u'copyright': u'Copyright',
u'dedication': u'D\u00e9dicace',
u'abstract': u'R\u00e9sum\u00e9',
u'attention': u'Attention!',
u'caution': u'Avertissement!',
u'danger': u'!DANGER!',
u'error': u'Erreur',
u'hint': u'Indication',
u'important': u'Important',
u'note': u'Note',
u'tip': u'Astuce',
u'warning': u'Avis',
u'contents': u'Sommaire'}
"""Mapping of node class name to label text."""
bibliographic_fields = {
u'auteur': u'author',
u'auteurs': u'authors',
u'organisation': u'organization',
u'adresse': u'address',
u'contact': u'contact',
u'version': u'version',
u'r\u00e9vision': u'revision',
u'statut': u'status',
u'date': u'date',
u'copyright': u'copyright',
u'd\u00e9dicace': u'dedication',
u'r\u00e9sum\u00e9': u'abstract'}
"""French (lowcased) to canonical name mapping for bibliographic fields."""
author_separators = [';', ',']
"""List of separator strings for the 'Authors' bibliographic field. Tried in
order."""
# Author: Nicola Larosa
# Contact: docutils@tekNico.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:42 $
# Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
Italian-language mappings for language-dependent features of Docutils.
"""
__docformat__ = 'reStructuredText'
labels = {
'author': 'Autore',
'authors': 'Autori',
'organization': 'Organizzazione',
'address': 'Indirizzo',
'contact': 'Contatti',
'version': 'Versione',
'revision': 'Revisione',
'status': 'Status',
'date': 'Data',
'copyright': 'Copyright',
'dedication': 'Dedica',
'abstract': 'Riassunto',
'attention': 'Attenzione!',
'caution': 'Cautela!',
'danger': '!PERICOLO!',
'error': 'Errore',
'hint': 'Suggerimento',
'important': 'Importante',
'note': 'Nota',
'tip': 'Consiglio',
'warning': 'Avvertenza',
'contents': 'Indice'}
"""Mapping of node class name to label text."""
bibliographic_fields = {
'autore': 'author',
'autori': 'authors',
'organizzazione': 'organization',
'indirizzo': 'address',
'contatti': 'contact',
'versione': 'version',
'revisione': 'revision',
'status': 'status',
'data': 'date',
'copyright': 'copyright',
'dedica': 'dedication',
'riassunto': 'abstract'}
"""Italian (lowcased) to canonical name mapping for bibliographic fields."""
author_separators = [';', ',']
"""List of separator strings for the 'Authors' bibliographic field. Tried in
order."""
# -*- coding: iso-8859-1 -*-
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.1.2.1 $
# Date: $Date: 2004/05/12 19:57:42 $
# Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
Brazilian Portuguese-language mappings for language-dependent features of Docutils.
"""
__docformat__ = 'reStructuredText'
labels = {
# fixed: language-dependent
'author': u'Autor',
'authors': u'Autores',
'organization': u'Organiza\u00E7\u00E3o',
'address': u'Endere\u00E7o',
'contact': u'Contato',
'version': u'Vers\u00E3o',
'revision': u'Revis\u00E3o',
'status': u'Estado',
'date': u'Data',
'copyright': u'Copyright',
'dedication': u'Dedicat\u00F3ria',
'abstract': u'Resumo',
'attention': u'Atten\u00E7\u00E3o!',
'caution': u'Cuidado!',
'danger': u'PERIGO!',
'error': u'Erro',
'hint': u'Sugest\u00E3o',
'important': u'Importante',
'note': u'Nota',
'tip': u'Dica',
'warning': u'Aviso',
'contents': u'Sum\u00E1rio'}
"""Mapping of node class name to label text."""
bibliographic_fields = {
# language-dependent: fixed
u'autor': 'author',
u'autores': 'authors',
u'organiza\u00E7\u00E3o': 'organization',
u'endere\u00E7o': 'address',
u'contato': 'contact',
u'vers\u00E3o': 'version',
u'revis\u00E3o': 'revision',
u'estado': 'status',
u'data': 'date',
u'copyright': 'copyright',
u'dedicat\u00F3ria': 'dedication',
u'resumo': 'abstract'}
"""Brazilian Portuguese (lowcased) to canonical name mapping for bibliographic fields."""
author_separators = [';', ',']
"""List of separator strings for the 'Authors' bibliographic field. Tried in
order."""
# Author: Roman Suzi
# Contact: rnd@onego.ru
# Revision: $Revision: 1.1.2.3.8.1 $
# Date: $Date: 2004/05/12 19:57:42 $
# Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
Russian-language mappings for language-dependent features of Docutils.
"""
__docformat__ = 'reStructuredText'
labels = {
u'abstract': u'\u0410\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u044f',
u'address': u'\u0410\u0434\u0440\u0435\u0441',
u'attention': u'\u0412\u043d\u0438\u043c\u0430\u043d\u0438\u0435!',
u'author': u'\u0410\u0432\u0442\u043e\u0440',
u'authors': u'\u0410\u0432\u0442\u043e\u0440\u044b',
u'caution': u'\u041e\u0441\u0442\u043e\u0440\u043e\u0436\u043d\u043e!',
u'contact': u'\u041a\u043e\u043d\u0442\u0430\u043a\u0442',
u'contents':
u'\u0421\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435',
u'copyright': u'\u041f\u0440\u0430\u0432\u0430 '
u'\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f',
u'danger': u'\u041e\u041f\u0410\u0421\u041d\u041e!',
u'date': u'\u0414\u0430\u0442\u0430',
u'dedication':
u'\u041f\u043e\u0441\u0432\u044f\u0449\u0435\u043d\u0438\u0435',
u'error': u'\u041e\u0448\u0438\u0431\u043a\u0430',
u'hint': u'\u0421\u043e\u0432\u0435\u0442',
u'important': u'\u0412\u0430\u0436\u043d\u043e',
u'note': u'\u041f\u0440\u0438\u043c\u0435\u0447\u0430\u043d\u0438\u0435',
u'organization':
u'\u041e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044f',
u'revision': u'\u0420\u0435\u0434\u0430\u043a\u0446\u0438\u044f',
u'status': u'\u0421\u0442\u0430\u0442\u0443\u0441',
u'tip': u'\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430',
u'version': u'\u0412\u0435\u0440\u0441\u0438\u044f',
u'warning': u'\u041f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436'
u'\u0434\u0435\u043d\u0438\u0435'}
"""Mapping of node class name to label text."""
bibliographic_fields = {
u'\u0410\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u044f': u'abstract',
u'\u0410\u0434\u0440\u0435\u0441': u'address',
u'\u0410\u0432\u0442\u043e\u0440': u'author',
u'\u0410\u0432\u0442\u043e\u0440\u044b': u'authors',
u'\u041a\u043e\u043d\u0442\u0430\u043a\u0442': u'contact',
u'\u041f\u0440\u0430\u0432\u0430 \u043a\u043e\u043f\u0438\u0440\u043e'
u'\u0432\u0430\u043d\u0438\u044f': u'copyright',
u'\u0414\u0430\u0442\u0430': u'date',
u'\u041f\u043e\u0441\u0432\u044f\u0449\u0435\u043d\u0438\u0435':
u'dedication',
u'\u041e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044f':
u'organization',
u'\u0420\u0435\u0434\u0430\u043a\u0446\u0438\u044f': u'revision',
u'\u0421\u0442\u0430\u0442\u0443\u0441': u'status',
u'\u0412\u0435\u0440\u0441\u0438\u044f': u'version'}
"""Russian (lowcased) to canonical name mapping for bibliographic fields."""
author_separators = [';', ',']
"""List of separator strings for the 'Authors' bibliographic field. Tried in
order."""
# :Author: Miroslav Vasko
# :Contact: zemiak@zoznam.sk
# :Revision: $Revision: 1.2.10.3.8.1 $
# :Date: $Date: 2004/05/12 19:57:42 $
# :Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
Slovak-language mappings for language-dependent features of Docutils.
"""
__docformat__ = 'reStructuredText'
labels = {
'author': u'Autor',
'authors': u'Autori',
'organization': u'Organiz\u00E1cia',
'address': u'Adresa',
'contact': u'Kontakt',
'version': u'Verzia',
'revision': u'Rev\u00EDzia',
'status': u'Stav',
'date': u'D\u00E1tum',
'copyright': u'Copyright',
'dedication': u'Venovanie',
'abstract': u'Abstraktne',
'attention': u'Pozor!',
'caution': u'Opatrne!',
'danger': u'!NEBEZPE\u010cENSTVO!',
'error': u'Chyba',
'hint': u'Rada',
'important': u'D\u00F4le\u017Eit\u00E9',
'note': u'Pozn\u00E1mka',
'tip': u'Tip',
'warning': u'Varovanie',
'contents': u'Obsah'}
"""Mapping of node class name to label text."""
bibliographic_fields = {
u'autor': 'author',
u'autori': 'authors',
u'organiz\u00E1cia': 'organization',
u'adresa': 'address',
u'kontakt': 'contact',
u'verzia': 'version',
u'rev\u00EDzia': 'revision',
u'stav': 'status',
u'd\u00E1tum': 'date',
u'copyright': 'copyright',
u'venovanie': 'dedication',
u'abstraktne': 'abstract'}
"""Slovak (lowcased) to canonical name mapping for bibliographic fields."""
author_separators = [';', ',']
"""List of separator strings for the 'Authors' bibliographic field. Tried in
order."""
# Author: Adam Chodorowski
# Contact: chodorowski@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:42 $
# Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
Swedish language mappings for language-dependent features of Docutils.
"""
__docformat__ = 'reStructuredText'
labels = {
'author': u'F\u00f6rfattare',
'authors': u'F\u00f6rfattare',
'organization': u'Organisation',
'address': u'Adress',
'contact': u'Kontakt',
'version': u'Version',
'revision': u'Revision',
'status': u'Status',
'date': u'Datum',
'copyright': u'Copyright',
'dedication': u'Dedikation',
'abstract': u'Sammanfattning',
'attention': u'Observera!',
'caution': u'Varning!',
'danger': u'FARA!',
'error': u'Fel',
'hint': u'V\u00e4gledning',
'important': u'Viktigt',
'note': u'Notera',
'tip': u'Tips',
'warning': u'Varning',
'contents': u'Inneh\u00e5ll' }
"""Mapping of node class name to label text."""
bibliographic_fields = {
# 'Author' and 'Authors' identical in Swedish; assume the plural:
u'f\u00f6rfattare': 'authors',
u' n/a': 'author',
u'organisation': 'organization',
u'adress': 'address',
u'kontakt': 'contact',
u'version': 'version',
u'revision': 'revision',
u'status': 'status',
u'datum': 'date',
u'copyright': 'copyright',
u'dedikation': 'dedication',
u'sammanfattning': 'abstract' }
"""Swedish (lowcased) to canonical name mapping for bibliographic fields."""
author_separators = [';', ',']
"""List of separator strings for the 'Authors' bibliographic field. Tried in
order."""
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:38 $
# 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/docs/ref/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.
If passing children to `__init__()`, make sure to set `text` to
``''`` or some other suitable value.
"""
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
indirect_reference_name = None
"""Holds the whitespace_normalized_name (contains mixed case) of a target"""
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)
# "note" here is an imperative verb: "take note of".
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 info(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 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(General, 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 info 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('%s visiting unknown node type: %s'
% (self.__class__, node.__class__.__name__))
def unknown_departure(self, node):
"""
Called before exiting unknown `Node` types.
Raise exception unless overridden.
"""
raise NotImplementedError('%s departing unknown node type: %s'
% (self.__class__, 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.
"""
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)
def _nop(self, node):
pass
def _add_node_class_names(names):
"""Save typing with dynamic assignments:"""
for _name in names:
setattr(GenericNodeVisitor, "visit_" + _name, _call_default_visit)
setattr(GenericNodeVisitor, "depart_" + _name, _call_default_departure)
setattr(SparseNodeVisitor, 'visit_' + _name, _nop)
setattr(SparseNodeVisitor, 'depart' + _name, _nop)
_add_node_class_names(node_class_names)
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 [&iexcl;-&yuml;]
escape {unicode}|\\[ -~&iexcl;-&yuml;]
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())
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:45 $
# Copyright: This module has been placed in the public domain.
"""
This package contains Docutils parser modules.
"""
__docformat__ = 'reStructuredText'
from docutils import Component
class Parser(Component):
component_type = 'parser'
config_section = 'parsers'
def parse(self, inputstring, document):
"""Override to parse `inputstring` into document tree `document`."""
raise NotImplementedError('subclass must override this method')
def setup_parse(self, inputstring, document):
"""Initial parse setup. Call at start of `self.parse()`."""
self.inputstring = inputstring
self.document = document
document.reporter.attach_observer(document.note_parse_message)
def finish_parse(self):
"""Finalize parse details. Call at end of `self.parse()`."""
self.document.reporter.detach_observer(
self.document.note_parse_message)
_parser_aliases = {
'restructuredtext': 'rst',
'rest': 'rst',
'restx': 'rst',
'rtxt': 'rst',}
def get_parser_class(parser_name):
"""Return the Parser class from the `parser_name` module."""
parser_name = parser_name.lower()
if _parser_aliases.has_key(parser_name):
parser_name = _parser_aliases[parser_name]
module = __import__(parser_name, globals(), locals())
return module.Parser
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:46 $
# Copyright: This module has been placed in the public domain.
"""
This is ``docutils.parsers.rst`` package. It exports a single class, `Parser`,
the reStructuredText parser.
Usage
=====
1. Create a parser::
parser = docutils.parsers.rst.Parser()
Several optional arguments may be passed to modify the parser's behavior.
Please see `Customizing the Parser`_ below for details.
2. Gather input (a multi-line string), by reading a file or the standard
input::
input = sys.stdin.read()
3. Create a new empty `docutils.nodes.document` tree::
document = docutils.utils.new_document(source, settings)
See `docutils.utils.new_document()` for parameter details.
4. Run the parser, populating the document tree::
parser.parse(input, document)
Parser Overview
===============
The reStructuredText parser is implemented as a state machine, examining its
input one line at a time. To understand how the parser works, please first
become familiar with the `docutils.statemachine` module, then see the
`states` module.
Customizing the Parser
----------------------
Anything that isn't already customizable is that way simply because that type
of customizability hasn't been implemented yet. Patches welcome!
When instantiating an object of the `Parser` class, two parameters may be
passed: ``rfc2822`` and ``inliner``. Pass ``rfc2822=1`` to enable an initial
RFC-2822 style header block, parsed as a "field_list" element (with "class"
attribute set to "rfc2822"). Currently this is the only body-level element
which is customizable without subclassing. (Tip: subclass `Parser` and change
its "state_classes" and "initial_state" attributes to refer to new classes.
Contact the author if you need more details.)
The ``inliner`` parameter takes an instance of `states.Inliner` or a subclass.
It handles inline markup recognition. A common extension is the addition of
further implicit hyperlinks, like "RFC 2822". This can be done by subclassing
`states.Inliner`, adding a new method for the implicit markup, and adding a
``(pattern, method)`` pair to the "implicit_dispatch" attribute of the
subclass. See `states.Inliner.implicit_inline()` for details. Explicit
inline markup can be customized in a `states.Inliner` subclass via the
``patterns.initial`` and ``dispatch`` attributes (and new methods as
appropriate).
"""
__docformat__ = 'reStructuredText'
import docutils.parsers
import docutils.statemachine
from docutils.parsers.rst import states
from docutils import frontend
class Parser(docutils.parsers.Parser):
"""The reStructuredText parser."""
supported = ('restructuredtext', 'rst', 'rest', 'restx', 'rtxt', 'rstx')
"""Aliases this parser supports."""
settings_spec = (
'reStructuredText Parser Options',
None,
(('Recognize and link to standalone PEP references (like "PEP 258").',
['--pep-references'],
{'action': 'store_true', 'validator': frontend.validate_boolean}),
('Base URL for PEP references '
'(default "http://www.python.org/peps/").',
['--pep-base-url'],
{'metavar': '<URL>', 'default': 'http://www.python.org/peps/',
'validator': frontend.validate_url_trailing_slash}),
('Recognize and link to standalone RFC references (like "RFC 822").',
['--rfc-references'],
{'action': 'store_true', 'validator': frontend.validate_boolean}),
('Base URL for RFC references (default "http://www.faqs.org/rfcs/").',
['--rfc-base-url'],
{'metavar': '<URL>', 'default': 'http://www.faqs.org/rfcs/',
'validator': frontend.validate_url_trailing_slash}),
('Set number of spaces for tab expansion (default 8).',
['--tab-width'],
{'metavar': '<width>', 'type': 'int', 'default': 8}),
('Remove spaces before footnote references.',
['--trim-footnote-reference-space'],
{'action': 'store_true', 'validator': frontend.validate_boolean}),))
config_section = 'restructuredtext parser'
config_section_dependencies = ('parsers',)
def __init__(self, rfc2822=None, inliner=None):
if rfc2822:
self.initial_state = 'RFC2822Body'
else:
self.initial_state = 'Body'
self.state_classes = states.state_classes
self.inliner = inliner
def parse(self, inputstring, document):
"""Parse `inputstring` and populate `document`, a document tree."""
self.setup_parse(inputstring, document)
debug = document.reporter[''].debug
self.statemachine = states.RSTStateMachine(
state_classes=self.state_classes,
initial_state=self.initial_state,
debug=debug)
inputlines = docutils.statemachine.string2lines(
inputstring, tab_width=document.settings.tab_width,
convert_whitespace=1)
self.statemachine.run(inputlines, document, inliner=self.inliner)
self.finish_parse()
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.4.8.1 $
# Date: $Date: 2004/05/12 19:57:48 $
# Copyright: This module has been placed in the public domain.
"""
This package contains directive implementation modules.
The interface for directive functions is as follows::
def directive_fn(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
code...
# Set function attributes:
directive_fn.arguments = ...
directive_fn.options = ...
direcitve_fn.content = ...
Parameters:
- ``name`` is the directive type or name (string).
- ``arguments`` is a list of positional arguments (strings).
- ``options`` is a dictionary mapping option names (strings) to values (type
depends on option conversion functions; see below).
- ``content`` is a list of strings, the directive content.
- ``lineno`` is the line number of the first line of the directive.
- ``content_offset`` is the line offset of the first line of the content from
the beginning of the current input. Used when initiating a nested parse.
- ``block_text`` is a string containing the entire directive. Include it as
the content of a literal block in a system message if there is a problem.
- ``state`` is the state which called the directive function.
- ``state_machine`` is the state machine which controls the state which called
the directive function.
Function attributes, interpreted by the directive parser (which calls the
directive function):
- ``arguments``: A 3-tuple specifying the expected positional arguments, or
``None`` if the directive has no arguments. The 3 items in the tuple are
``(required, optional, whitespace OK in last argument)``:
1. The number of required arguments.
2. The number of optional arguments.
3. A boolean, indicating if the final argument may contain whitespace.
Arguments are normally single whitespace-separated words. The final
argument may contain whitespace if the third item in the argument spec tuple
is 1/True. If the form of the arguments is more complex, specify only one
argument (either required or optional) and indicate that final whitespace is
OK; the client code must do any context-sensitive parsing.
- ``options``: A dictionary, mapping known option names to conversion
functions such as `int` or `float`. ``None`` or an empty dict implies no
options to parse. Several directive option conversion functions are defined
in this module.
Option conversion functions take a single parameter, the option argument (a
string or ``None``), validate it and/or convert it to the appropriate form.
Conversion functions may raise ``ValueError`` and ``TypeError`` exceptions.
- ``content``: A boolean; true if content is allowed. Client code must handle
the case where content is required but not supplied (an empty content list
will be supplied).
Directive functions return a list of nodes which will be inserted into the
document tree at the point where the directive was encountered (can be an
empty list).
See `Creating reStructuredText Directives`_ for more information.
.. _Creating reStructuredText Directives:
http://docutils.sourceforge.net/docs/howto/rst-directives.html
"""
__docformat__ = 'reStructuredText'
import re
from docutils import nodes
from docutils.parsers.rst.languages import en as _fallback_language_module
_directive_registry = {
'attention': ('admonitions', 'attention'),
'caution': ('admonitions', 'caution'),
'danger': ('admonitions', 'danger'),
'error': ('admonitions', 'error'),
'important': ('admonitions', 'important'),
'note': ('admonitions', 'note'),
'tip': ('admonitions', 'tip'),
'hint': ('admonitions', 'hint'),
'warning': ('admonitions', 'warning'),
'admonition': ('admonitions', 'admonition'),
'sidebar': ('body', 'sidebar'),
'topic': ('body', 'topic'),
'line-block': ('body', 'line_block'),
'parsed-literal': ('body', 'parsed_literal'),
'rubric': ('body', 'rubric'),
'epigraph': ('body', 'epigraph'),
'highlights': ('body', 'highlights'),
'pull-quote': ('body', 'pull_quote'),
#'questions': ('body', 'question_list'),
'table': ('tables', 'table'),
'csv-table': ('tables', 'csv_table'),
'image': ('images', 'image'),
'figure': ('images', 'figure'),
'contents': ('parts', 'contents'),
'sectnum': ('parts', 'sectnum'),
#'footnotes': ('parts', 'footnotes'),
#'citations': ('parts', 'citations'),
'target-notes': ('references', 'target_notes'),
'meta': ('html', 'meta'),
#'imagemap': ('html', 'imagemap'),
'raw': ('misc', 'raw'),
'include': ('misc', 'include'),
'replace': ('misc', 'replace'),
'unicode': ('misc', 'unicode_directive'),
'class': ('misc', 'class_directive'),
'role': ('misc', 'role'),
'restructuredtext-test-directive': ('misc', 'directive_test_function'),}
"""Mapping of directive name to (module name, function name). The directive
name is canonical & must be lowercase. Language-dependent names are defined
in the ``language`` subpackage."""
_modules = {}
"""Cache of imported directive modules."""
_directives = {}
"""Cache of imported directive functions."""
def directive(directive_name, language_module, document):
"""
Locate and return a directive function from its language-dependent name.
If not found in the current language, check English. Return None if the
named directive cannot be found.
"""
normname = directive_name.lower()
messages = []
msg_text = []
if _directives.has_key(normname):
return _directives[normname], messages
canonicalname = None
try:
canonicalname = language_module.directives[normname]
except AttributeError, error:
msg_text.append('Problem retrieving directive entry from language '
'module %r: %s.' % (language_module, error))
except KeyError:
msg_text.append('No directive entry for "%s" in module "%s".'
% (directive_name, language_module.__name__))
if not canonicalname:
try:
canonicalname = _fallback_language_module.directives[normname]
msg_text.append('Using English fallback for directive "%s".'
% directive_name)
except KeyError:
msg_text.append('Trying "%s" as canonical directive name.'
% directive_name)
# The canonical name should be an English name, but just in case:
canonicalname = normname
if msg_text:
message = document.reporter.info(
'\n'.join(msg_text), line=document.current_line)
messages.append(message)
try:
modulename, functionname = _directive_registry[canonicalname]
except KeyError:
messages.append(document.reporter.error(
'Directive "%s" not registered (canonical name "%s").'
% (directive_name, canonicalname), line=document.current_line))
return None, messages
if _modules.has_key(modulename):
module = _modules[modulename]
else:
try:
module = __import__(modulename, globals(), locals())
except ImportError, detail:
messages.append(document.reporter.error(
'Error importing directive module "%s" (directive "%s"):\n%s'
% (modulename, directive_name, detail),
line=document.current_line))
return None, messages
try:
function = getattr(module, functionname)
_directives[normname] = function
except AttributeError:
messages.append(document.reporter.error(
'No function "%s" in module "%s" (directive "%s").'
% (functionname, modulename, directive_name),
line=document.current_line))
return None, messages
return function, messages
def register_directive(name, directive_function):
"""
Register a nonstandard application-defined directive function.
Language lookups are not needed for such functions.
"""
_directives[name] = directive_function
def flag(argument):
"""
Check for a valid flag option (no argument) and return ``None``.
(Directive option conversion function.)
Raise ``ValueError`` if an argument is found.
"""
if argument and argument.strip():
raise ValueError('no argument is allowed; "%s" supplied' % argument)
else:
return None
def unchanged_required(argument):
"""
Return the argument text, unchanged.
(Directive option conversion function.)
Raise ``ValueError`` if no argument is found.
"""
if argument is None:
raise ValueError('argument required but none supplied')
else:
return argument # unchanged!
def unchanged(argument):
"""
Return the argument text, unchanged.
(Directive option conversion function.)
No argument implies empty string ("").
"""
if argument is None:
return u''
else:
return argument # unchanged!
def path(argument):
"""
Return the path argument unwrapped (with newlines removed).
(Directive option conversion function.)
Raise ``ValueError`` if no argument is found or if the path contains
internal whitespace.
"""
if argument is None:
raise ValueError('argument required but none supplied')
else:
path = ''.join([s.strip() for s in argument.splitlines()])
if path.find(' ') == -1:
return path
else:
raise ValueError('path contains whitespace')
def nonnegative_int(argument):
"""
Check for a nonnegative integer argument; raise ``ValueError`` if not.
(Directive option conversion function.)
"""
value = int(argument)
if value < 0:
raise ValueError('negative value; must be positive or zero')
return value
def class_option(argument):
"""
Convert the argument into an ID-compatible string and return it.
(Directive option conversion function.)
Raise ``ValueError`` if no argument is found.
"""
if argument is None:
raise ValueError('argument required but none supplied')
class_name = nodes.make_id(argument)
if not class_name:
raise ValueError('cannot make "%s" into a class name' % argument)
return class_name
unicode_pattern = re.compile(
r'(?:0x|x|\\x|U\+?|\\u)([0-9a-f]+)$|&#x([0-9a-f]+);$', re.IGNORECASE)
def unicode_code(code):
r"""
Convert a Unicode character code to a Unicode character.
Codes may be decimal numbers, hexadecimal numbers (prefixed by ``0x``,
``x``, ``\x``, ``U+``, ``u``, or ``\u``; e.g. ``U+262E``), or XML-style
numeric character entities (e.g. ``&#x262E;``). Other text remains as-is.
"""
try:
if code.isdigit(): # decimal number
return unichr(int(code))
else:
match = unicode_pattern.match(code)
if match: # hex number
value = match.group(1) or match.group(2)
return unichr(int(value, 16))
else: # other text
return code
except OverflowError, detail:
raise ValueError('code too large (%s)' % detail)
def single_char_or_unicode(argument):
char = unicode_code(argument)
if len(char) > 1:
raise ValueError('%r invalid; must be a single character or '
'a Unicode code' % char)
return char
def single_char_or_whitespace_or_unicode(argument):
if argument == 'tab':
char = '\t'
elif argument == 'space':
char = ' '
else:
char = single_char_or_unicode(argument)
return char
def positive_int(argument):
value = int(argument)
if value < 1:
raise ValueError('negative or zero value; must be positive')
return value
def positive_int_list(argument):
if ',' in argument:
entries = argument.split(',')
else:
entries = argument.split()
return [positive_int(entry) for entry in entries]
def format_values(values):
return '%s, or "%s"' % (', '.join(['"%s"' % s for s in values[:-1]]),
values[-1])
def choice(argument, values):
"""
Directive option utility function, supplied to enable options whose
argument must be a member of a finite set of possible values (must be
lower case). A custom conversion function must be written to use it. For
example::
from docutils.parsers.rst import directives
def yesno(argument):
return directives.choice(argument, ('yes', 'no'))
Raise ``ValueError`` if no argument is found or if the argument's value is
not valid (not an entry in the supplied list).
"""
try:
value = argument.lower().strip()
except AttributeError:
raise ValueError('must supply an argument; choose from %s'
% format_values(values))
if value in values:
return value
else:
raise ValueError('"%s" unknown; choose from %s'
% (argument, format_values(values)))
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:48 $
# Copyright: This module has been placed in the public domain.
"""
Admonition directives.
"""
__docformat__ = 'reStructuredText'
from docutils.parsers.rst import states, directives
from docutils import nodes
def make_admonition(node_class, name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
if not content:
error = state_machine.reporter.error(
'The "%s" admonition is empty; content required.' % (name),
nodes.literal_block(block_text, block_text), line=lineno)
return [error]
text = '\n'.join(content)
admonition_node = node_class(text)
if arguments:
title_text = arguments[0]
textnodes, messages = state.inline_text(title_text, lineno)
admonition_node += nodes.title(title_text, '', *textnodes)
admonition_node += messages
if options.has_key('class'):
class_value = options['class']
else:
class_value = 'admonition-' + nodes.make_id(title_text)
admonition_node.set_class(class_value)
state.nested_parse(content, content_offset, admonition_node)
return [admonition_node]
def admonition(*args):
return make_admonition(nodes.admonition, *args)
admonition.arguments = (1, 0, 1)
admonition.options = {'class': directives.class_option}
admonition.content = 1
def attention(*args):
return make_admonition(nodes.attention, *args)
attention.content = 1
def caution(*args):
return make_admonition(nodes.caution, *args)
caution.content = 1
def danger(*args):
return make_admonition(nodes.danger, *args)
danger.content = 1
def error(*args):
return make_admonition(nodes.error, *args)
error.content = 1
def hint(*args):
return make_admonition(nodes.hint, *args)
hint.content = 1
def important(*args):
return make_admonition(nodes.important, *args)
important.content = 1
def note(*args):
return make_admonition(nodes.note, *args)
note.content = 1
def tip(*args):
return make_admonition(nodes.tip, *args)
tip.content = 1
def warning(*args):
return make_admonition(nodes.warning, *args)
warning.content = 1
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:48 $
# Copyright: This module has been placed in the public domain.
"""
Directives for additional body elements.
"""
__docformat__ = 'reStructuredText'
import sys
from docutils import nodes
from docutils.parsers.rst import directives
def topic(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine,
node_class=nodes.topic):
if not state_machine.match_titles:
error = state_machine.reporter.error(
'The "%s" directive may not be used within topics, sidebars, '
'or body elements.' % name,
nodes.literal_block(block_text, block_text), line=lineno)
return [error]
if not content:
warning = state_machine.reporter.warning(
'Content block expected for the "%s" directive; none found.'
% name, nodes.literal_block(block_text, block_text),
line=lineno)
return [warning]
title_text = arguments[0]
textnodes, messages = state.inline_text(title_text, lineno)
titles = [nodes.title(title_text, '', *textnodes)]
# sidebar uses this code
if options.has_key('subtitle'):
textnodes, more_messages = state.inline_text(options['subtitle'],
lineno)
titles.append(nodes.subtitle(options['subtitle'], '', *textnodes))
messages.extend(more_messages)
text = '\n'.join(content)
node = node_class(text, *(titles + messages))
if options.has_key('class'):
node.set_class(options['class'])
if text:
state.nested_parse(content, content_offset, node)
return [node]
topic.arguments = (1, 0, 1)
topic.options = {'class': directives.class_option}
topic.content = 1
def sidebar(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
return topic(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine,
node_class=nodes.sidebar)
sidebar.arguments = (1, 0, 1)
sidebar.options = {'subtitle': directives.unchanged_required,
'class': directives.class_option}
sidebar.content = 1
def line_block(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine,
node_class=nodes.line_block):
if not content:
warning = state_machine.reporter.warning(
'Content block expected for the "%s" directive; none found.'
% name, nodes.literal_block(block_text, block_text), line=lineno)
return [warning]
text = '\n'.join(content)
text_nodes, messages = state.inline_text(text, lineno)
node = node_class(text, '', *text_nodes, **options)
node.line = content_offset + 1
return [node] + messages
line_block.options = {'class': directives.class_option}
line_block.content = 1
def parsed_literal(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
return line_block(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine,
node_class=nodes.literal_block)
parsed_literal.options = {'class': directives.class_option}
parsed_literal.content = 1
def rubric(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
rubric_text = arguments[0]
textnodes, messages = state.inline_text(rubric_text, lineno)
rubric = nodes.rubric(rubric_text, '', *textnodes, **options)
return [rubric] + messages
rubric.arguments = (1, 0, 1)
rubric.options = {'class': directives.class_option}
def epigraph(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
block_quote, messages = state.block_quote(content, content_offset)
block_quote.set_class('epigraph')
return [block_quote] + messages
epigraph.content = 1
def highlights(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
block_quote, messages = state.block_quote(content, content_offset)
block_quote.set_class('highlights')
return [block_quote] + messages
highlights.content = 1
def pull_quote(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
block_quote, messages = state.block_quote(content, content_offset)
block_quote.set_class('pull-quote')
return [block_quote] + messages
pull_quote.content = 1
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:48 $
# Copyright: This module has been placed in the public domain.
"""
Directives for typically HTML-specific constructs.
"""
__docformat__ = 'reStructuredText'
import sys
from docutils import nodes, utils
from docutils.parsers.rst import states
from docutils.transforms import components
def meta(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
node = nodes.Element()
if content:
new_line_offset, blank_finish = state.nested_list_parse(
content, content_offset, node, initial_state='MetaBody',
blank_finish=1, state_machine_kwargs=metaSMkwargs)
if (new_line_offset - content_offset) != len(content):
# incomplete parse of block?
error = state_machine.reporter.error(
'Invalid meta directive.',
nodes.literal_block(block_text, block_text), line=lineno)
node += error
else:
error = state_machine.reporter.error(
'Empty meta directive.',
nodes.literal_block(block_text, block_text), line=lineno)
node += error
return node.get_children()
meta.content = 1
def imagemap(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
return []
class MetaBody(states.SpecializedBody):
class meta(nodes.Special, nodes.PreBibliographic, nodes.Element):
"""HTML-specific "meta" element."""
pass
def field_marker(self, match, context, next_state):
"""Meta element."""
node, blank_finish = self.parsemeta(match)
self.parent += node
return [], next_state, []
def parsemeta(self, match):
name = self.parse_field_marker(match)
indented, indent, line_offset, blank_finish = \
self.state_machine.get_first_known_indented(match.end())
node = self.meta()
pending = nodes.pending(components.Filter,
{'component': 'writer',
'format': 'html',
'nodes': [node]})
node['content'] = ' '.join(indented)
if not indented:
line = self.state_machine.line
msg = self.reporter.info(
'No content for meta tag "%s".' % name,
nodes.literal_block(line, line),
line=self.state_machine.abs_line_number())
return msg, blank_finish
tokens = name.split()
try:
attname, val = utils.extract_name_value(tokens[0])[0]
node[attname.lower()] = val
except utils.NameValueError:
node['name'] = tokens[0]
for token in tokens[1:]:
try:
attname, val = utils.extract_name_value(token)[0]
node[attname.lower()] = val
except utils.NameValueError, detail:
line = self.state_machine.line
msg = self.reporter.error(
'Error parsing meta tag attribute "%s": %s.'
% (token, detail), nodes.literal_block(line, line),
line=self.state_machine.abs_line_number())
return msg, blank_finish
self.document.note_pending(pending)
return pending, blank_finish
metaSMkwargs = {'state_classes': (MetaBody,)}
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:48 $
# Copyright: This module has been placed in the public domain.
"""
Directives for figures and simple images.
"""
__docformat__ = 'reStructuredText'
import sys
from docutils import nodes, utils
from docutils.parsers.rst import directives, states
from docutils.nodes import whitespace_normalize_name
try:
import Image # PIL
except ImportError:
Image = None
align_values = ('top', 'middle', 'bottom', 'left', 'center', 'right')
def align(argument):
return directives.choice(argument, align_values)
def image(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
messages = []
reference = ''.join(arguments[0].split('\n'))
if reference.find(' ') != -1:
error = state_machine.reporter.error(
'Image URI contains whitespace.',
nodes.literal_block(block_text, block_text), line=lineno)
return [error]
options['uri'] = reference
reference_node = None
if options.has_key('target'):
block = states.escape2null(options['target']).splitlines()
block = [line for line in block]
target_type, data = state.parse_target(block, block_text, lineno)
if target_type == 'refuri':
reference_node = nodes.reference(refuri=data)
elif target_type == 'refname':
reference_node = nodes.reference(
refname=data, name=whitespace_normalize_name(options['target']))
state.document.note_refname(reference_node)
else: # malformed target
messages.append(data) # data is a system message
del options['target']
image_node = nodes.image(block_text, **options)
if reference_node:
reference_node += image_node
return messages + [reference_node]
else:
return messages + [image_node]
image.arguments = (1, 0, 1)
image.options = {'alt': directives.unchanged,
'height': directives.nonnegative_int,
'width': directives.nonnegative_int,
'scale': directives.nonnegative_int,
'align': align,
'target': directives.unchanged_required,
'class': directives.class_option}
def figure(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
figwidth = options.setdefault('figwidth')
figclass = options.setdefault('figclass')
del options['figwidth']
del options['figclass']
(image_node,) = image(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine)
if isinstance(image_node, nodes.system_message):
return [image_node]
figure_node = nodes.figure('', image_node)
if figwidth == 'image':
if Image:
# PIL doesn't like Unicode paths:
try:
i = Image.open(str(image_node['uri']))
except (IOError, UnicodeError):
pass
else:
figure_node['width'] = i.size[0]
elif figwidth is not None:
figure_node['width'] = figwidth
if figclass:
figure_node.set_class(figclass)
if content:
node = nodes.Element() # anonymous container for parsing
state.nested_parse(content, content_offset, node)
first_node = node[0]
if isinstance(first_node, nodes.paragraph):
caption = nodes.caption(first_node.rawsource, '',
*first_node.children)
figure_node += caption
elif not (isinstance(first_node, nodes.comment)
and len(first_node) == 0):
error = state_machine.reporter.error(
'Figure caption must be a paragraph or empty comment.',
nodes.literal_block(block_text, block_text), line=lineno)
return [figure_node, error]
if len(node) > 1:
figure_node += nodes.legend('', *node[1:])
return [figure_node]
def figwidth_value(argument):
if argument.lower() == 'image':
return 'image'
else:
return directives.nonnegative_int(argument)
figure.arguments = (1, 0, 1)
figure.options = {'figwidth': figwidth_value,
'figclass': directives.class_option}
figure.options.update(image.options)
figure.content = 1
# Authors: David Goodger, Dethe Elza
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:48 $
# Copyright: This module has been placed in the public domain.
"""Miscellaneous directives."""
__docformat__ = 'reStructuredText'
import sys
import os.path
import re
from docutils import io, nodes, statemachine, utils
from docutils.parsers.rst import directives, roles, states
from docutils.transforms import misc
try:
import urllib2
except ImportError:
urllib2 = None
def include(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
"""Include a reST file as part of the content of this reST file."""
source = state_machine.input_lines.source(
lineno - state_machine.input_offset - 1)
source_dir = os.path.dirname(os.path.abspath(source))
path = ''.join(arguments[0].splitlines())
if path.find(' ') != -1:
error = state_machine.reporter.error(
'"%s" directive path contains whitespace.' % name,
nodes.literal_block(block_text, block_text), line=lineno)
return [error]
path = os.path.normpath(os.path.join(source_dir, path))
path = utils.relative_path(None, path)
try:
include_file = io.FileInput(
source_path=path, encoding=state.document.settings.input_encoding,
handle_io_errors=None)
except IOError, error:
severe = state_machine.reporter.severe(
'Problems with "%s" directive path:\n%s: %s.'
% (name, error.__class__.__name__, error),
nodes.literal_block(block_text, block_text), line=lineno)
return [severe]
include_text = include_file.read()
if options.has_key('literal'):
literal_block = nodes.literal_block(include_text, include_text,
source=path)
literal_block.line = 1
return literal_block
else:
include_lines = statemachine.string2lines(include_text,
convert_whitespace=1)
state_machine.insert_input(include_lines, path)
return []
include.arguments = (1, 0, 1)
include.options = {'literal': directives.flag}
def raw(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
"""
Pass through content unchanged
Content is included in output based on type argument
Content may be included inline (content section of directive) or
imported from a file or url.
"""
attributes = {'format': arguments[0]}
if content:
if options.has_key('file') or options.has_key('url'):
error = state_machine.reporter.error(
'"%s" directive may not both specify an external file and '
'have content.' % name,
nodes.literal_block(block_text, block_text), line=lineno)
return [error]
text = '\n'.join(content)
elif options.has_key('file'):
if options.has_key('url'):
error = state_machine.reporter.error(
'The "file" and "url" options may not be simultaneously '
'specified for the "%s" directive.' % name,
nodes.literal_block(block_text, block_text), line=lineno)
return [error]
source_dir = os.path.dirname(
os.path.abspath(state.document.current_source))
path = os.path.normpath(os.path.join(source_dir, options['file']))
path = utils.relative_path(None, path)
try:
raw_file = open(path)
except IOError, error:
severe = state_machine.reporter.severe(
'Problems with "%s" directive path:\n%s.' % (name, error),
nodes.literal_block(block_text, block_text), line=lineno)
return [severe]
text = raw_file.read()
raw_file.close()
attributes['source'] = path
elif options.has_key('url'):
if not urllib2:
severe = state_machine.reporter.severe(
'Problems with the "%s" directive and its "url" option: '
'unable to access the required functionality (from the '
'"urllib2" module).' % name,
nodes.literal_block(block_text, block_text), line=lineno)
return [severe]
try:
raw_file = urllib2.urlopen(options['url'])
except (urllib2.URLError, IOError, OSError), error:
severe = state_machine.reporter.severe(
'Problems with "%s" directive URL "%s":\n%s.'
% (name, options['url'], error),
nodes.literal_block(block_text, block_text), line=lineno)
return [severe]
text = raw_file.read()
raw_file.close()
attributes['source'] = options['file']
else:
error = state_machine.reporter.warning(
'The "%s" directive requires content; none supplied.' % (name),
nodes.literal_block(block_text, block_text), line=lineno)
return [error]
raw_node = nodes.raw('', text, **attributes)
return [raw_node]
raw.arguments = (1, 0, 1)
raw.options = {'file': directives.path,
'url': directives.path}
raw.content = 1
def replace(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
if not isinstance(state, states.SubstitutionDef):
error = state_machine.reporter.error(
'Invalid context: the "%s" directive can only be used within a '
'substitution definition.' % (name),
nodes.literal_block(block_text, block_text), line=lineno)
return [error]
text = '\n'.join(content)
element = nodes.Element(text)
if text:
state.nested_parse(content, content_offset, element)
if len(element) != 1 or not isinstance(element[0], nodes.paragraph):
messages = []
for node in element:
if isinstance(node, nodes.system_message):
if node.has_key('backrefs'):
del node['backrefs']
messages.append(node)
error = state_machine.reporter.error(
'Error in "%s" directive: may contain a single paragraph '
'only.' % (name), line=lineno)
messages.append(error)
return messages
else:
return element[0].children
else:
error = state_machine.reporter.error(
'The "%s" directive is empty; content required.' % (name),
line=lineno)
return [error]
replace.content = 1
def unicode_directive(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
r"""
Convert Unicode character codes (numbers) to characters. Codes may be
decimal numbers, hexadecimal numbers (prefixed by ``0x``, ``x``, ``\x``,
``U+``, ``u``, or ``\u``; e.g. ``U+262E``), or XML-style numeric character
entities (e.g. ``&#x262E;``). Text following ".." is a comment and is
ignored. Spaces are ignored, and any other text remains as-is.
"""
if not isinstance(state, states.SubstitutionDef):
error = state_machine.reporter.error(
'Invalid context: the "%s" directive can only be used within a '
'substitution definition.' % (name),
nodes.literal_block(block_text, block_text), line=lineno)
return [error]
codes = unicode_comment_pattern.split(arguments[0])[0].split()
element = nodes.Element()
for code in codes:
try:
decoded = directives.unicode_code(code)
except ValueError, err:
error = state_machine.reporter.error(
'Invalid character code: %s\n%s: %s'
% (code, err.__class__.__name__, err),
nodes.literal_block(block_text, block_text), line=lineno)
return [error]
element += nodes.Text(decoded)
return element.children
unicode_directive.arguments = (1, 0, 1)
unicode_comment_pattern = re.compile(r'( |\n|^).. ')
def class_directive(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
""""""
class_value = nodes.make_id(arguments[0])
if class_value:
pending = nodes.pending(misc.ClassAttribute,
{'class': class_value, 'directive': name},
block_text)
state_machine.document.note_pending(pending)
return [pending]
else:
error = state_machine.reporter.error(
'Invalid class attribute value for "%s" directive: "%s".'
% (name, arguments[0]),
nodes.literal_block(block_text, block_text), line=lineno)
return [error]
class_directive.arguments = (1, 0, 0)
class_directive.content = 1
role_arg_pat = re.compile(r'(%s)\s*(\(\s*(%s)\s*\)\s*)?$'
% ((states.Inliner.simplename,) * 2))
def role(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
"""Dynamically create and register a custom interpreted text role."""
if content_offset > lineno or not content:
error = state_machine.reporter.error(
'"%s" directive requires arguments on the first line.'
% name, nodes.literal_block(block_text, block_text), line=lineno)
return [error]
args = content[0]
match = role_arg_pat.match(args)
if not match:
error = state_machine.reporter.error(
'"%s" directive arguments not valid role names: "%s".'
% (name, args), nodes.literal_block(block_text, block_text),
line=lineno)
return [error]
new_role_name = match.group(1)
base_role_name = match.group(3)
messages = []
if base_role_name:
base_role, messages = roles.role(
base_role_name, state_machine.language, lineno, state.reporter)
if base_role is None:
error = state.reporter.error(
'Unknown interpreted text role "%s".' % base_role_name,
nodes.literal_block(block_text, block_text), line=lineno)
return messages + [error]
else:
base_role = roles.generic_custom_role
assert not hasattr(base_role, 'arguments'), (
'Supplemental directive arguments for "%s" directive not supported'
'(specified by "%r" role).' % (name, base_role))
try:
(arguments, options, content, content_offset) = (
state.parse_directive_block(content[1:], content_offset, base_role,
option_presets={}))
except states.MarkupError, detail:
error = state_machine.reporter.error(
'Error in "%s" directive:\n%s.' % (name, detail),
nodes.literal_block(block_text, block_text), line=lineno)
return messages + [error]
if not options.has_key('class'):
try:
options['class'] = directives.class_option(new_role_name)
except ValueError, detail:
error = state_machine.reporter.error(
'Invalid argument for "%s" directive:\n%s.'
% (name, detail),
nodes.literal_block(block_text, block_text), line=lineno)
return messages + [error]
role = roles.CustomRole(new_role_name, base_role, options, content)
roles.register_local_role(new_role_name, role)
return messages
role.content = 1
def directive_test_function(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
"""This directive is useful only for testing purposes."""
if content:
text = '\n'.join(content)
info = state_machine.reporter.info(
'Directive processed. Type="%s", arguments=%r, options=%r, '
'content:' % (name, arguments, options),
nodes.literal_block(text, text), line=lineno)
else:
info = state_machine.reporter.info(
'Directive processed. Type="%s", arguments=%r, options=%r, '
'content: None' % (name, arguments, options), line=lineno)
return [info]
directive_test_function.arguments = (0, 1, 1)
directive_test_function.options = {'option': directives.unchanged_required}
directive_test_function.content = 1
# Author: David Goodger, Dmitry Jemerov
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:48 $
# Copyright: This module has been placed in the public domain.
"""
Directives for document parts.
"""
__docformat__ = 'reStructuredText'
from docutils import nodes, languages
from docutils.transforms import parts
from docutils.parsers.rst import directives
backlinks_values = ('top', 'entry', 'none')
def backlinks(arg):
value = directives.choice(arg, backlinks_values)
if value == 'none':
return None
else:
return value
def contents(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
"""Table of contents."""
document = state_machine.document
language = languages.get_language(document.settings.language_code)
if arguments:
title_text = arguments[0]
text_nodes, messages = state.inline_text(title_text, lineno)
title = nodes.title(title_text, '', *text_nodes)
else:
messages = []
if options.has_key('local'):
title = None
else:
title = nodes.title('', language.labels['contents'])
topic = nodes.topic(CLASS='contents')
cls = options.get('class')
if cls:
topic.set_class(cls)
if title:
name = title.astext()
topic += title
else:
name = language.labels['contents']
name = nodes.fully_normalize_name(name)
if not document.has_name(name):
topic['name'] = name
document.note_implicit_target(topic)
pending = nodes.pending(parts.Contents, rawsource=block_text)
pending.details.update(options)
document.note_pending(pending)
topic += pending
return [topic] + messages
contents.arguments = (0, 1, 1)
contents.options = {'depth': directives.nonnegative_int,
'local': directives.flag,
'backlinks': backlinks,
'class': directives.class_option}
def sectnum(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
"""Automatic section numbering."""
pending = nodes.pending(parts.SectNum)
pending.details.update(options)
state_machine.document.note_pending(pending)
return [pending]
sectnum.options = {'depth': int}
# Author: David Goodger, Dmitry Jemerov
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:48 $
# Copyright: This module has been placed in the public domain.
"""
Directives for references and targets.
"""
__docformat__ = 'reStructuredText'
from docutils import nodes
from docutils.transforms import references
def target_notes(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
"""Target footnote generation."""
pending = nodes.pending(references.TargetNotes)
state_machine.document.note_pending(pending)
nodelist = [pending]
return nodelist
# Authors: David Goodger, David Priest
# Contact: goodger@python.org
# Revision: $Revision: 1.2 $
# Date: $Date: 2004/06/19 22:53:32 $
# Copyright: This module has been placed in the public domain.
"""
Directives for table elements.
"""
__docformat__ = 'reStructuredText'
import sys
import os.path
from docutils import nodes, statemachine, utils
from docutils.utils import SystemMessagePropagation
from docutils.parsers.rst import directives
try:
import csv # new in Python 2.3
except ImportError:
csv = None
try:
import urllib2
except ImportError:
urllib2 = None
try:
True
except NameError: # Python 2.2 & 2.1 compatibility
True = not 0
False = not 1
def table(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
if not content:
warning = state_machine.reporter.warning(
'Content block expected for the "%s" directive; none found.'
% name, nodes.literal_block(block_text, block_text),
line=lineno)
return [warning]
title, messages = make_title(arguments, state, lineno)
node = nodes.Element() # anonymous container for parsing
text = '\n'.join(content)
state.nested_parse(content, content_offset, node)
if len(node) != 1 or not isinstance(node[0], nodes.table):
error = state_machine.reporter.error(
'Error parsing content block for the "%s" directive: '
'exactly one table expected.'
% name, nodes.literal_block(block_text, block_text),
line=lineno)
return [error]
table_node = node[0]
if options.has_key('class'):
table_node.set_class(options['class'])
if title:
table_node.insert(0, title)
return [table_node] + messages
table.arguments = (0, 1, 1)
table.options = {'class': directives.class_option}
table.content = 1
def make_title(arguments, state, lineno):
if arguments:
title_text = arguments[0]
text_nodes, messages = state.inline_text(title_text, lineno)
title = nodes.title(title_text, '', *text_nodes)
else:
title = None
messages = []
return title, messages
if csv:
class DocutilsDialect(csv.Dialect):
"""CSV dialect for `csv_table` directive function."""
delimiter = ','
quotechar = '"'
doublequote = True
skipinitialspace = True
lineterminator = '\n'
quoting = csv.QUOTE_MINIMAL
def __init__(self, options):
if options.has_key('delim'):
self.delimiter = str(options['delim'])
if options.has_key('keepspace'):
self.skipinitialspace = False
if options.has_key('quote'):
self.quotechar = str(options['quote'])
if options.has_key('escape'):
self.doublequote = False
self.escapechar = str(options['escape'])
csv.Dialect.__init__(self)
class HeaderDialect(csv.Dialect):
"""CSV dialect to use for the "header" option data."""
delimiter = ','
quotechar = '"'
escapechar = '\\'
doublequote = False
skipinitialspace = True
lineterminator = '\n'
quoting = csv.QUOTE_MINIMAL
def csv_table(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
try:
check_requirements(name, lineno, block_text, state_machine)
title, messages = make_title(arguments, state, lineno)
csv_data, source = get_csv_data(
name, options, content, lineno, block_text, state, state_machine)
table_head, max_header_cols = process_header_option(
options, state_machine, lineno)
rows, max_cols = parse_csv_data_into_rows(
csv_data, DocutilsDialect(options), source, options)
max_cols = max(max_cols, max_header_cols)
header_rows = options.get('header-rows', 0) # default 0
check_table_dimensions(
rows, header_rows, name, lineno, block_text, state_machine)
table_head.extend(rows[:header_rows])
table_body = rows[header_rows:]
col_widths = get_column_widths(
max_cols, name, options, lineno, block_text, state_machine)
extend_short_rows_with_empty_cells(max_cols, (table_head, table_body))
except SystemMessagePropagation, detail:
return [detail.args[0]]
except csv.Error, detail:
error = state_machine.reporter.error(
'Error with CSV data in "%s" directive:\n%s' % (name, detail),
nodes.literal_block(block_text, block_text), line=lineno)
return [error]
table = (col_widths, table_head, table_body)
table_node = state.build_table(table, content_offset)
if options.has_key('class'):
table_node.set_class(options['class'])
if title:
table_node.insert(0, title)
return [table_node] + messages
csv_table.arguments = (0, 1, 1)
csv_table.options = {'header-rows': directives.nonnegative_int,
'header': directives.unchanged,
'widths': directives.positive_int_list,
'file': directives.path,
'url': directives.path,
'class': directives.class_option,
# field delimiter char
'delim': directives.single_char_or_whitespace_or_unicode,
# treat whitespace after delimiter as significant
'keepspace': directives.flag,
# text field quote/unquote char:
'quote': directives.single_char_or_unicode,
# char used to escape delim & quote as-needed:
'escape': directives.single_char_or_unicode,}
csv_table.content = 1
def check_requirements(name, lineno, block_text, state_machine):
if not csv:
error = state_machine.reporter.error(
'The "%s" directive is not compatible with this version of '
'Python (%s). Requires the "csv" module, new in Python 2.3.'
% (name, sys.version.split()[0]),
nodes.literal_block(block_text, block_text), line=lineno)
raise SystemMessagePropagation(error)
def get_csv_data(name, options, content, lineno, block_text,
state, state_machine):
"""
CSV data can come from the directive content, from an external file, or
from a URL reference.
"""
if content: # CSV data is from directive content
if options.has_key('file') or options.has_key('url'):
error = state_machine.reporter.error(
'"%s" directive may not both specify an external file and '
'have content.' % name,
nodes.literal_block(block_text, block_text), line=lineno)
raise SystemMessagePropagation(error)
source = content.source(0)
csv_data = content
elif options.has_key('file'): # CSV data is from an external file
if options.has_key('url'):
error = state_machine.reporter.error(
'The "file" and "url" options may not be simultaneously '
'specified for the "%s" directive.' % name,
nodes.literal_block(block_text, block_text), line=lineno)
raise SystemMessagePropagation(error)
source_dir = os.path.dirname(
os.path.abspath(state.document.current_source))
source = os.path.normpath(os.path.join(source_dir, options['file']))
source = utils.relative_path(None, source)
try:
csv_file = open(source, 'rb')
try:
csv_data = csv_file.read().splitlines()
finally:
csv_file.close()
except IOError, error:
severe = state_machine.reporter.severe(
'Problems with "%s" directive path:\n%s.' % (name, error),
nodes.literal_block(block_text, block_text), line=lineno)
raise SystemMessagePropagation(severe)
elif options.has_key('url'): # CSV data is from a URL
if not urllib2:
severe = state_machine.reporter.severe(
'Problems with the "%s" directive and its "url" option: '
'unable to access the required functionality (from the '
'"urllib2" module).' % name,
nodes.literal_block(block_text, block_text), line=lineno)
raise SystemMessagePropagation(severe)
source = options['url']
try:
csv_data = urllib2.urlopen(source).read().splitlines()
except (urllib2.URLError, IOError, OSError, ValueError), error:
severe = state_machine.reporter.severe(
'Problems with "%s" directive URL "%s":\n%s.'
% (name, options['url'], error),
nodes.literal_block(block_text, block_text), line=lineno)
raise SystemMessagePropagation(severe)
else:
error = state_machine.reporter.warning(
'The "%s" directive requires content; none supplied.' % (name),
nodes.literal_block(block_text, block_text), line=lineno)
raise SystemMessagePropagation(error)
return csv_data, source
def process_header_option(options, state_machine, lineno):
source = state_machine.get_source(lineno - 1)
table_head = []
max_header_cols = 0
if options.has_key('header'): # separate table header in option
rows, max_header_cols = parse_csv_data_into_rows(
options['header'].split('\n'), HeaderDialect(), source, options)
table_head.extend(rows)
return table_head, max_header_cols
def parse_csv_data_into_rows(csv_data, dialect, source, options):
csv_reader = csv.reader(csv_data, dialect=dialect)
rows = []
max_cols = 0
for row in csv_reader:
row_data = []
for cell in row:
cell_data = (0, 0, 0, statemachine.StringList(cell.splitlines(),
source=source))
row_data.append(cell_data)
rows.append(row_data)
max_cols = max(max_cols, len(row))
return rows, max_cols
def check_table_dimensions(rows, header_rows, name, lineno, block_text,
state_machine):
if len(rows) < header_rows:
error = state_machine.reporter.error(
'%s header row(s) specified but only %s row(s) of data supplied '
'("%s" directive).' % (header_rows, len(rows), name),
nodes.literal_block(block_text, block_text), line=lineno)
raise SystemMessagePropagation(error)
elif len(rows) == header_rows > 0:
error = state_machine.reporter.error(
'Insufficient data supplied (%s row(s)); no data remaining for '
'table body, required by "%s" directive.' % (len(rows), name),
nodes.literal_block(block_text, block_text), line=lineno)
raise SystemMessagePropagation(error)
def get_column_widths(max_cols, name, options, lineno, block_text,
state_machine):
if options.has_key('widths'):
col_widths = options['widths']
if len(col_widths) != max_cols:
error = state_machine.reporter.error(
'"%s" widths do not match the number of columns in table (%s).'
% (name, max_cols),
nodes.literal_block(block_text, block_text), line=lineno)
raise SystemMessagePropagation(error)
else:
col_widths = [100 / max_cols] * max_cols
return col_widths
def extend_short_rows_with_empty_cells(columns, parts):
for part in parts:
for row in part:
if len(row) < columns:
row.extend([(0, 0, 0, [])] * (columns - len(row)))
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:50 $
# Copyright: This module has been placed in the public domain.
# Internationalization details are documented in
# <http://docutils.sf.net/spec/howto/i18n.html>.
"""
This package contains modules for language-dependent features of
reStructuredText.
"""
__docformat__ = 'reStructuredText'
_languages = {}
def get_language(language_code):
if _languages.has_key(language_code):
return _languages[language_code]
try:
module = __import__(language_code, globals(), locals())
except ImportError:
return None
_languages[language_code] = module
return module
# Author: Jannie Hofmeyr
# Contact: jhsh@sun.ac.za
# Revision: $Revision: 1.1.2.3.8.1 $
# Date: $Date: 2004/05/12 19:57:50 $
# Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
Afrikaans-language mappings for language-dependent features of
reStructuredText.
"""
__docformat__ = 'reStructuredText'
directives = {
'aandag': 'attention',
'versigtig': 'caution',
'gevaar': 'danger',
'fout': 'error',
'wenk': 'hint',
'belangrik': 'important',
'nota': 'note',
'tip': 'tip', # hint and tip both have the same translation: wenk
'waarskuwing': 'warning',
'vermaning': 'admonition',
'kantstreep': 'sidebar',
'onderwerp': 'topic',
'lynblok': 'line-block',
'parsed-literal (translation required)': 'parsed-literal',
'rubriek': 'rubric',
'epigraaf': 'epigraph',
'hoogtepunte': 'highlights',
'pull-quote (translation required)': 'pull-quote',
#'vrae': 'questions',
#'qa': 'questions',
#'faq': 'questions',
'table (translation required)': 'table',
'csv-table (translation required)': 'csv-table',
'meta': 'meta',
#'beeldkaart': 'imagemap',
'beeld': 'image',
'figuur': 'figure',
'insluiting': 'include',
'rou': 'raw',
'vervang': 'replace',
'unicode': 'unicode', # should this be translated? unikode
'klas': 'class',
'role (translation required)': 'role',
'inhoud': 'contents',
'sectnum': 'sectnum',
'section-numbering': 'sectnum',
#'voetnote': 'footnotes',
#'aanhalings': 'citations',
'teikennotas': 'target-notes',
'restructuredtext-test-directive': 'restructuredtext-test-directive'}
"""Afrikaans name to registered (in directives/__init__.py) directive name
mapping."""
roles = {
'afkorting': 'abbreviation',
'ab': 'abbreviation',
'akroniem': 'acronym',
'ac': 'acronym',
'indeks': 'index',
'i': 'index',
'voetskrif': 'subscript',
'sub': 'subscript',
'boskrif': 'superscript',
'sup': 'superscript',
'titelverwysing': 'title-reference',
'titel': 'title-reference',
't': 'title-reference',
'pep-verwysing': 'pep-reference',
'pep': 'pep-reference',
'rfc-verwysing': 'rfc-reference',
'rfc': 'rfc-reference',
'nadruk': 'emphasis',
'sterk': 'strong',
'literal (translation required)': 'literal',
'benoemde verwysing': 'named-reference',
'anonieme verwysing': 'anonymous-reference',
'voetnootverwysing': 'footnote-reference',
'aanhalingverwysing': 'citation-reference',
'vervangingsverwysing': 'substitution-reference',
'teiken': 'target',
'uri-verwysing': 'uri-reference',
'uri': 'uri-reference',
'url': 'uri-reference',}
"""Mapping of Afrikaans role names to canonical role names for interpreted text.
"""
# Author: Marek Blaha
# Contact: mb@dat.cz
# Revision: $Revision: 1.1.2.1 $
# Date: $Date: 2004/05/12 19:57:50 $
# Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
Czech-language mappings for language-dependent features of
reStructuredText.
"""
__docformat__ = 'reStructuredText'
directives = {
# language-dependent: fixed
u'pozor': 'attention',
u'caution': 'caution', # jak rozlisit caution a warning?
u'nebezpe\u010D\u00ED': 'danger',
u'chyba': 'error',
u'rada': 'hint',
u'd\u016Fle\u017Eit\u00E9': 'important',
u'pozn\u00E1mka': 'note',
u'tip': 'tip',
u'varov\u00E1n\u00ED': 'warning',
u'admonition': 'admonition',
u'sidebar': 'sidebar',
u't\u00E9ma': 'topic',
u'line-block': 'line-block',
u'parsed-literal': 'parsed-literal',
u'odd\u00EDl': 'rubric',
u'moto': 'epigraph',
u'highlights': 'highlights',
u'pull-quote': 'pull-quote',
#'questions': 'questions',
#'qa': 'questions',
#'faq': 'questions',
u'table (translation required)': 'table',
u'csv-table (translation required)': 'csv-table',
u'meta': 'meta',
#'imagemap': 'imagemap',
u'image': 'image', # obrazek
u'figure': 'figure', # a tady?
u'include': 'include',
u'raw': 'raw',
u'replace': 'replace',
u'unicode': 'unicode',
u't\u0159\u00EDda': 'class',
u'role (translation required)': 'role',
u'obsah': 'contents',
u'sectnum': 'sectnum',
u'section-numbering': 'sectnum',
#'footnotes': 'footnotes',
#'citations': 'citations',
u'target-notes': 'target-notes',
u'restructuredtext-test-directive': 'restructuredtext-test-directive'}
"""Czech name to registered (in directives/__init__.py) directive name
mapping."""
roles = {
# language-dependent: fixed
u'abbreviation': 'abbreviation',
u'ab': 'abbreviation',
u'acronym': 'acronym',
u'ac': 'acronym',
u'index': 'index',
u'i': 'index',
u'subscript': 'subscript',
u'sub': 'subscript',
u'superscript': 'superscript',
u'sup': 'superscript',
u'title-reference': 'title-reference',
u'title': 'title-reference',
u't': 'title-reference',
u'pep-reference': 'pep-reference',
u'pep': 'pep-reference',
u'rfc-reference': 'rfc-reference',
u'rfc': 'rfc-reference',
u'emphasis': 'emphasis',
u'strong': 'strong',
u'literal': 'literal',
u'named-reference': 'named-reference',
u'anonymous-reference': 'anonymous-reference',
u'footnote-reference': 'footnote-reference',
u'citation-reference': 'citation-reference',
u'substitution-reference': 'substitution-reference',
u'target': 'target',
u'uri-reference': 'uri-reference',
u'uri': 'uri-reference',
u'url': 'uri-reference',}
"""Mapping of Czech role names to canonical role names for interpreted text.
"""
# -*- coding: iso-8859-1 -*-
# Author: Engelbert Gruber
# Contact: grubert@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:50 $
# Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
German-language mappings for language-dependent features of
reStructuredText.
"""
__docformat__ = 'reStructuredText'
directives = {
'achtung': 'attention',
'vorsicht': 'caution',
'gefahr': 'danger',
'fehler': 'error',
'hinweis': 'hint',
'wichtig': 'important',
'notiz': 'note',
'tip': 'tip',
'warnung': 'warning',
'ermahnung': 'admonition',
'kasten': 'sidebar', # seitenkasten ?
'thema': 'topic',
'line-block': 'line-block',
'parsed-literal': 'parsed-literal',
'rubrik': 'rubric',
'epigraph (translation required)': 'epigraph',
'highlights (translation required)': 'highlights',
'pull-quote (translation required)': 'pull-quote', # kasten too ?
#'questions': 'questions',
#'qa': 'questions',
#'faq': 'questions',
'table (translation required)': 'table',
'csv-table (translation required)': 'csv-table',
'meta': 'meta',
#'imagemap': 'imagemap',
'bild': 'image',
'abbildung': 'figure',
'raw': 'raw', # unbearbeitet
'include': 'include', # einfügen, "füge ein" would be more like a command.
# einfügung would be the noun.
'ersetzung': 'replace', # ersetzen, ersetze
'unicode': 'unicode',
'klasse': 'class', # offer "class" too ?
'role (translation required)': 'role',
'inhalt': 'contents',
'sectnum': 'sectnum',
'section-numbering': 'sectnum',
'target-notes': 'target-notes',
#'footnotes': 'footnotes',
#'citations': 'citations',
'restructuredtext-test-directive': 'restructuredtext-test-directive'}
"""German name to registered (in directives/__init__.py) directive name
mapping."""
roles = {
'abbreviation (translation required)': 'abbreviation',
'acronym (translation required)': 'acronym',
'index (translation required)': 'index',
'subscript (translation required)': 'subscript',
'superscript (translation required)': 'superscript',
'title-reference (translation required)': 'title-reference',
'pep-reference (translation required)': 'pep-reference',
'rfc-reference (translation required)': 'rfc-reference',
'emphasis (translation required)': 'emphasis',
'strong (translation required)': 'strong',
'literal (translation required)': 'literal',
'named-reference (translation required)': 'named-reference',
'anonymous-reference (translation required)': 'anonymous-reference',
'footnote-reference (translation required)': 'footnote-reference',
'citation-reference (translation required)': 'citation-reference',
'substitution-reference (translation required)': 'substitution-reference',
'target (translation required)': 'target',
'uri-reference (translation required)': 'uri-reference',}
"""Mapping of German role names to canonical role names for interpreted text.
"""
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:50 $
# Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
English-language mappings for language-dependent features of
reStructuredText.
"""
__docformat__ = 'reStructuredText'
directives = {
# language-dependent: fixed
'attention': 'attention',
'caution': 'caution',
'danger': 'danger',
'error': 'error',
'hint': 'hint',
'important': 'important',
'note': 'note',
'tip': 'tip',
'warning': 'warning',
'admonition': 'admonition',
'sidebar': 'sidebar',
'topic': 'topic',
'line-block': 'line-block',
'parsed-literal': 'parsed-literal',
'rubric': 'rubric',
'epigraph': 'epigraph',
'highlights': 'highlights',
'pull-quote': 'pull-quote',
#'questions': 'questions',
'table': 'table',
'csv-table': 'csv-table',
#'qa': 'questions',
#'faq': 'questions',
'meta': 'meta',
#'imagemap': 'imagemap',
'image': 'image',
'figure': 'figure',
'include': 'include',
'raw': 'raw',
'replace': 'replace',
'unicode': 'unicode',
'class': 'class',
'role': 'role',
'contents': 'contents',
'sectnum': 'sectnum',
'section-numbering': 'sectnum',
#'footnotes': 'footnotes',
#'citations': 'citations',
'target-notes': 'target-notes',
'restructuredtext-test-directive': 'restructuredtext-test-directive'}
"""English name to registered (in directives/__init__.py) directive name
mapping."""
roles = {
# language-dependent: fixed
'abbreviation': 'abbreviation',
'ab': 'abbreviation',
'acronym': 'acronym',
'ac': 'acronym',
'index': 'index',
'i': 'index',
'subscript': 'subscript',
'sub': 'subscript',
'superscript': 'superscript',
'sup': 'superscript',
'title-reference': 'title-reference',
'title': 'title-reference',
't': 'title-reference',
'pep-reference': 'pep-reference',
'pep': 'pep-reference',
'rfc-reference': 'rfc-reference',
'rfc': 'rfc-reference',
'emphasis': 'emphasis',
'strong': 'strong',
'literal': 'literal',
'named-reference': 'named-reference',
'anonymous-reference': 'anonymous-reference',
'footnote-reference': 'footnote-reference',
'citation-reference': 'citation-reference',
'substitution-reference': 'substitution-reference',
'target': 'target',
'uri-reference': 'uri-reference',
'uri': 'uri-reference',
'url': 'uri-reference',}
"""Mapping of English role names to canonical role names for interpreted text.
"""
# Author: Marcelo Huerta San Martin
# Contact: richieadler@users.sourceforge.net
# Revision: $Revision: 1.1.2.1.8.1 $
# Date: $Date: 2004/05/12 19:57:50 $
# Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
Esperanto-language mappings for language-dependent features of
reStructuredText.
"""
__docformat__ = 'reStructuredText'
directives = {
# language-dependent: fixed
u'atentu': 'attention',
u'zorgu': 'caution',
u'dangxero': 'danger',
u'dan\u011dero': 'danger',
u'eraro': 'error',
u'spuro': 'hint',
u'grava': 'important',
u'noto': 'note',
u'helpeto': 'tip',
u'averto': 'warning',
u'admono': 'admonition',
u'flankteksto': 'sidebar',
u'temo': 'topic',
u'linea-bloko': 'line-block',
u'analizota-literalo': 'parsed-literal',
u'rubriko': 'rubric',
u'epigrafo': 'epigraph',
u'elstarajxoj': 'highlights',
u'elstara\u0135oj': 'highlights',
u'ekstera-citajxo': 'pull-quote',
u'ekstera-cita\u0135o': 'pull-quote',
#'questions': 'questions',
#'qa': 'questions',
#'faq': 'questions',
u'tabelo': 'table',
u'tabelo-vdk': 'csv-table', # "valoroj disigitaj per komoj"
u'tabelo-csv': 'csv-table',
u'meta': 'meta',
#'imagemap': 'imagemap',
u'bildo': 'image',
u'figuro': 'figure',
u'inkludi': 'include',
u'senformata': 'raw',
u'anstatauxi': 'replace',
u'anstata\u016di': 'replace',
u'unicode': 'unicode',
u'klaso': 'class',
u'rolo': 'role',
u'enhavo': 'contents',
u'seknum': 'sectnum',
u'sekcia-numerado': 'sectnum',
#'footnotes': 'footnotes',
#'citations': 'citations',
u'celaj-notoj': 'target-notes',
u'restructuredtext-test-directive': 'restructuredtext-test-directive'}
"""Esperanto name to registered (in directives/__init__.py) directive name
mapping."""
roles = {
# language-dependent: fixed
u'mallongigo': 'abbreviation',
u'mall': 'abbreviation',
u'komenclitero': 'acronym',
u'kl': 'acronym',
u'indekso': 'index',
u'i': 'index',
u'subskribo': 'subscript',
u'sub': 'subscript',
u'supraskribo': 'superscript',
u'sup': 'superscript',
u'titola-referenco': 'title-reference',
u'titolo': 'title-reference',
u't': 'title-reference',
u'pep-referenco': 'pep-reference',
u'pep': 'pep-reference',
u'rfc-referenco': 'rfc-reference',
u'rfc': 'rfc-reference',
u'emfazo': 'emphasis',
u'forta': 'strong',
u'litera': 'literal',
u'nomita-referenco': 'named-reference',
u'nenomita-referenco': 'anonymous-reference',
u'piednota-referenco': 'footnote-reference',
u'citajxo-referenco': 'citation-reference',
u'cita\u0135o-referenco': 'citation-reference',
u'anstatauxa-referenco': 'substitution-reference',
u'anstata\u016da-referenco': 'substitution-reference',
u'celo': 'target',
u'uri-referenco': 'uri-reference',
u'uri': 'uri-reference',
u'url': 'uri-reference',}
"""Mapping of Esperanto role names to canonical role names for interpreted text.
"""
# -*- coding: iso-8859-1 -*-
# Author: Marcelo Huerta San Martn
# Contact: mghsm@uol.com.ar
# Revision: $Revision: 1.1.2.3.8.1 $
# Date: $Date: 2004/05/12 19:57:51 $
# Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
Spanish-language mappings for language-dependent features of
reStructuredText.
"""
__docformat__ = 'reStructuredText'
directives = {
u'atenci\u00f3n': 'attention',
u'atencion': 'attention',
u'precauci\u00f3n': 'caution',
u'precaucion': 'caution',
u'peligro': 'danger',
u'error': 'error',
u'sugerencia': 'hint',
u'importante': 'important',
u'nota': 'note',
u'consejo': 'tip',
u'advertencia': 'warning',
u'exhortacion': 'admonition',
u'exhortaci\u00f3n': 'admonition',
u'nota-al-margen': 'sidebar',
u'tema': 'topic',
u'bloque-de-lineas': 'line-block',
u'bloque-de-l\u00edneas': 'line-block',
u'literal-evaluado': 'parsed-literal',
u'firma': 'rubric',
u'ep\u00edgrafe': 'epigraph',
u'epigrafe': 'epigraph',
u'destacado': 'highlights',
u'cita-destacada': 'pull-quote',
#'questions': 'questions',
#'qa': 'questions',
#'faq': 'questions',
u'tabla': 'table',
u'tabla-vsc': 'csv-table',
u'tabla-csv': 'csv-table',
u'meta': 'meta',
#'imagemap': 'imagemap',
u'imagen': 'image',
u'figura': 'figure',
u'incluir': 'include',
u'raw': 'raw',
u'reemplazar': 'replace',
u'unicode': 'unicode',
u'clase': 'class',
u'rol': 'role',
u'contenido': 'contents',
u'numseccion': 'sectnum',
u'numsecci\u00f3n': 'sectnum',
u'numeracion-seccion': 'sectnum',
u'numeraci\u00f3n-secci\u00f3n': 'sectnum',
u'notas-destino': 'target-notes',
#'footnotes': 'footnotes',
#'citations': 'citations',
u'restructuredtext-test-directive': 'restructuredtext-test-directive'}
"""Spanish name to registered (in directives/__init__.py) directive name
mapping."""
roles = {
u'abreviatura': 'abbreviation',
u'ab': 'abbreviation',
u'acronimo': 'acronym',
u'acronimo': 'acronym',
u'ac': 'acronym',
u'indice': 'index',
u'i': 'index',
u'subindice': 'subscript',
u'sub\u00edndice': 'subscript',
u'superindice': 'superscript',
u'super\u00edndice': 'superscript',
u'referencia-titulo': 'title-reference',
u'titulo': 'title-reference',
u't': 'title-reference',
u'referencia-pep': 'pep-reference',
u'pep': 'pep-reference',
u'referencia-rfc': 'rfc-reference',
u'rfc': 'rfc-reference',
u'enfasis': 'emphasis',
u'\u00e9nfasis': 'emphasis',
u'destacado': 'strong',
u'literal': 'literal',
u'referencia-con-nombre': 'named-reference',
u'referencia-anonima': 'anonymous-reference',
u'referencia-an\u00f3nima': 'anonymous-reference',
u'referencia-nota-al-pie': 'footnote-reference',
u'referencia-cita': 'citation-reference',
u'referencia-sustitucion': 'substitution-reference',
u'referencia-sustituci\u00f3n': 'substitution-reference',
u'destino': 'target',
u'referencia-uri': 'uri-reference',
u'uri': 'uri-reference',
u'url': 'uri-reference',
}
"""Mapping of Spanish role names to canonical role names for interpreted text.
"""
# Authors: David Goodger; William Dode
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:51 $
# Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
French-language mappings for language-dependent features of
reStructuredText.
"""
__docformat__ = 'reStructuredText'
directives = {
u'attention': 'attention',
u'pr\u00E9caution': 'caution',
u'danger': 'danger',
u'erreur': 'error',
u'conseil': 'hint',
u'important': 'important',
u'note': 'note',
u'astuce': 'tip',
u'avertissement': 'warning',
u'admonition': 'admonition',
u'encadr\u00E9': 'sidebar',
u'sujet': 'topic',
u'bloc-textuel': 'line-block',
u'bloc-interpr\u00E9t\u00E9': 'parsed-literal',
u'code-interpr\u00E9t\u00E9': 'parsed-literal',
u'intertitre': 'rubric',
u'exergue': 'epigraph',
u'\u00E9pigraphe': 'epigraph',
u'chapeau': 'highlights',
u'accroche': 'pull-quote',
#u'questions': 'questions',
#u'qr': 'questions',
#u'faq': 'questions',
u'tableau': 'table',
u'csv-table (translation required)': 'csv-table',
u'm\u00E9ta': 'meta',
#u'imagemap (translation required)': 'imagemap',
u'image': 'image',
u'figure': 'figure',
u'inclure': 'include',
u'brut': 'raw',
u'remplacer': 'replace',
u'remplace': 'replace',
u'unicode': 'unicode',
u'classe': 'class',
u'role (translation required)': 'role',
u'sommaire': 'contents',
u'table-des-mati\u00E8res': 'contents',
u'sectnum': 'sectnum',
u'section-num\u00E9rot\u00E9e': 'sectnum',
u'liens': 'target-notes',
#u'footnotes (translation required)': 'footnotes',
#u'citations (translation required)': 'citations',
}
"""French name to registered (in directives/__init__.py) directive name
mapping."""
roles = {
u'abr\u00E9viation': 'abbreviation',
u'acronyme': 'acronym',
u'sigle': 'acronym',
u'index': 'index',
u'indice': 'subscript',
u'ind': 'subscript',
u'exposant': 'superscript',
u'exp': 'superscript',
u'titre-r\u00E9f\u00E9rence': 'title-reference',
u'titre': 'title-reference',
u'pep-r\u00E9f\u00E9rence': 'pep-reference',
u'rfc-r\u00E9f\u00E9rence': 'rfc-reference',
u'emphase': 'emphasis',
u'fort': 'strong',
u'litt\u00E9ral': 'literal',
u'nomm\u00E9e-r\u00E9f\u00E9rence': 'named-reference',
u'anonyme-r\u00E9f\u00E9rence': 'anonymous-reference',
u'note-r\u00E9f\u00E9rence': 'footnote-reference',
u'citation-r\u00E9f\u00E9rence': 'citation-reference',
u'substitution-r\u00E9f\u00E9rence': 'substitution-reference',
u'lien': 'target',
u'uri-r\u00E9f\u00E9rence': 'uri-reference',}
"""Mapping of French role names to canonical role names for interpreted text.
"""
# Author: Nicola Larosa, Lele Gaifax
# Contact: docutils@tekNico.net, lele@seldati.it
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:51 $
# Copyright: This module has been placed in the public domain.
"""
Italian-language mappings for language-dependent features of
reStructuredText.
"""
__docformat__ = 'reStructuredText'
directives = {
'attenzione': 'attention',
'cautela': 'caution',
'pericolo': 'danger',
'errore': 'error',
'suggerimento': 'hint',
'importante': 'important',
'nota': 'note',
'consiglio': 'tip',
'avvertenza': 'warning',
'ammonizione': 'admonition',
'riquadro': 'sidebar',
'argomento': 'topic',
'blocco-di-righe': 'line-block',
'blocco-interpretato': 'parsed-literal',
'rubrica': 'rubric',
'epigrafe': 'epigraph',
'evidenzia': 'highlights',
'pull-quote (translation required)': 'pull-quote',
#'questions': 'questions',
#'qa': 'questions',
#'faq': 'questions',
'tabella': 'table',
'csv-table (translation required)': 'csv-table',
'meta': 'meta',
#'imagemap': 'imagemap',
'immagine': 'image',
'figura': 'figure',
'includi': 'include',
'grezzo': 'raw',
'sostituisci': 'replace',
'unicode': 'unicode',
'classe': 'class',
'ruolo': 'role',
'indice': 'contents',
'seznum': 'sectnum',
'sezioni-autonumerate': 'sectnum',
'annota-riferimenti-esterni': 'target-notes',
#'footnotes': 'footnotes',
#'citations': 'citations',
'restructuredtext-test-directive': 'restructuredtext-test-directive'}
"""Italian name to registered (in directives/__init__.py) directive name
mapping."""
roles = {
'abbreviazione': 'abbreviation',
'acronimo': 'acronym',
'indice': 'index',
'deponente': 'subscript',
'esponente': 'superscript',
'riferimento-titolo': 'title-reference',
'riferimento-pep': 'pep-reference',
'riferimento-rfc': 'rfc-reference',
'enfasi': 'emphasis',
'forte': 'strong',
'letterale': 'literal',
'riferimento-con-nome': 'named-reference',
'riferimento-anonimo': 'anonymous-reference',
'riferimento-nota': 'footnote-reference',
'riferimento-citazione': 'citation-reference',
'riferimento-sostituzione': 'substitution-reference',
'destinazione': 'target',
'riferimento-uri': 'uri-reference',}
"""Mapping of Italian role names to canonical role names for interpreted text.
"""
# -*- coding: iso-8859-1 -*-
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.1.2.1 $
# Date: $Date: 2004/05/12 19:57:51 $
# Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
Brazilian Portuguese-language mappings for language-dependent features of
reStructuredText.
"""
__docformat__ = 'reStructuredText'
directives = {
# language-dependent: fixed
u'aten\u00E7\u00E3o': 'attention',
'cuidado': 'caution',
'perigo': 'danger',
'erro': 'error',
u'sugest\u00E3o': 'hint',
'importante': 'important',
'nota': 'note',
'dica': 'tip',
'aviso': 'warning',
u'exorta\u00E7\u00E3o': 'admonition',
'barra-lateral': 'sidebar',
u't\u00F3pico': 'topic',
'bloco-de-linhas': 'line-block',
'literal-interpretado': 'parsed-literal',
'rubrica': 'rubric',
u'ep\u00EDgrafo': 'epigraph',
'destaques': 'highlights',
u'cita\u00E7\u00E3o-destacada': 'pull-quote',
#'perguntas': 'questions',
#'qa': 'questions',
#'faq': 'questions',
u'table (translation required)': 'table',
u'csv-table (translation required)': 'csv-table',
'meta': 'meta',
#'imagemap': 'imagemap',
'imagem': 'image',
'figura': 'figure',
u'inclus\u00E3o': 'include',
'cru': 'raw',
u'substitui\u00E7\u00E3o': 'replace',
'unicode': 'unicode',
'classe': 'class',
'role (translation required)': 'role',
u'\u00EDndice': 'contents',
'numsec': 'sectnum',
u'numera\u00E7\u00E3o-de-se\u00E7\u00F5es': 'sectnum',
#u'notas-de-rorap\u00E9': 'footnotes',
#u'cita\u00E7\u00F5es': 'citations',
u'links-no-rodap\u00E9': 'target-notes',
'restructuredtext-test-directive': 'restructuredtext-test-directive'}
"""Brazilian Portuguese name to registered (in directives/__init__.py)
directive name mapping."""
roles = {
# language-dependent: fixed
u'abbrevia\u00E7\u00E3o': 'abbreviation',
'ab': 'abbreviation',
u'acr\u00F4nimo': 'acronym',
'ac': 'acronym',
u'\u00EDndice-remissivo': 'index',
'i': 'index',
'subscrito': 'subscript',
'sub': 'subscript',
'sobrescrito': 'superscript',
'sob': 'superscript',
u'refer\u00EAncia-a-t\u00EDtulo': 'title-reference',
u't\u00EDtulo': 'title-reference',
't': 'title-reference',
u'refer\u00EAncia-a-pep': 'pep-reference',
'pep': 'pep-reference',
u'refer\u00EAncia-a-rfc': 'rfc-reference',
'rfc': 'rfc-reference',
u'\u00EAnfase': 'emphasis',
'forte': 'strong',
'literal': 'literal',
u'refer\u00EAncia-por-nome': 'named-reference',
u'refer\u00EAncia-an\u00F4nima': 'anonymous-reference',
u'refer\u00EAncia-a-nota-de-rodap\u00E9': 'footnote-reference',
u'refer\u00EAncia-a-cita\u00E7\u00E3o': 'citation-reference',
u'refer\u00EAncia-a-substitui\u00E7\u00E3o': 'substitution-reference',
'alvo': 'target',
u'refer\u00EAncia-a-uri': 'uri-reference',
'uri': 'uri-reference',
'url': 'uri-reference',}
"""Mapping of Brazilian Portuguese role names to canonical role names
for interpreted text."""
# Author: Roman Suzi
# Contact: rnd@onego.ru
# Revision: $Revision: 1.1.2.3.8.1 $
# Date: $Date: 2004/05/12 19:57:51 $
# Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
Russian-language mappings for language-dependent features of
reStructuredText.
"""
__docformat__ = 'reStructuredText'
directives = {
u'\u0431\u043b\u043e\u043a-\u0441\u0442\u0440\u043e\u043a': u'line-block',
u'meta': u'meta',
u'\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043d\u044b\u0439-\u043b\u0438\u0442\u0435\u0440\u0430\u043b':
u'parsed-literal',
u'\u0432\u044b\u0434\u0435\u043b\u0435\u043d\u043d\u0430\u044f-\u0446\u0438\u0442\u0430\u0442\u0430':
u'pull-quote',
u'table (translation required)': 'table',
u'csv-table (translation required)': 'csv-table',
u'\u0441\u044b\u0440\u043e\u0439': u'raw',
u'\u0437\u0430\u043c\u0435\u043d\u0430': u'replace',
u'\u0442\u0435\u0441\u0442\u043e\u0432\u0430\u044f-\u0434\u0438\u0440\u0435\u043a\u0442\u0438\u0432\u0430-restructuredtext':
u'restructuredtext-test-directive',
u'\u0446\u0435\u043b\u0435\u0432\u044b\u0435-\u0441\u043d\u043e\u0441\u043a\u0438':
u'target-notes',
u'unicode': u'unicode',
u'\u0431\u043e\u043a\u043e\u0432\u0430\u044f-\u043f\u043e\u043b\u043e\u0441\u0430':
u'sidebar',
u'\u0432\u0430\u0436\u043d\u043e': u'important',
u'\u0432\u043a\u043b\u044e\u0447\u0430\u0442\u044c': u'include',
u'\u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435': u'attention',
u'\u0432\u044b\u0434\u0435\u043b\u0435\u043d\u0438\u0435': u'highlights',
u'\u0437\u0430\u043c\u0435\u0447\u0430\u043d\u0438\u0435': u'admonition',
u'\u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435':
u'image',
u'\u043a\u043b\u0430\u0441\u0441': u'class',
u'role (translation required)': 'role',
u'\u043d\u043e\u043c\u0435\u0440-\u0440\u0430\u0437\u0434\u0435\u043b\u0430':
u'sectnum',
u'\u043d\u0443\u043c\u0435\u0440\u0430\u0446\u0438\u044f-\u0440\u0430\u0437'
u'\u0434\u0435\u043b\u043e\u0432': u'sectnum',
u'\u043e\u043f\u0430\u0441\u043d\u043e': u'danger',
u'\u043e\u0441\u0442\u043e\u0440\u043e\u0436\u043d\u043e': u'caution',
u'\u043e\u0448\u0438\u0431\u043a\u0430': u'error',
u'\u043f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430': u'tip',
u'\u043f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436\u0434\u0435\u043d'
u'\u0438\u0435': u'warning',
u'\u043f\u0440\u0438\u043c\u0435\u0447\u0430\u043d\u0438\u0435': u'note',
u'\u0440\u0438\u0441\u0443\u043d\u043e\u043a': u'figure',
u'\u0440\u0443\u0431\u0440\u0438\u043a\u0430': u'rubric',
u'\u0441\u043e\u0432\u0435\u0442': u'hint',
u'\u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435': u'contents',
u'\u0442\u0435\u043c\u0430': u'topic',
u'\u044d\u043f\u0438\u0433\u0440\u0430\u0444': u'epigraph'}
"""Russian name to registered (in directives/__init__.py) directive name
mapping."""
roles = {
u'\u0430\u043a\u0440\u043e\u043d\u0438\u043c': 'acronym',
u'\u0430\u043d\u043e\u043d\u0438\u043c\u043d\u0430\u044f-\u0441\u0441\u044b\u043b\u043a\u0430':
'anonymous-reference',
u'\u0431\u0443\u043a\u0432\u0430\u043b\u044c\u043d\u043e': 'literal',
u'\u0432\u0435\u0440\u0445\u043d\u0438\u0439-\u0438\u043d\u0434\u0435\u043a\u0441':
'superscript',
u'\u0432\u044b\u0434\u0435\u043b\u0435\u043d\u0438\u0435': 'emphasis',
u'\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u043d\u0430\u044f-\u0441\u0441\u044b\u043b\u043a\u0430':
'named-reference',
u'\u0438\u043d\u0434\u0435\u043a\u0441': 'index',
u'\u043d\u0438\u0436\u043d\u0438\u0439-\u0438\u043d\u0434\u0435\u043a\u0441':
'subscript',
u'\u0441\u0438\u043b\u044c\u043d\u043e\u0435-\u0432\u044b\u0434\u0435\u043b\u0435\u043d\u0438\u0435':
'strong',
u'\u0441\u043e\u043a\u0440\u0430\u0449\u0435\u043d\u0438\u0435':
'abbreviation',
u'\u0441\u0441\u044b\u043b\u043a\u0430-\u0437\u0430\u043c\u0435\u043d\u0430':
'substitution-reference',
u'\u0441\u0441\u044b\u043b\u043a\u0430-\u043d\u0430-pep': 'pep-reference',
u'\u0441\u0441\u044b\u043b\u043a\u0430-\u043d\u0430-rfc': 'rfc-reference',
u'\u0441\u0441\u044b\u043b\u043a\u0430-\u043d\u0430-uri': 'uri-reference',
u'\u0441\u0441\u044b\u043b\u043a\u0430-\u043d\u0430-\u0437\u0430\u0433\u043b\u0430\u0432\u0438\u0435':
'title-reference',
u'\u0441\u0441\u044b\u043b\u043a\u0430-\u043d\u0430-\u0441\u043d\u043e\u0441\u043a\u0443':
'footnote-reference',
u'\u0446\u0438\u0442\u0430\u0442\u043d\u0430\u044f-\u0441\u0441\u044b\u043b\u043a\u0430':
'citation-reference',
u'\u0446\u0435\u043b\u044c': 'target'}
"""Mapping of Russian role names to canonical role names for interpreted text.
"""
# Author: Miroslav Vasko
# Contact: zemiak@zoznam.sk
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:51 $
# Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
Slovak-language mappings for language-dependent features of
reStructuredText.
"""
__docformat__ = 'reStructuredText'
directives = {
u'pozor': 'attention',
u'opatrne': 'caution',
u'nebezpe\xe8enstvo': 'danger',
u'chyba': 'error',
u'rada': 'hint',
u'd\xf4le\x9eit\xe9': 'important',
u'pozn\xe1mka': 'note',
u'tip': 'tip',
u'varovanie': 'warning',
u'admonition (translation required)': 'admonition',
u'sidebar (translation required)': 'sidebar',
u't\xe9ma': 'topic',
u'blok-riadkov': 'line-block',
u'parsed-literal': 'parsed-literal',
u'rubric (translation required)': 'rubric',
u'epigraph (translation required)': 'epigraph',
u'highlights (translation required)': 'highlights',
u'pull-quote (translation required)': 'pull-quote',
#u'questions': 'questions',
#u'qa': 'questions',
#u'faq': 'questions',
u'table (translation required)': 'table',
u'csv-table (translation required)': 'csv-table',
u'meta': 'meta',
#u'imagemap': 'imagemap',
u'obr\xe1zok': 'image',
u'tvar': 'figure',
u'vlo\x9ei\x9d': 'include',
u'raw': 'raw',
u'nahradi\x9d': 'replace',
u'unicode': 'unicode',
u'class (translation required)': 'class',
u'role (translation required)': 'role',
u'obsah': 'contents',
u'\xe8as\x9d': 'sectnum',
u'\xe8as\x9d-\xe8\xedslovanie': 'sectnum',
u'cie\xbeov\xe9-pozn\xe1mky': 'target-notes',
#u'footnotes': 'footnotes',
#u'citations': 'citations',
}
"""Slovak name to registered (in directives/__init__.py) directive name
mapping."""
roles = {
u'abbreviation (translation required)': 'abbreviation',
u'acronym (translation required)': 'acronym',
u'index (translation required)': 'index',
u'subscript (translation required)': 'subscript',
u'superscript (translation required)': 'superscript',
u'title-reference (translation required)': 'title-reference',
u'pep-reference (translation required)': 'pep-reference',
u'rfc-reference (translation required)': 'rfc-reference',
u'emphasis (translation required)': 'emphasis',
u'strong (translation required)': 'strong',
u'literal (translation required)': 'literal',
u'named-reference (translation required)': 'named-reference',
u'anonymous-reference (translation required)': 'anonymous-reference',
u'footnote-reference (translation required)': 'footnote-reference',
u'citation-reference (translation required)': 'citation-reference',
u'substitution-reference (translation required)': 'substitution-reference',
u'target (translation required)': 'target',
u'uri-reference (translation required)': 'uri-reference',}
"""Mapping of Slovak role names to canonical role names for interpreted text.
"""
# Author: Adam Chodorowski
# Contact: chodorowski@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:51 $
# Copyright: This module has been placed in the public domain.
# New language mappings are welcome. Before doing a new translation, please
# read <http://docutils.sf.net/spec/howto/i18n.html>. Two files must be
# translated for each language: one in docutils/languages, the other in
# docutils/parsers/rst/languages.
"""
Swedish language mappings for language-dependent features of reStructuredText.
"""
__docformat__ = 'reStructuredText'
directives = {
u'observera': 'attention',
u'caution (translation required)': 'caution',
u'fara': 'danger',
u'fel': 'error',
u'v\u00e4gledning': 'hint',
u'viktigt': 'important',
u'notera': 'note',
u'tips': 'tip',
u'varning': 'warning',
u'admonition (translation required)': 'admonition',
u'sidebar (translation required)': 'sidebar',
u'\u00e4mne': 'topic',
u'line-block (translation required)': 'line-block',
u'parsed-literal (translation required)': 'parsed-literal',
u'mellanrubrik': 'rubric',
u'epigraph (translation required)': 'epigraph',
u'highlights (translation required)': 'highlights',
u'pull-quote (translation required)': 'pull-quote',
# u'fr\u00e5gor': 'questions',
# NOTE: A bit long, but recommended by http://www.nada.kth.se/dataterm/:
# u'fr\u00e5gor-och-svar': 'questions',
# u'vanliga-fr\u00e5gor': 'questions',
u'table (translation required)': 'table',
u'csv-table (translation required)': 'csv-table',
u'meta': 'meta',
# u'bildkarta': 'imagemap', # FIXME: Translation might be too literal.
u'bild': 'image',
u'figur': 'figure',
u'inkludera': 'include',
u'r\u00e5': 'raw', # FIXME: Translation might be too literal.
u'ers\u00e4tt': 'replace',
u'unicode': 'unicode',
u'class (translation required)': 'class',
u'role (translation required)': 'role',
u'inneh\u00e5ll': 'contents',
u'sektionsnumrering': 'sectnum',
u'target-notes (translation required)': 'target-notes',
# u'fotnoter': 'footnotes',
# u'citeringar': 'citations',
}
"""Swedish name to registered (in directives/__init__.py) directive name
mapping."""
roles = {
u'abbreviation (translation required)': 'abbreviation',
u'acronym (translation required)': 'acronym',
u'index (translation required)': 'index',
u'subscript (translation required)': 'subscript',
u'superscript (translation required)': 'superscript',
u'title-reference (translation required)': 'title-reference',
u'pep-reference (translation required)': 'pep-reference',
u'rfc-reference (translation required)': 'rfc-reference',
u'emphasis (translation required)': 'emphasis',
u'strong (translation required)': 'strong',
u'literal (translation required)': 'literal',
u'named-reference (translation required)': 'named-reference',
u'anonymous-reference (translation required)': 'anonymous-reference',
u'footnote-reference (translation required)': 'footnote-reference',
u'citation-reference (translation required)': 'citation-reference',
u'substitution-reference (translation required)': 'substitution-reference',
u'target (translation required)': 'target',
u'uri-reference (translation required)': 'uri-reference',}
"""Mapping of Swedish role names to canonical role names for interpreted text.
"""
# Author: Edward Loper
# Contact: edloper@gradient.cis.upenn.edu
# Revision: $Revision: 1.1.2.1 $
# Date: $Date: 2004/05/12 19:57:46 $
# Copyright: This module has been placed in the public domain.
"""
This module defines standard interpreted text role functions, a registry for
interpreted text roles, and an API for adding to and retrieving from the
registry.
The interface for interpreted role functions is as follows::
def role_fn(name, rawtext, text, lineno, inliner,
options={}, content=[]):
code...
# Set function attributes for customization:
role_fn.options = ...
role_fn.content = ...
Parameters:
- ``name`` is the local name of the interpreted text role, the role name
actually used in the document.
- ``rawtext`` is a string containing the entire interpreted text construct.
Return it as a ``problematic`` node linked to a system message if there is a
problem.
- ``text`` is the interpreted text content.
- ``lineno`` is the line number where the interpreted text beings.
- ``inliner`` is the Inliner object that called the role function.
It defines the following useful attributes: ``reporter``,
``problematic``, ``memo``, ``parent``, ``document``.
- ``options``: A dictionary of directive options for customization, to be
interpreted by the role function. Used for additional attributes for the
generated elements and other functionality.
- ``content``: A list of strings, the directive content for customization
("role" directive). To be interpreted by the role function.
Function attributes for customization, interpreted by the "role" directive:
- ``options``: A dictionary, mapping known option names to conversion
functions such as `int` or `float`. ``None`` or an empty dict implies no
options to parse. Several directive option conversion functions are defined
in the `directives` module.
All role functions implicitly support the "class" option, unless disabled
with an explicit ``{'class': None}``.
- ``content``: A boolean; true if content is allowed. Client code must handle
the case where content is required but not supplied (an empty content list
will be supplied).
Note that unlike directives, the "arguments" function attribute is not
supported for role customization. Directive arguments are handled by the
"role" directive itself.
Interpreted role functions return a tuple of two values:
- A list of nodes which will be inserted into the document tree at the
point where the interpreted role was encountered (can be an empty
list).
- A list of system messages, which will be inserted into the document tree
immediately after the end of the current inline block (can also be empty).
"""
__docformat__ = 'reStructuredText'
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.parsers.rst.languages import en as _fallback_language_module
DEFAULT_INTERPRETED_ROLE = 'title-reference'
"""
The canonical name of the default interpreted role. This role is used
when no role is specified for a piece of interpreted text.
"""
_role_registry = {}
"""Mapping of canonical role names to role functions. Language-dependent role
names are defined in the ``language`` subpackage."""
_roles = {}
"""Mapping of local or language-dependent interpreted text role names to role
functions."""
def role(role_name, language_module, lineno, reporter):
"""
Locate and return a role function from its language-dependent name, along
with a list of system messages. If the role is not found in the current
language, check English. Return a 2-tuple: role function (``None`` if the
named role cannot be found) and a list of system messages.
"""
normname = role_name.lower()
messages = []
msg_text = []
if _roles.has_key(normname):
return _roles[normname], messages
if role_name:
canonicalname = None
try:
canonicalname = language_module.roles[normname]
except AttributeError, error:
msg_text.append('Problem retrieving role entry from language '
'module %r: %s.' % (language_module, error))
except KeyError:
msg_text.append('No role entry for "%s" in module "%s".'
% (role_name, language_module.__name__))
else:
canonicalname = DEFAULT_INTERPRETED_ROLE
# If we didn't find it, try English as a fallback.
if not canonicalname:
try:
canonicalname = _fallback_language_module.roles[normname]
msg_text.append('Using English fallback for role "%s".'
% role_name)
except KeyError:
msg_text.append('Trying "%s" as canonical role name.'
% role_name)
# The canonical name should be an English name, but just in case:
canonicalname = normname
# Collect any messages that we generated.
if msg_text:
message = reporter.info('\n'.join(msg_text), line=lineno)
messages.append(message)
# Look the role up in the registry, and return it.
if _role_registry.has_key(canonicalname):
role_fn = _role_registry[canonicalname]
register_local_role(normname, role_fn)
return role_fn, messages
else:
return None, messages # Error message will be generated by caller.
def register_canonical_role(name, role_fn):
"""
Register an interpreted text role by its canonical name.
:Parameters:
- `name`: The canonical name of the interpreted role.
- `role_fn`: The role function. See the module docstring.
"""
set_implicit_options(role_fn)
_role_registry[name] = role_fn
def register_local_role(name, role_fn):
"""
Register an interpreted text role by its local or language-dependent name.
:Parameters:
- `name`: The local or language-dependent name of the interpreted role.
- `role_fn`: The role function. See the module docstring.
"""
set_implicit_options(role_fn)
_roles[name] = role_fn
def set_implicit_options(role_fn):
"""
Add customization options to role functions, unless explicitly set or
disabled.
"""
if not hasattr(role_fn, 'options') or role_fn.options is None:
role_fn.options = {'class': directives.class_option}
elif not role_fn.options.has_key('class'):
role_fn.options['class'] = directives.class_option
def register_generic_role(canonical_name, node_class):
"""For roles which simply wrap a given `node_class` around the text."""
role = GenericRole(canonical_name, node_class)
register_canonical_role(canonical_name, role)
class GenericRole:
"""
Generic interpreted text role, where the interpreted text is simply
wrapped with the provided node class.
"""
def __init__(self, role_name, node_class):
self.name = role_name
self.node_class = node_class
def __call__(self, role, rawtext, text, lineno, inliner,
options={}, content=[]):
return [self.node_class(rawtext, text, **options)], []
class CustomRole:
"""
Wrapper for custom interpreted text roles.
"""
def __init__(self, role_name, base_role, options={}, content=[]):
self.name = role_name
self.base_role = base_role
self.options = None
if hasattr(base_role, 'options'):
self.options = base_role.options
self.content = None
if hasattr(base_role, 'content'):
self.content = base_role.content
self.supplied_options = options
self.supplied_content = content
def __call__(self, role, rawtext, text, lineno, inliner,
options={}, content=[]):
opts = self.supplied_options.copy()
opts.update(options)
cont = list(self.supplied_content)
if cont and content:
cont += '\n'
cont.extend(content)
return self.base_role(role, rawtext, text, lineno, inliner,
options=opts, content=cont)
def generic_custom_role(role, rawtext, text, lineno, inliner,
options={}, content=[]):
""""""
# Once nested inline markup is implemented, this and other methods should
# recursively call inliner.nested_parse().
return [nodes.inline(rawtext, text, **options)], []
generic_custom_role.options = {'class': directives.class_option}
######################################################################
# Define and register the standard roles:
######################################################################
register_generic_role('abbreviation', nodes.abbreviation)
register_generic_role('acronym', nodes.acronym)
register_generic_role('emphasis', nodes.emphasis)
register_generic_role('literal', nodes.literal)
register_generic_role('strong', nodes.strong)
register_generic_role('subscript', nodes.subscript)
register_generic_role('superscript', nodes.superscript)
register_generic_role('title-reference', nodes.title_reference)
def pep_reference_role(role, rawtext, text, lineno, inliner,
options={}, content=[]):
try:
pepnum = int(text)
if pepnum < 0 or pepnum > 9999:
raise ValueError
except ValueError:
msg = inliner.reporter.error(
'PEP number must be a number from 0 to 9999; "%s" is invalid.'
% text, line=lineno)
prb = inliner.problematic(rawtext, rawtext, msg)
return [prb], [msg]
# Base URL mainly used by inliner.pep_reference; so this is correct:
ref = inliner.document.settings.pep_base_url + inliner.pep_url % pepnum
return [nodes.reference(rawtext, 'PEP ' + text, refuri=ref, **options)], []
register_canonical_role('pep-reference', pep_reference_role)
def rfc_reference_role(role, rawtext, text, lineno, inliner,
options={}, content=[]):
try:
rfcnum = int(text)
if rfcnum <= 0:
raise ValueError
except ValueError:
msg = inliner.reporter.error(
'RFC number must be a number greater than or equal to 1; '
'"%s" is invalid.' % text, line=lineno)
prb = inliner.problematic(rawtext, rawtext, msg)
return [prb], [msg]
# Base URL mainly used by inliner.rfc_reference, so this is correct:
ref = inliner.document.settings.rfc_base_url + inliner.rfc_url % rfcnum
node = nodes.reference(rawtext, 'RFC ' + text, refuri=ref, **options)
return [node], []
register_canonical_role('rfc-reference', rfc_reference_role)
######################################################################
# Register roles that are currently unimplemented.
######################################################################
def unimplemented_role(role, rawtext, text, lineno, inliner, attributes={}):
msg = inliner.reporter.error(
'Interpreted text role "%s" not implemented.' % role, line=lineno)
prb = inliner.problematic(rawtext, rawtext, msg)
return [prb], [msg]
register_canonical_role('index', unimplemented_role)
register_canonical_role('named-reference', unimplemented_role)
register_canonical_role('anonymous-reference', unimplemented_role)
register_canonical_role('uri-reference', unimplemented_role)
register_canonical_role('footnote-reference', unimplemented_role)
register_canonical_role('citation-reference', unimplemented_role)
register_canonical_role('substitution-reference', unimplemented_role)
register_canonical_role('target', unimplemented_role)
# This should remain unimplemented, for testing purposes:
register_canonical_role('restructuredtext-unimplemented-role',
unimplemented_role)
"""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
This source diff could not be displayed because it is too large. You can view the blob instead.
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:47 $
# Copyright: This module has been placed in the public domain.
"""
This module defines table parser classes,which parse plaintext-graphic tables
and produce a well-formed data structure suitable for building a CALS table.
:Classes:
- `GridTableParser`: Parse fully-formed tables represented with a grid.
- `SimpleTableParser`: Parse simple tables, delimited by top & bottom
borders.
:Exception class: `TableMarkupError`
:Function:
`update_dict_of_lists()`: Merge two dictionaries containing list values.
"""
__docformat__ = 'reStructuredText'
import re
import sys
from docutils import DataError
class TableMarkupError(DataError): pass
class TableParser:
"""
Abstract superclass for the common parts of the syntax-specific parsers.
"""
head_body_separator_pat = None
"""Matches the row separator between head rows and body rows."""
def parse(self, block):
"""
Analyze the text `block` and return a table data structure.
Given a plaintext-graphic table in `block` (list of lines of text; no
whitespace padding), parse the table, construct and return the data
necessary to construct a CALS table or equivalent.
Raise `TableMarkupError` if there is any problem with the markup.
"""
self.setup(block)
self.find_head_body_sep()
self.parse_table()
structure = self.structure_from_cells()
return structure
def find_head_body_sep(self):
"""Look for a head/body row separator line; store the line index."""
for i in range(len(self.block)):
line = self.block[i]
if self.head_body_separator_pat.match(line):
if self.head_body_sep:
raise TableMarkupError(
'Multiple head/body row separators in table (at line '
'offset %s and %s); only one allowed.'
% (self.head_body_sep, i))
else:
self.head_body_sep = i
self.block[i] = line.replace('=', '-')
if self.head_body_sep == 0 or self.head_body_sep == (len(self.block)
- 1):
raise TableMarkupError('The head/body row separator may not be '
'the first or last line of the table.')
class GridTableParser(TableParser):
"""
Parse a grid table using `parse()`.
Here's an example of a grid table::
+------------------------+------------+----------+----------+
| Header row, column 1 | Header 2 | Header 3 | Header 4 |
+========================+============+==========+==========+
| body row 1, column 1 | column 2 | column 3 | column 4 |
+------------------------+------------+----------+----------+
| body row 2 | Cells may span columns. |
+------------------------+------------+---------------------+
| body row 3 | Cells may | - Table cells |
+------------------------+ span rows. | - contain |
| body row 4 | | - body elements. |
+------------------------+------------+---------------------+
Intersections use '+', row separators use '-' (except for one optional
head/body row separator, which uses '='), and column separators use '|'.
Passing the above table to the `parse()` method will result in the
following data structure::
([24, 12, 10, 10],
[[(0, 0, 1, ['Header row, column 1']),
(0, 0, 1, ['Header 2']),
(0, 0, 1, ['Header 3']),
(0, 0, 1, ['Header 4'])]],
[[(0, 0, 3, ['body row 1, column 1']),
(0, 0, 3, ['column 2']),
(0, 0, 3, ['column 3']),
(0, 0, 3, ['column 4'])],
[(0, 0, 5, ['body row 2']),
(0, 2, 5, ['Cells may span columns.']),
None,
None],
[(0, 0, 7, ['body row 3']),
(1, 0, 7, ['Cells may', 'span rows.', '']),
(1, 1, 7, ['- Table cells', '- contain', '- body elements.']),
None],
[(0, 0, 9, ['body row 4']), None, None, None]])
The first item is a list containing column widths (colspecs). The second
item is a list of head rows, and the third is a list of body rows. Each
row contains a list of cells. Each cell is either None (for a cell unused
because of another cell's span), or a tuple. A cell tuple contains four
items: the number of extra rows used by the cell in a vertical span
(morerows); the number of extra columns used by the cell in a horizontal
span (morecols); the line offset of the first line of the cell contents;
and the cell contents, a list of lines of text.
"""
head_body_separator_pat = re.compile(r'\+=[=+]+=\+ *$')
def setup(self, block):
self.block = block[:] # make a copy; it may be modified
self.block.disconnect() # don't propagate changes to parent
self.bottom = len(block) - 1
self.right = len(block[0]) - 1
self.head_body_sep = None
self.done = [-1] * len(block[0])
self.cells = []
self.rowseps = {0: [0]}
self.colseps = {0: [0]}
def parse_table(self):
"""
Start with a queue of upper-left corners, containing the upper-left
corner of the table itself. Trace out one rectangular cell, remember
it, and add its upper-right and lower-left corners to the queue of
potential upper-left corners of further cells. Process the queue in
top-to-bottom order, keeping track of how much of each text column has
been seen.
We'll end up knowing all the row and column boundaries, cell positions
and their dimensions.
"""
corners = [(0, 0)]
while corners:
top, left = corners.pop(0)
if top == self.bottom or left == self.right \
or top <= self.done[left]:
continue
result = self.scan_cell(top, left)
if not result:
continue
bottom, right, rowseps, colseps = result
update_dict_of_lists(self.rowseps, rowseps)
update_dict_of_lists(self.colseps, colseps)
self.mark_done(top, left, bottom, right)
cellblock = self.block.get_2D_block(top + 1, left + 1,
bottom, right)
cellblock.disconnect() # lines in cell can't sync with parent
self.cells.append((top, left, bottom, right, cellblock))
corners.extend([(top, right), (bottom, left)])
corners.sort()
if not self.check_parse_complete():
raise TableMarkupError('Malformed table; parse incomplete.')
def mark_done(self, top, left, bottom, right):
"""For keeping track of how much of each text column has been seen."""
before = top - 1
after = bottom - 1
for col in range(left, right):
assert self.done[col] == before
self.done[col] = after
def check_parse_complete(self):
"""Each text column should have been completely seen."""
last = self.bottom - 1
for col in range(self.right):
if self.done[col] != last:
return None
return 1
def scan_cell(self, top, left):
"""Starting at the top-left corner, start tracing out a cell."""
assert self.block[top][left] == '+'
result = self.scan_right(top, left)
return result
def scan_right(self, top, left):
"""
Look for the top-right corner of the cell, and make note of all column
boundaries ('+').
"""
colseps = {}
line = self.block[top]
for i in range(left + 1, self.right + 1):
if line[i] == '+':
colseps[i] = [top]
result = self.scan_down(top, left, i)
if result:
bottom, rowseps, newcolseps = result
update_dict_of_lists(colseps, newcolseps)
return bottom, i, rowseps, colseps
elif line[i] != '-':
return None
return None
def scan_down(self, top, left, right):
"""
Look for the bottom-right corner of the cell, making note of all row
boundaries.
"""
rowseps = {}
for i in range(top + 1, self.bottom + 1):
if self.block[i][right] == '+':
rowseps[i] = [right]
result = self.scan_left(top, left, i, right)
if result:
newrowseps, colseps = result
update_dict_of_lists(rowseps, newrowseps)
return i, rowseps, colseps
elif self.block[i][right] != '|':
return None
return None
def scan_left(self, top, left, bottom, right):
"""
Noting column boundaries, look for the bottom-left corner of the cell.
It must line up with the starting point.
"""
colseps = {}
line = self.block[bottom]
for i in range(right - 1, left, -1):
if line[i] == '+':
colseps[i] = [bottom]
elif line[i] != '-':
return None
if line[left] != '+':
return None
result = self.scan_up(top, left, bottom, right)
if result is not None:
rowseps = result
return rowseps, colseps
return None
def scan_up(self, top, left, bottom, right):
"""
Noting row boundaries, see if we can return to the starting point.
"""
rowseps = {}
for i in range(bottom - 1, top, -1):
if self.block[i][left] == '+':
rowseps[i] = [left]
elif self.block[i][left] != '|':
return None
return rowseps
def structure_from_cells(self):
"""
From the data collected by `scan_cell()`, convert to the final data
structure.
"""
rowseps = self.rowseps.keys() # list of row boundaries
rowseps.sort()
rowindex = {}
for i in range(len(rowseps)):
rowindex[rowseps[i]] = i # row boundary -> row number mapping
colseps = self.colseps.keys() # list of column boundaries
colseps.sort()
colindex = {}
for i in range(len(colseps)):
colindex[colseps[i]] = i # column boundary -> col number map
colspecs = [(colseps[i] - colseps[i - 1] - 1)
for i in range(1, len(colseps))] # list of column widths
# prepare an empty table with the correct number of rows & columns
onerow = [None for i in range(len(colseps) - 1)]
rows = [onerow[:] for i in range(len(rowseps) - 1)]
# keep track of # of cells remaining; should reduce to zero
remaining = (len(rowseps) - 1) * (len(colseps) - 1)
for top, left, bottom, right, block in self.cells:
rownum = rowindex[top]
colnum = colindex[left]
assert rows[rownum][colnum] is None, (
'Cell (row %s, column %s) already used.'
% (rownum + 1, colnum + 1))
morerows = rowindex[bottom] - rownum - 1
morecols = colindex[right] - colnum - 1
remaining -= (morerows + 1) * (morecols + 1)
# write the cell into the table
rows[rownum][colnum] = (morerows, morecols, top + 1, block)
assert remaining == 0, 'Unused cells remaining.'
if self.head_body_sep: # separate head rows from body rows
numheadrows = rowindex[self.head_body_sep]
headrows = rows[:numheadrows]
bodyrows = rows[numheadrows:]
else:
headrows = []
bodyrows = rows
return (colspecs, headrows, bodyrows)
class SimpleTableParser(TableParser):
"""
Parse a simple table using `parse()`.
Here's an example of a simple table::
===== =====
col 1 col 2
===== =====
1 Second column of row 1.
2 Second column of row 2.
Second line of paragraph.
3 - Second column of row 3.
- Second item in bullet
list (row 3, column 2).
4 is a span
------------
5
===== =====
Top and bottom borders use '=', column span underlines use '-', column
separation is indicated with spaces.
Passing the above table to the `parse()` method will result in the
following data structure, whose interpretation is the same as for
`GridTableParser`::
([5, 25],
[[(0, 0, 1, ['col 1']),
(0, 0, 1, ['col 2'])]],
[[(0, 0, 3, ['1']),
(0, 0, 3, ['Second column of row 1.'])],
[(0, 0, 4, ['2']),
(0, 0, 4, ['Second column of row 2.',
'Second line of paragraph.'])],
[(0, 0, 6, ['3']),
(0, 0, 6, ['- Second column of row 3.',
'',
'- Second item in bullet',
' list (row 3, column 2).'])],
[(0, 1, 10, ['4 is a span'])],
[(0, 0, 12, ['5']),
(0, 0, 12, [''])]])
"""
head_body_separator_pat = re.compile('=[ =]*$')
span_pat = re.compile('-[ -]*$')
def setup(self, block):
self.block = block[:] # make a copy; it will be modified
self.block.disconnect() # don't propagate changes to parent
# Convert top & bottom borders to column span underlines:
self.block[0] = self.block[0].replace('=', '-')
self.block[-1] = self.block[-1].replace('=', '-')
self.head_body_sep = None
self.columns = []
self.border_end = None
self.table = []
self.done = [-1] * len(block[0])
self.rowseps = {0: [0]}
self.colseps = {0: [0]}
def parse_table(self):
"""
First determine the column boundaries from the top border, then
process rows. Each row may consist of multiple lines; accumulate
lines until a row is complete. Call `self.parse_row` to finish the
job.
"""
# Top border must fully describe all table columns.
self.columns = self.parse_columns(self.block[0], 0)
self.border_end = self.columns[-1][1]
firststart, firstend = self.columns[0]
offset = 1 # skip top border
start = 1
text_found = None
while offset < len(self.block):
line = self.block[offset]
if self.span_pat.match(line):
# Column span underline or border; row is complete.
self.parse_row(self.block[start:offset], start,
(line.rstrip(), offset))
start = offset + 1
text_found = None
elif line[firststart:firstend].strip():
# First column not blank, therefore it's a new row.
if text_found and offset != start:
self.parse_row(self.block[start:offset], start)
start = offset
text_found = 1
elif not text_found:
start = offset + 1
offset += 1
def parse_columns(self, line, offset):
"""
Given a column span underline, return a list of (begin, end) pairs.
"""
cols = []
end = 0
while 1:
begin = line.find('-', end)
end = line.find(' ', begin)
if begin < 0:
break
if end < 0:
end = len(line)
cols.append((begin, end))
if self.columns:
if cols[-1][1] != self.border_end:
raise TableMarkupError('Column span incomplete at line '
'offset %s.' % offset)
# Allow for an unbounded rightmost column:
cols[-1] = (cols[-1][0], self.columns[-1][1])
return cols
def init_row(self, colspec, offset):
i = 0
cells = []
for start, end in colspec:
morecols = 0
try:
assert start == self.columns[i][0]
while end != self.columns[i][1]:
i += 1
morecols += 1
except (AssertionError, IndexError):
raise TableMarkupError('Column span alignment problem at '
'line offset %s.' % (offset + 1))
cells.append([0, morecols, offset, []])
i += 1
return cells
def parse_row(self, lines, start, spanline=None):
"""
Given the text `lines` of a row, parse it and append to `self.table`.
The row is parsed according to the current column spec (either
`spanline` if provided or `self.columns`). For each column, extract
text from each line, and check for text in column margins. Finally,
adjust for insigificant whitespace.
"""
if not (lines or spanline):
# No new row, just blank lines.
return
if spanline:
columns = self.parse_columns(*spanline)
span_offset = spanline[1]
else:
columns = self.columns[:]
span_offset = start
self.check_columns(lines, start, columns)
row = self.init_row(columns, start)
for i in range(len(columns)):
start, end = columns[i]
cellblock = lines.get_2D_block(0, start, len(lines), end)
cellblock.disconnect() # lines in cell can't sync with parent
row[i][3] = cellblock
self.table.append(row)
def check_columns(self, lines, first_line, columns):
"""
Check for text in column margins and text overflow in the last column.
Raise TableMarkupError if anything but whitespace is in column margins.
Adjust the end value for the last column if there is text overflow.
"""
# "Infinite" value for a dummy last column's beginning, used to
# check for text overflow:
columns.append((sys.maxint, None))
lastcol = len(columns) - 2
for i in range(len(columns) - 1):
start, end = columns[i]
nextstart = columns[i+1][0]
offset = 0
for line in lines:
if i == lastcol and line[end:].strip():
text = line[start:].rstrip()
new_end = start + len(text)
columns[i] = (start, new_end)
main_start, main_end = self.columns[-1]
if new_end > main_end:
self.columns[-1] = (main_start, new_end)
elif line[end:nextstart].strip():
raise TableMarkupError('Text in column margin at line '
'offset %s.' % (first_line + offset))
offset += 1
columns.pop()
def structure_from_cells(self):
colspecs = [end - start for start, end in self.columns]
first_body_row = 0
if self.head_body_sep:
for i in range(len(self.table)):
if self.table[i][0][2] > self.head_body_sep:
first_body_row = i
break
return (colspecs, self.table[:first_body_row],
self.table[first_body_row:])
def update_dict_of_lists(master, newdata):
"""
Extend the list values of `master` with those from `newdata`.
Both parameters must be dictionaries containing list values.
"""
for key, values in newdata.items():
master.setdefault(key, []).extend(values)
# Authors: David Goodger; Ueli Schlaepfer
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:52 $
# Copyright: This module has been placed in the public domain.
"""
This package contains Docutils Reader modules.
"""
__docformat__ = 'reStructuredText'
import sys
from docutils import utils, parsers, Component
from docutils.transforms import universal
class Reader(Component):
"""
Abstract base class for docutils Readers.
Each reader module or package must export a subclass also called 'Reader'.
The three steps of a Reader's responsibility are defined: `scan()`,
`parse()`, and `transform()`. Call `read()` to process a document.
"""
component_type = 'reader'
config_section = 'readers'
def __init__(self, parser=None, parser_name='restructuredtext'):
"""
Initialize the Reader instance.
Several instance attributes are defined with dummy initial values.
Subclasses may use these attributes as they wish.
"""
self.parser = parser
"""A `parsers.Parser` instance shared by all doctrees. May be left
unspecified if the document source determines the parser."""
if parser is None and parser_name:
self.set_parser(parser_name)
self.source = None
"""`docutils.io` IO object, source of input data."""
self.input = None
"""Raw text input; either a single string or, for more complex cases,
a collection of strings."""
def set_parser(self, parser_name):
"""Set `self.parser` by name."""
parser_class = parsers.get_parser_class(parser_name)
self.parser = parser_class()
def read(self, source, parser, settings):
self.source = source
if not self.parser:
self.parser = parser
self.settings = settings
self.input = self.source.read()
self.parse()
return self.document
def parse(self):
"""Parse `self.input` into a document tree."""
self.document = document = self.new_document()
self.parser.parse(self.input, document)
document.current_source = document.current_line = None
def new_document(self):
"""Create and return a new empty document tree (root node)."""
document = utils.new_document(self.source.source_path, self.settings)
return document
_reader_aliases = {}
def get_reader_class(reader_name):
"""Return the Reader class from the `reader_name` module."""
reader_name = reader_name.lower()
if _reader_aliases.has_key(reader_name):
reader_name = _reader_aliases[reader_name]
module = __import__(reader_name, globals(), locals())
return module.Reader
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:52 $
# Copyright: This module has been placed in the public domain.
"""
Python Enhancement Proposal (PEP) Reader.
"""
__docformat__ = 'reStructuredText'
from docutils.readers import standalone
from docutils.transforms import peps, references
from docutils.parsers import rst
class Reader(standalone.Reader):
supported = ('pep',)
"""Contexts this reader supports."""
settings_spec = (
'PEP Reader Option Defaults',
'The --pep-references and --rfc-references options (for the '
'reStructuredText parser) are on by default.',
())
config_section = 'pep reader'
config_section_dependencies = ('readers', 'standalone reader')
default_transforms = (references.Substitutions,
peps.Headers,
peps.Contents,
references.ChainedTargets,
references.AnonymousHyperlinks,
references.IndirectHyperlinks,
peps.TargetNotes,
references.Footnotes,
references.ExternalTargets,
references.InternalTargets,)
settings_default_overrides = {'pep_references': 1, 'rfc_references': 1}
inliner_class = rst.states.Inliner
def __init__(self, parser=None, parser_name=None):
"""`parser` should be ``None``."""
if parser is None:
parser = rst.Parser(rfc2822=1, inliner=self.inliner_class())
standalone.Reader.__init__(self, parser, '')
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.7 $
# Date: $Date: 2004/07/25 01:45:26 $
# Copyright: This module has been placed in the public domain.
"""
This package contains the Python Source Reader modules.
It requires Python 2.2 or higher (`moduleparser` depends on `compiler` and
`tokenizer` modules).
"""
__docformat__ = 'reStructuredText'
import sys
import docutils.readers
from docutils.readers.python import moduleparser
from docutils import parsers
from docutils import nodes
from docutils.readers.python import pynodes
from docutils import readers
class Reader(docutils.readers.Reader):
config_section = 'python reader'
config_section_dependencies = ('readers',)
default_parser = 'restructuredtext'
def parse(self):
"""Parse `self.input` into a document tree."""
self.document = document = self.new_document()
module_section = moduleparser.parse_module(self.input,
self.source.source_path)
module_section.walk(DocformatVisitor(self.document))
visitor = DocstringFormattingVisitor(
document=document,
default_parser=self.default_parser)
module_section.walk(visitor)
self.document.append(module_section)
class DocformatVisitor(nodes.SparseNodeVisitor):
"""
This sets docformat attributes in a module. Wherever an assignment
to __docformat__ is found, we look for the enclosing scope -- a class,
a module, or a function -- and set the docformat attribute there.
We can't do this during the DocstringFormattingVisitor walking,
because __docformat__ may appear below a docstring in that format
(typically below the module docstring).
"""
def visit_attribute(self, node):
assert isinstance(node[0], pynodes.object_name)
name = node[0][0].data
if name != '__docformat__':
return
value = None
for child in children:
if isinstance(child, pynodes.expression_value):
value = child[0].data
break
assert value.startswith("'") or value.startswith('"'), "__docformat__ must be assigned a string literal (not %s); line: %s" % (value, node['lineno'])
name = name[1:-1]
looking_in = node.parent
while not isinstance(looking_in, (pynodes.module_section,
pynodes.function_section,
pynodes.class_section)):
looking_in = looking_in.parent
looking_in['docformat'] = name
class DocstringFormattingVisitor(nodes.SparseNodeVisitor):
def __init__(self, document, default_parser):
self.document = document
self.default_parser = default_parser
self.parsers = {}
def visit_docstring(self, node):
text = node[0].data
docformat = self.find_docformat(node)
del node[0]
node['docformat'] = docformat
parser = self.get_parser(docformat)
parser.parse(text, self.document)
for child in self.document.get_children():
node.append(child)
self.document.current_source = self.document.current_line = None
del self.document[:]
def get_parser(self, parser_name):
"""
Get a parser based on its name. We reuse parsers during this
visitation, so parser instances are cached.
"""
parser_name = parsers._parser_aliases.get(parser_name, parser_name)
if not self.parsers.has_key(parser_name):
cls = parsers.get_parser_class(parser_name)
self.parsers[parser_name] = cls()
return self.parsers[parser_name]
def find_docformat(self, node):
"""
Find the __docformat__ closest to this node (i.e., look in the
class or module)
"""
while node:
if node.get('docformat'):
return node['docformat']
node = node.parent
return self.default_parser
if __name__ == '__main__':
try:
import locale
locale.setlocale(locale.LC_ALL, '')
except:
pass
from docutils.core import publish_cmdline, default_description
description = ('Generates pseudo-XML from Python modules '
'(for testing purposes). ' + default_description)
publish_cmdline(description=description,
reader=Reader())
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.14 $
# Date: $Date: 2004/07/25 01:45:26 $
# Copyright: This module has been placed in the public domain.
"""
Parser for Python modules. Requires Python 2.2 or higher.
The `parse_module()` function takes a module's text and file name,
runs it through the module parser (using compiler.py and tokenize.py)
and produces a parse tree of the source code, using the nodes as found
in pynodes.py. For example, given this module (x.py)::
# comment
'''Docstring'''
'''Additional docstring'''
__docformat__ = 'reStructuredText'
a = 1
'''Attribute docstring'''
class C(Super):
'''C's docstring'''
class_attribute = 1
'''class_attribute's docstring'''
def __init__(self, text=None):
'''__init__'s docstring'''
self.instance_attribute = (text * 7
+ ' whaddyaknow')
'''instance_attribute's docstring'''
def f(x, # parameter x
y=a*5, # parameter y
*args): # parameter args
'''f's docstring'''
return [x + item for item in args]
f.function_attribute = 1
'''f.function_attribute's docstring'''
The module parser will produce this module documentation tree::
<module_section filename="test data">
<docstring>
Docstring
<docstring lineno="5">
Additional docstring
<attribute lineno="7">
<object_name>
__docformat__
<expression_value lineno="7">
'reStructuredText'
<attribute lineno="9">
<object_name>
a
<expression_value lineno="9">
1
<docstring lineno="10">
Attribute docstring
<class_section lineno="12">
<object_name>
C
<class_base>
Super
<docstring lineno="12">
C's docstring
<attribute lineno="16">
<object_name>
class_attribute
<expression_value lineno="16">
1
<docstring lineno="17">
class_attribute's docstring
<method_section lineno="19">
<object_name>
__init__
<docstring lineno="19">
__init__'s docstring
<parameter_list lineno="19">
<parameter lineno="19">
<object_name>
self
<parameter lineno="19">
<object_name>
text
<parameter_default lineno="19">
None
<attribute lineno="22">
<object_name>
self.instance_attribute
<expression_value lineno="22">
(text * 7 + ' whaddyaknow')
<docstring lineno="24">
instance_attribute's docstring
<function_section lineno="27">
<object_name>
f
<docstring lineno="27">
f's docstring
<parameter_list lineno="27">
<parameter lineno="27">
<object_name>
x
<comment>
# parameter x
<parameter lineno="27">
<object_name>
y
<parameter_default lineno="27">
a * 5
<comment>
# parameter y
<parameter excess_positional="1" lineno="27">
<object_name>
args
<comment>
# parameter args
<attribute lineno="33">
<object_name>
f.function_attribute
<expression_value lineno="33">
1
<docstring lineno="34">
f.function_attribute's docstring
(Comments are not implemented yet.)
compiler.parse() provides most of what's needed for this doctree, and
"tokenize" can be used to get the rest. We can determine the line
number from the compiler.parse() AST, and the TokenParser.rhs(lineno)
method provides the rest.
The Docutils Python reader component will transform this module doctree into a
Python-specific Docutils doctree, and then a `stylist transform`_ will
further transform it into a generic doctree. Namespaces will have to be
compiled for each of the scopes, but I'm not certain at what stage of
processing.
It's very important to keep all docstring processing out of this, so that it's
a completely generic and not tool-specific.
> Why perform all of those transformations? Why not go from the AST to a
> generic doctree? Or, even from the AST to the final output?
I want the docutils.readers.python.moduleparser.parse_module() function to
produce a standard documentation-oriented tree that can be used by any tool.
We can develop it together without having to compromise on the rest of our
design (i.e., HappyDoc doesn't have to be made to work like Docutils, and
vice-versa). It would be a higher-level version of what compiler.py provides.
The Python reader component transforms this generic AST into a Python-specific
doctree (it knows about modules, classes, functions, etc.), but this is
specific to Docutils and cannot be used by HappyDoc or others. The stylist
transform does the final layout, converting Python-specific structures
("class" sections, etc.) into a generic doctree using primitives (tables,
sections, lists, etc.). This generic doctree does *not* know about Python
structures any more. The advantage is that this doctree can be handed off to
any of the output writers to create any output format we like.
The latter two transforms are separate because I want to be able to have
multiple independent layout styles (multiple runtime-selectable "stylist
transforms"). Each of the existing tools (HappyDoc, pydoc, epydoc, Crystal,
etc.) has its own fixed format. I personally don't like the tables-based
format produced by these tools, and I'd like to be able to customize the
format easily. That's the goal of stylist transforms, which are independent
from the Reader component itself. One stylist transform could produce
HappyDoc-like output, another could produce output similar to module docs in
the Python library reference manual, and so on.
It's for exactly this reason:
>> It's very important to keep all docstring processing out of this, so that
>> it's a completely generic and not tool-specific.
... but it goes past docstring processing. It's also important to keep style
decisions and tool-specific data transforms out of this module parser.
Issues
======
* At what point should namespaces be computed? Should they be part of the
basic AST produced by the ASTVisitor walk, or generated by another tree
traversal?
* At what point should a distinction be made between local variables &
instance attributes in __init__ methods?
* Docstrings are getting their lineno from their parents. Should the
TokenParser find the real line no's?
* Comments: include them? How and when? Only full-line comments, or
parameter comments too? (See function "f" above for an example.)
* Module could use more docstrings & refactoring in places.
"""
__docformat__ = 'reStructuredText'
import sys
import compiler
import compiler.ast
import tokenize
import token
from compiler.consts import OP_ASSIGN
from compiler.visitor import ASTVisitor
from types import StringType, UnicodeType, TupleType
from docutils.readers.python import pynodes
from docutils.nodes import Text
def parse_module(module_text, filename):
"""Return a module documentation tree from `module_text`."""
ast = compiler.parse(module_text)
token_parser = TokenParser(module_text)
visitor = ModuleVisitor(filename, token_parser)
compiler.walk(ast, visitor, walker=visitor)
return visitor.module
class BaseVisitor(ASTVisitor):
def __init__(self, token_parser):
ASTVisitor.__init__(self)
self.token_parser = token_parser
self.context = []
self.documentable = None
def default(self, node, *args):
self.documentable = None
#print 'in default (%s)' % node.__class__.__name__
#ASTVisitor.default(self, node, *args)
def default_visit(self, node, *args):
#print 'in default_visit (%s)' % node.__class__.__name__
ASTVisitor.default(self, node, *args)
class DocstringVisitor(BaseVisitor):
def visitDiscard(self, node):
if self.documentable:
self.visit(node.expr)
def visitConst(self, node):
if self.documentable:
if type(node.value) in (StringType, UnicodeType):
self.documentable.append(make_docstring(node.value, node.lineno))
else:
self.documentable = None
def visitStmt(self, node):
self.default_visit(node)
class AssignmentVisitor(DocstringVisitor):
def visitAssign(self, node):
visitor = AttributeVisitor(self.token_parser)
compiler.walk(node, visitor, walker=visitor)
if visitor.attributes:
self.context[-1].extend(visitor.attributes)
if len(visitor.attributes) == 1:
self.documentable = visitor.attributes[0]
else:
self.documentable = None
class ModuleVisitor(AssignmentVisitor):
def __init__(self, filename, token_parser):
AssignmentVisitor.__init__(self, token_parser)
self.filename = filename
self.module = None
def visitModule(self, node):
self.module = module = pynodes.module_section()
module['filename'] = self.filename
append_docstring(module, node.doc, node.lineno)
self.context.append(module)
self.documentable = module
self.visit(node.node)
self.context.pop()
def visitImport(self, node):
self.context[-1] += make_import_group(names=node.names,
lineno=node.lineno)
self.documentable = None
def visitFrom(self, node):
self.context[-1].append(
make_import_group(names=node.names, from_name=node.modname,
lineno=node.lineno))
self.documentable = None
def visitFunction(self, node):
visitor = FunctionVisitor(self.token_parser,
function_class=pynodes.function_section)
compiler.walk(node, visitor, walker=visitor)
self.context[-1].append(visitor.function)
def visitClass(self, node):
visitor = ClassVisitor(self.token_parser)
compiler.walk(node, visitor, walker=visitor)
self.context[-1].append(visitor.klass)
class AttributeVisitor(BaseVisitor):
def __init__(self, token_parser):
BaseVisitor.__init__(self, token_parser)
self.attributes = pynodes.class_attribute_section()
def visitAssign(self, node):
# Don't visit the expression itself, just the attribute nodes:
for child in node.nodes:
self.dispatch(child)
expression_text = self.token_parser.rhs(node.lineno)
expression = pynodes.expression_value()
expression.append(Text(expression_text))
for attribute in self.attributes:
attribute.append(expression)
def visitAssName(self, node):
self.attributes.append(make_attribute(node.name,
lineno=node.lineno))
def visitAssTuple(self, node):
attributes = self.attributes
self.attributes = []
self.default_visit(node)
n = pynodes.attribute_tuple()
n.extend(self.attributes)
n['lineno'] = self.attributes[0]['lineno']
attributes.append(n)
self.attributes = attributes
#self.attributes.append(att_tuple)
def visitAssAttr(self, node):
self.default_visit(node, node.attrname)
def visitGetattr(self, node, suffix):
self.default_visit(node, node.attrname + '.' + suffix)
def visitName(self, node, suffix):
self.attributes.append(make_attribute(node.name + '.' + suffix,
lineno=node.lineno))
class FunctionVisitor(DocstringVisitor):
in_function = 0
def __init__(self, token_parser, function_class):
DocstringVisitor.__init__(self, token_parser)
self.function_class = function_class
def visitFunction(self, node):
if self.in_function:
self.documentable = None
# Don't bother with nested function definitions.
return
self.in_function = 1
self.function = function = make_function_like_section(
name=node.name,
lineno=node.lineno,
doc=node.doc,
function_class=self.function_class)
self.context.append(function)
self.documentable = function
self.parse_parameter_list(node)
self.visit(node.code)
self.context.pop()
def parse_parameter_list(self, node):
parameters = []
special = []
argnames = list(node.argnames)
if node.kwargs:
special.append(make_parameter(argnames[-1], excess_keyword=1))
argnames.pop()
if node.varargs:
special.append(make_parameter(argnames[-1],
excess_positional=1))
argnames.pop()
defaults = list(node.defaults)
defaults = [None] * (len(argnames) - len(defaults)) + defaults
function_parameters = self.token_parser.function_parameters(
node.lineno)
#print >>sys.stderr, function_parameters
for argname, default in zip(argnames, defaults):
if type(argname) is TupleType:
parameter = pynodes.parameter_tuple()
for tuplearg in argname:
parameter.append(make_parameter(tuplearg))
argname = normalize_parameter_name(argname)
else:
parameter = make_parameter(argname)
if default:
n_default = pynodes.parameter_default()
n_default.append(Text(function_parameters[argname]))
parameter.append(n_default)
parameters.append(parameter)
if parameters or special:
special.reverse()
parameters.extend(special)
parameter_list = pynodes.parameter_list()
parameter_list.extend(parameters)
self.function.append(parameter_list)
class ClassVisitor(AssignmentVisitor):
in_class = 0
def __init__(self, token_parser):
AssignmentVisitor.__init__(self, token_parser)
self.bases = []
def visitClass(self, node):
if self.in_class:
self.documentable = None
# Don't bother with nested class definitions.
return
self.in_class = 1
#import mypdb as pdb
#pdb.set_trace()
for base in node.bases:
self.visit(base)
self.klass = klass = make_class_section(node.name, self.bases,
doc=node.doc,
lineno=node.lineno)
self.context.append(klass)
self.documentable = klass
self.visit(node.code)
self.context.pop()
def visitGetattr(self, node, suffix=None):
if suffix:
name = node.attrname + '.' + suffix
else:
name = node.attrname
self.default_visit(node, name)
def visitName(self, node, suffix=None):
if suffix:
name = node.name + '.' + suffix
else:
name = node.name
self.bases.append(name)
def visitFunction(self, node):
if node.name == '__init__':
visitor = InitMethodVisitor(self.token_parser,
function_class=pynodes.method_section)
compiler.walk(node, visitor, walker=visitor)
else:
visitor = FunctionVisitor(self.token_parser,
function_class=pynodes.method_section)
compiler.walk(node, visitor, walker=visitor)
self.context[-1].append(visitor.function)
class InitMethodVisitor(FunctionVisitor, AssignmentVisitor): pass
class TokenParser:
def __init__(self, text):
self.text = text + '\n\n'
self.lines = self.text.splitlines(1)
self.generator = tokenize.generate_tokens(iter(self.lines).next)
self.next()
def __iter__(self):
return self
def next(self):
self.token = self.generator.next()
self.type, self.string, self.start, self.end, self.line = self.token
return self.token
def goto_line(self, lineno):
while self.start[0] < lineno:
self.next()
return token
def rhs(self, lineno):
"""
Return a whitespace-normalized expression string from the right-hand
side of an assignment at line `lineno`.
"""
self.goto_line(lineno)
while self.string != '=':
self.next()
self.stack = None
while self.type != token.NEWLINE and self.string != ';':
if self.string == '=' and not self.stack:
self.tokens = []
self.stack = []
self._type = None
self._string = None
self._backquote = 0
else:
self.note_token()
self.next()
self.next()
text = ''.join(self.tokens)
return text.strip()
closers = {')': '(', ']': '[', '}': '{'}
openers = {'(': 1, '[': 1, '{': 1}
del_ws_prefix = {'.': 1, '=': 1, ')': 1, ']': 1, '}': 1, ':': 1, ',': 1}
no_ws_suffix = {'.': 1, '=': 1, '(': 1, '[': 1, '{': 1}
def note_token(self):
if self.type == tokenize.NL:
return
del_ws = self.del_ws_prefix.has_key(self.string)
append_ws = not self.no_ws_suffix.has_key(self.string)
if self.openers.has_key(self.string):
self.stack.append(self.string)
if (self._type == token.NAME
or self.closers.has_key(self._string)):
del_ws = 1
elif self.closers.has_key(self.string):
assert self.stack[-1] == self.closers[self.string]
self.stack.pop()
elif self.string == '`':
if self._backquote:
del_ws = 1
assert self.stack[-1] == '`'
self.stack.pop()
else:
append_ws = 0
self.stack.append('`')
self._backquote = not self._backquote
if del_ws and self.tokens and self.tokens[-1] == ' ':
del self.tokens[-1]
self.tokens.append(self.string)
self._type = self.type
self._string = self.string
if append_ws:
self.tokens.append(' ')
def function_parameters(self, lineno):
"""
Return a dictionary mapping parameters to defaults
(whitespace-normalized strings).
"""
self.goto_line(lineno)
while self.string != 'def':
self.next()
while self.string != '(':
self.next()
name = None
default = None
parameter_tuple = None
self.tokens = []
parameters = {}
self.stack = [self.string]
self.next()
while 1:
if len(self.stack) == 1:
if parameter_tuple:
# Just encountered ")".
#print >>sys.stderr, 'parameter_tuple: %r' % self.tokens
name = ''.join(self.tokens).strip()
self.tokens = []
parameter_tuple = None
if self.string in (')', ','):
if name:
if self.tokens:
default_text = ''.join(self.tokens).strip()
else:
default_text = None
parameters[name] = default_text
self.tokens = []
name = None
default = None
if self.string == ')':
break
elif self.type == token.NAME:
if name and default:
self.note_token()
else:
assert name is None, (
'token=%r name=%r parameters=%r stack=%r'
% (self.token, name, parameters, self.stack))
name = self.string
#print >>sys.stderr, 'name=%r' % name
elif self.string == '=':
assert name is not None, 'token=%r' % (self.token,)
assert default is None, 'token=%r' % (self.token,)
assert self.tokens == [], 'token=%r' % (self.token,)
default = 1
self._type = None
self._string = None
self._backquote = 0
elif name:
self.note_token()
elif self.string == '(':
parameter_tuple = 1
self._type = None
self._string = None
self._backquote = 0
self.note_token()
else: # ignore these tokens:
assert (self.string in ('*', '**', '\n')
or self.type == tokenize.COMMENT), (
'token=%r' % (self.token,))
else:
self.note_token()
self.next()
return parameters
def make_docstring(doc, lineno):
n = pynodes.docstring()
if lineno:
# Really, only module docstrings don't have a line
# (@@: but maybe they should)
n['lineno'] = lineno
n.append(Text(doc))
return n
def append_docstring(node, doc, lineno):
if doc:
node.append(make_docstring(doc, lineno))
def make_class_section(name, bases, lineno, doc):
n = pynodes.class_section()
n['lineno'] = lineno
n.append(make_object_name(name))
for base in bases:
b = pynodes.class_base()
b.append(make_object_name(base))
n.append(b)
append_docstring(n, doc, lineno)
return n
def make_object_name(name):
n = pynodes.object_name()
n.append(Text(name))
return n
def make_function_like_section(name, lineno, doc, function_class):
n = function_class()
n['lineno'] = lineno
n.append(make_object_name(name))
append_docstring(n, doc, lineno)
return n
def make_import_group(names, lineno, from_name=None):
n = pynodes.import_group()
n['lineno'] = lineno
if from_name:
n_from = pynodes.import_from()
n_from.append(Text(from_name))
n.append(n_from)
for name, alias in names:
n_name = pynodes.import_name()
n_name.append(Text(name))
if alias:
n_alias = pynodes.import_alias()
n_alias.append(Text(alias))
n_name.append(n_alias)
n.append(n_name)
return n
def make_class_attribute(name, lineno):
n = pynodes.class_attribute()
n['lineno'] = lineno
n.append(Text(name))
return n
def make_attribute(name, lineno):
n = pynodes.attribute()
n['lineno'] = lineno
n.append(make_object_name(name))
return n
def make_parameter(name, excess_keyword=0, excess_positional=0):
"""
excess_keyword and excess_positional must be either 1 or 0, and
not both of them can be 1.
"""
n = pynodes.parameter()
n.append(make_object_name(name))
assert not excess_keyword or not excess_positional
if excess_keyword:
n['excess_keyword'] = 1
if excess_positional:
n['excess_positional'] = 1
return n
def trim_docstring(text):
"""
Trim indentation and blank lines from docstring text & return it.
See PEP 257.
"""
if not text:
return text
# Convert tabs to spaces (following the normal Python rules)
# and split into a list of lines:
lines = text.expandtabs().splitlines()
# Determine minimum indentation (first line doesn't count):
indent = sys.maxint
for line in lines[1:]:
stripped = line.lstrip()
if stripped:
indent = min(indent, len(line) - len(stripped))
# Remove indentation (first line is special):
trimmed = [lines[0].strip()]
if indent < sys.maxint:
for line in lines[1:]:
trimmed.append(line[indent:].rstrip())
# Strip off trailing and leading blank lines:
while trimmed and not trimmed[-1]:
trimmed.pop()
while trimmed and not trimmed[0]:
trimmed.pop(0)
# Return a single string:
return '\n'.join(trimmed)
def normalize_parameter_name(name):
"""
Converts a tuple like ``('a', ('b', 'c'), 'd')`` into ``'(a, (b, c), d)'``
"""
if type(name) is TupleType:
return '(%s)' % ', '.join([normalize_parameter_name(n) for n in name])
else:
return name
if __name__ == '__main__':
import sys
args = sys.argv[1:]
if args[0] == '-v':
filename = args[1]
module_text = open(filename).read()
ast = compiler.parse(module_text)
visitor = compiler.visitor.ExampleASTVisitor()
compiler.walk(ast, visitor, walker=visitor, verbose=1)
else:
filename = args[0]
content = open(filename).read()
print parse_module(content, filename).pformat()
#! /usr/bin/env python
"""
:Author: David Goodger
:Contact: goodger@users.sourceforge.net
:Revision: $Revision: 1.3 $
:Date: $Date: 2004/03/23 23:21:11 $
:Copyright: This module has been placed in the public domain.
"""
from docutils import nodes
from docutils.nodes import Element, TextElement, Structural, Inline, Part, \
Text
import types
# This is the parent class of all the other pynode classes:
class PythonStructural(Structural): pass
# =====================
# Structural Elements
# =====================
class module_section(PythonStructural, Element): pass
class class_section(PythonStructural, Element): pass
class class_base(PythonStructural, Element): pass
class method_section(PythonStructural, Element): pass
class attribute(PythonStructural, Element): pass
class function_section(PythonStructural, Element): pass
class class_attribute_section(PythonStructural, Element): pass
class class_attribute(PythonStructural, Element): pass
class expression_value(PythonStructural, Element): pass
class attribute(PythonStructural, Element): pass
# Structural Support Elements
# ---------------------------
class parameter_list(PythonStructural, Element): pass
class parameter_tuple(PythonStructural, Element): pass
class parameter_default(PythonStructural, TextElement): pass
class import_group(PythonStructural, TextElement): pass
class import_from(PythonStructural, TextElement): pass
class import_name(PythonStructural, TextElement): pass
class import_alias(PythonStructural, TextElement): pass
class docstring(PythonStructural, Element): pass
# =================
# Inline Elements
# =================
# These elements cannot become references until the second
# pass. Initially, we'll use "reference" or "name".
class object_name(PythonStructural, TextElement): pass
class parameter_list(PythonStructural, TextElement): pass
class parameter(PythonStructural, TextElement): pass
class parameter_default(PythonStructural, TextElement): pass
class class_attribute(PythonStructural, TextElement): pass
class attribute_tuple(PythonStructural, TextElement): pass
# =================
# Unused Elements
# =================
# These were part of the model, and maybe should be in the future, but
# aren't now.
#class package_section(PythonStructural, Element): pass
#class module_attribute_section(PythonStructural, Element): pass
#class instance_attribute_section(PythonStructural, Element): pass
#class module_attribute(PythonStructural, TextElement): pass
#class instance_attribute(PythonStructural, TextElement): pass
#class exception_class(PythonStructural, TextElement): pass
#class warning_class(PythonStructural, TextElement): pass
# Collect all the classes we've written above
def install_node_class_names():
node_class_names = []
for name, var in globals().items():
if (type(var) is types.ClassType
and issubclass(var, PythonStructural) \
and name.lower() == name):
node_class_names.append(var.tagname or name)
# Register the new node names with GenericNodeVisitor and
# SpecificNodeVisitor:
nodes._add_node_class_names(node_class_names)
install_node_class_names()
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:52 $
# Copyright: This module has been placed in the public domain.
"""
Standalone file Reader for the reStructuredText markup syntax.
"""
__docformat__ = 'reStructuredText'
import sys
from docutils import frontend, readers
from docutils.transforms import frontmatter, references
class Reader(readers.Reader):
supported = ('standalone',)
"""Contexts this reader supports."""
document = None
"""A single document tree."""
settings_spec = (
'Standalone Reader',
None,
(('Disable the promotion of a lone top-level section title to '
'document title (and subsequent section title to document '
'subtitle promotion; enabled by default).',
['--no-doc-title'],
{'dest': 'doctitle_xform', 'action': 'store_false', 'default': 1,
'validator': frontend.validate_boolean}),
('Disable the bibliographic field list transform (enabled by '
'default).',
['--no-doc-info'],
{'dest': 'docinfo_xform', 'action': 'store_false', 'default': 1,
'validator': frontend.validate_boolean}),))
config_section = 'standalone reader'
config_section_dependencies = ('readers',)
default_transforms = (references.Substitutions,
frontmatter.DocTitle,
frontmatter.DocInfo,
references.ChainedTargets,
references.AnonymousHyperlinks,
references.IndirectHyperlinks,
references.Footnotes,
references.ExternalTargets,
references.InternalTargets,)
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:39 $
# 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 get_source(self, line_offset):
"""Return source of line at absolute line offset `line_offset`."""
return self.input_lines.source(line_offset - self.input_offset)
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)
# Authors: David Goodger, Ueli Schlaepfer
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:55 $
# Copyright: This module has been placed in the public domain.
"""
This package contains modules for standard tree transforms available
to Docutils components. Tree transforms serve a variety of purposes:
- To tie up certain syntax-specific "loose ends" that remain after the
initial parsing of the input plaintext. These transforms are used to
supplement a limited syntax.
- To automate the internal linking of the document tree (hyperlink
references, footnote references, etc.).
- To extract useful information from the document tree. These
transforms may be used to construct (for example) indexes and tables
of contents.
Each transform is an optional step that a Docutils Reader may choose to
perform on the parsed document, depending on the input context. A Docutils
Reader may also perform Reader-specific transforms before or after performing
these standard transforms.
"""
__docformat__ = 'reStructuredText'
from docutils import languages, ApplicationError, TransformSpec
class TransformError(ApplicationError): pass
class Transform:
"""
Docutils transform component abstract base class.
"""
default_priority = None
"""Numerical priority of this transform, 0 through 999 (override)."""
def __init__(self, document, startnode=None):
"""
Initial setup for in-place document transforms.
"""
self.document = document
"""The document tree to transform."""
self.startnode = startnode
"""Node from which to begin the transform. For many transforms which
apply to the document as a whole, `startnode` is not set (i.e. its
value is `None`)."""
self.language = languages.get_language(
document.settings.language_code)
"""Language module local to this document."""
def apply(self):
"""Override to apply the transform to the document tree."""
raise NotImplementedError('subclass must override this method')
class Transformer(TransformSpec):
"""
Stores transforms (`Transform` classes) and applies them to document
trees. Also keeps track of components by component type name.
"""
from docutils.transforms import universal
default_transforms = (universal.Decorations,
universal.FinalChecks,
universal.Messages,
universal.FilterMessages)
"""These transforms are applied to all document trees."""
def __init__(self, document):
self.transforms = []
"""List of transforms to apply. Each item is a 3-tuple:
``(priority string, transform class, pending node or None)``."""
self.unknown_reference_resolvers = []
"""List of hook functions which assist in resolving references"""
self.document = document
"""The `nodes.document` object this Transformer is attached to."""
self.applied = []
"""Transforms already applied, in order."""
self.sorted = 0
"""Boolean: is `self.tranforms` sorted?"""
self.components = {}
"""Mapping of component type name to component object. Set by
`self.populate_from_components()`."""
self.serialno = 0
"""Internal serial number to keep track of the add order of
transforms."""
def add_transform(self, transform_class, priority=None):
"""
Store a single transform. Use `priority` to override the default.
"""
if priority is None:
priority = transform_class.default_priority
priority_string = self.get_priority_string(priority)
self.transforms.append((priority_string, transform_class, None))
self.sorted = 0
def add_transforms(self, transform_list):
"""Store multiple transforms, with default priorities."""
for transform_class in transform_list:
priority_string = self.get_priority_string(
transform_class.default_priority)
self.transforms.append((priority_string, transform_class, None))
self.sorted = 0
def add_pending(self, pending, priority=None):
"""Store a transform with an associated `pending` node."""
transform_class = pending.transform
if priority is None:
priority = transform_class.default_priority
priority_string = self.get_priority_string(priority)
self.transforms.append((priority_string, transform_class, pending))
self.sorted = 0
def get_priority_string(self, priority):
"""
Return a string, `priority` combined with `self.serialno`.
This ensures FIFO order on transforms with identical priority.
"""
self.serialno += 1
return '%03d-%03d' % (priority, self.serialno)
def populate_from_components(self, components):
"""
Store each component's default transforms, with default priorities.
Also, store components by type name in a mapping for later lookup.
"""
self.add_transforms(self.default_transforms)
for component in components:
if component is None:
continue
self.add_transforms(component.default_transforms)
self.components[component.component_type] = component
self.sorted = 0
# Setup all of the reference resolvers for this transformer. Each
# component of this transformer is able to register its own helper
# functions to help resolve references.
unknown_reference_resolvers = []
for i in components:
unknown_reference_resolvers.extend(i.unknown_reference_resolvers)
decorated_list = [(f.priority, f) for f in unknown_reference_resolvers]
decorated_list.sort()
self.unknown_reference_resolvers.extend([f[1] for f in decorated_list])
def apply_transforms(self):
"""Apply all of the stored transforms, in priority order."""
self.document.reporter.attach_observer(
self.document.note_transform_message)
while self.transforms:
if not self.sorted:
# Unsorted initially, and whenever a transform is added.
self.transforms.sort()
self.transforms.reverse()
self.sorted = 1
priority, transform_class, pending = self.transforms.pop()
transform = transform_class(self.document, startnode=pending)
transform.apply()
self.applied.append((priority, transform_class, pending))
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:55 $
# Copyright: This module has been placed in the public domain.
"""
Docutils component-related transforms.
"""
__docformat__ = 'reStructuredText'
import sys
import os
import re
import time
from docutils import nodes, utils
from docutils import ApplicationError, DataError
from docutils.transforms import Transform, TransformError
class Filter(Transform):
"""
Include or exclude elements which depend on a specific Docutils component.
For use with `nodes.pending` elements. A "pending" element's dictionary
attribute ``details`` must contain the keys "component" and "format". The
value of ``details['component']`` must match the type name of the
component the elements depend on (e.g. "writer"). The value of
``details['format']`` is the name of a specific format or context of that
component (e.g. "html"). If the matching Docutils component supports that
format or context, the "pending" element is replaced by the contents of
``details['nodes']`` (a list of nodes); otherwise, the "pending" element
is removed.
For example, the reStructuredText "meta" directive creates a "pending"
element containing a "meta" element (in ``pending.details['nodes']``).
Only writers (``pending.details['component'] == 'writer'``) supporting the
"html" format (``pending.details['format'] == 'html'``) will include the
"meta" element; it will be deleted from the output of all other writers.
"""
default_priority = 780
def apply(self):
pending = self.startnode
component_type = pending.details['component'] # 'reader' or 'writer'
format = pending.details['format']
component = self.document.transformer.components[component_type]
if component.supports(format):
pending.parent.replace(pending, pending.details['nodes'])
else:
pending.parent.remove(pending)
# Authors: David Goodger, Ueli Schlaepfer
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:55 $
# Copyright: This module has been placed in the public domain.
"""
Transforms related to the front matter of a document (information
found before the main text):
- `DocTitle`: Used to transform a lone top level section's title to
the document title, and promote a remaining lone top-level section's
title to the document subtitle.
- `DocInfo`: Used to transform a bibliographic field list into docinfo
elements.
"""
__docformat__ = 'reStructuredText'
import re
from docutils import nodes, utils
from docutils.transforms import TransformError, Transform
class DocTitle(Transform):
"""
In reStructuredText_, there is no way to specify a document title
and subtitle explicitly. Instead, we can supply the document title
(and possibly the subtitle as well) implicitly, and use this
two-step transform to "raise" or "promote" the title(s) (and their
corresponding section contents) to the document level.
1. If the document contains a single top-level section as its
first non-comment element, the top-level section's title
becomes the document's title, and the top-level section's
contents become the document's immediate contents. The lone
top-level section header must be the first non-comment element
in the document.
For example, take this input text::
=================
Top-Level Title
=================
A paragraph.
Once parsed, it looks like this::
<document>
<section name="top-level title">
<title>
Top-Level Title
<paragraph>
A paragraph.
After running the DocTitle transform, we have::
<document name="top-level title">
<title>
Top-Level Title
<paragraph>
A paragraph.
2. If step 1 successfully determines the document title, we
continue by checking for a subtitle.
If the lone top-level section itself contains a single
second-level section as its first non-comment element, that
section's title is promoted to the document's subtitle, and
that section's contents become the document's immediate
contents. Given this input text::
=================
Top-Level Title
=================
Second-Level Title
~~~~~~~~~~~~~~~~~~
A paragraph.
After parsing and running the Section Promotion transform, the
result is::
<document name="top-level title">
<title>
Top-Level Title
<subtitle name="second-level title">
Second-Level Title
<paragraph>
A paragraph.
(Note that the implicit hyperlink target generated by the
"Second-Level Title" is preserved on the "subtitle" element
itself.)
Any comment elements occurring before the document title or
subtitle are accumulated and inserted as the first body elements
after the title(s).
"""
default_priority = 320
def apply(self):
if not getattr(self.document.settings, 'doctitle_xform', 1):
return
if self.promote_document_title():
self.promote_document_subtitle()
def promote_document_title(self):
section, index = self.candidate_index()
if index is None:
return None
document = self.document
# Transfer the section's attributes to the document element (at root):
document.attributes.update(section.attributes)
document[:] = (section[:1] # section title
+ document[:index] # everything that was in the
# document before the section
+ section[1:]) # everything that was in the section
return 1
def promote_document_subtitle(self):
subsection, index = self.candidate_index()
if index is None:
return None
subtitle = nodes.subtitle()
# Transfer the subsection's attributes to the new subtitle:
subtitle.attributes.update(subsection.attributes)
# Transfer the contents of the subsection's title to the subtitle:
subtitle[:] = subsection[0][:]
document = self.document
document[:] = (document[:1] # document title
+ [subtitle]
# everything that was before the section:
+ document[1:index]
# everything that was in the subsection:
+ subsection[1:])
return 1
def candidate_index(self):
"""
Find and return the promotion candidate and its index.
Return (None, None) if no valid candidate was found.
"""
document = self.document
index = document.first_child_not_matching_class(
nodes.PreBibliographic)
if index is None or len(document) > (index + 1) or \
not isinstance(document[index], nodes.section):
return None, None
else:
return document[index], index
class DocInfo(Transform):
"""
This transform is specific to the reStructuredText_ markup syntax;
see "Bibliographic Fields" in the `reStructuredText Markup
Specification`_ for a high-level description. This transform
should be run *after* the `DocTitle` transform.
Given a field list as the first non-comment element after the
document title and subtitle (if present), registered bibliographic
field names are transformed to the corresponding DTD elements,
becoming child elements of the "docinfo" element (except for a
dedication and/or an abstract, which become "topic" elements after
"docinfo").
For example, given this document fragment after parsing::
<document>
<title>
Document Title
<field_list>
<field>
<field_name>
Author
<field_body>
<paragraph>
A. Name
<field>
<field_name>
Status
<field_body>
<paragraph>
$RCSfile: frontmatter.py,v $
...
After running the bibliographic field list transform, the
resulting document tree would look like this::
<document>
<title>
Document Title
<docinfo>
<author>
A. Name
<status>
frontmatter.py
...
The "Status" field contained an expanded RCS keyword, which is
normally (but optionally) cleaned up by the transform. The sole
contents of the field body must be a paragraph containing an
expanded RCS keyword of the form "$keyword: expansion text $". Any
RCS keyword can be processed in any bibliographic field. The
dollar signs and leading RCS keyword name are removed. Extra
processing is done for the following RCS keywords:
- "RCSfile" expands to the name of the file in the RCS or CVS
repository, which is the name of the source file with a ",v"
suffix appended. The transform will remove the ",v" suffix.
- "Date" expands to the format "YYYY/MM/DD hh:mm:ss" (in the UTC
time zone). The RCS Keywords transform will extract just the
date itself and transform it to an ISO 8601 format date, as in
"2000-12-31".
(Since the source file for this text is itself stored under CVS,
we can't show an example of the "Date" RCS keyword because we
can't prevent any RCS keywords used in this explanation from
being expanded. Only the "RCSfile" keyword is stable; its
expansion text changes only if the file name changes.)
"""
default_priority = 340
biblio_nodes = {
'author': nodes.author,
'authors': nodes.authors,
'organization': nodes.organization,
'address': nodes.address,
'contact': nodes.contact,
'version': nodes.version,
'revision': nodes.revision,
'status': nodes.status,
'date': nodes.date,
'copyright': nodes.copyright,
'dedication': nodes.topic,
'abstract': nodes.topic}
"""Canonical field name (lowcased) to node class name mapping for
bibliographic fields (field_list)."""
def apply(self):
if not getattr(self.document.settings, 'docinfo_xform', 1):
return
document = self.document
index = document.first_child_not_matching_class(
nodes.PreBibliographic)
if index is None:
return
candidate = document[index]
if isinstance(candidate, nodes.field_list):
biblioindex = document.first_child_not_matching_class(
nodes.Titular)
nodelist = self.extract_bibliographic(candidate)
del document[index] # untransformed field list (candidate)
document[biblioindex:biblioindex] = nodelist
return
def extract_bibliographic(self, field_list):
docinfo = nodes.docinfo()
bibliofields = self.language.bibliographic_fields
labels = self.language.labels
topics = {'dedication': None, 'abstract': None}
for field in field_list:
try:
name = field[0][0].astext()
normedname = nodes.fully_normalize_name(name)
if not (len(field) == 2 and bibliofields.has_key(normedname)
and self.check_empty_biblio_field(field, name)):
raise TransformError
canonical = bibliofields[normedname]
biblioclass = self.biblio_nodes[canonical]
if issubclass(biblioclass, nodes.TextElement):
if not self.check_compound_biblio_field(field, name):
raise TransformError
utils.clean_rcs_keywords(
field[1][0], self.rcs_keyword_substitutions)
docinfo.append(biblioclass('', '', *field[1][0]))
elif issubclass(biblioclass, nodes.authors):
self.extract_authors(field, name, docinfo)
elif issubclass(biblioclass, nodes.topic):
if topics[canonical]:
field[-1] += self.document.reporter.warning(
'There can only be one "%s" field.' % name,
base_node=field)
raise TransformError
title = nodes.title(name, labels[canonical])
topics[canonical] = biblioclass(
'', title, CLASS=canonical, *field[1].children)
else:
docinfo.append(biblioclass('', *field[1].children))
except TransformError:
if len(field[-1]) == 1 \
and isinstance(field[-1][0], nodes.paragraph):
utils.clean_rcs_keywords(
field[-1][0], self.rcs_keyword_substitutions)
docinfo.append(field)
nodelist = []
if len(docinfo) != 0:
nodelist.append(docinfo)
for name in ('dedication', 'abstract'):
if topics[name]:
nodelist.append(topics[name])
return nodelist
def check_empty_biblio_field(self, field, name):
if len(field[-1]) < 1:
field[-1] += self.document.reporter.warning(
'Cannot extract empty bibliographic field "%s".' % name,
base_node=field)
return None
return 1
def check_compound_biblio_field(self, field, name):
if len(field[-1]) > 1:
field[-1] += self.document.reporter.warning(
'Cannot extract compound bibliographic field "%s".' % name,
base_node=field)
return None
if not isinstance(field[-1][0], nodes.paragraph):
field[-1] += self.document.reporter.warning(
'Cannot extract bibliographic field "%s" containing '
'anything other than a single paragraph.' % name,
base_node=field)
return None
return 1
rcs_keyword_substitutions = [
(re.compile(r'\$' r'Date: (\d\d\d\d)/(\d\d)/(\d\d) [\d:]+ \$',
re.IGNORECASE), r'\1-\2-\3'),
(re.compile(r'\$' r'RCSfile: (.+),v \$', re.IGNORECASE), r'\1'),
(re.compile(r'\$[a-zA-Z]+: (.+) \$'), r'\1'),]
def extract_authors(self, field, name, docinfo):
try:
if len(field[1]) == 1:
if isinstance(field[1][0], nodes.paragraph):
authors = self.authors_from_one_paragraph(field)
elif isinstance(field[1][0], nodes.bullet_list):
authors = self.authors_from_bullet_list(field)
else:
raise TransformError
else:
authors = self.authors_from_paragraphs(field)
authornodes = [nodes.author('', '', *author)
for author in authors if author]
if len(authornodes) > 1:
docinfo.append(nodes.authors('', *authornodes))
elif len(authornodes) == 1:
docinfo.append(authornodes[0])
else:
raise TransformError
except TransformError:
field[-1] += self.document.reporter.warning(
'Bibliographic field "%s" incompatible with extraction: '
'it must contain either a single paragraph (with authors '
'separated by one of "%s"), multiple paragraphs (one per '
'author), or a bullet list with one paragraph (one author) '
'per item.'
% (name, ''.join(self.language.author_separators)),
base_node=field)
raise
def authors_from_one_paragraph(self, field):
text = field[1][0].astext().strip()
if not text:
raise TransformError
for authorsep in self.language.author_separators:
authornames = text.split(authorsep)
if len(authornames) > 1:
break
authornames = [author.strip() for author in authornames]
authors = [[nodes.Text(author)] for author in authornames if author]
return authors
def authors_from_bullet_list(self, field):
authors = []
for item in field[1][0]:
if len(item) != 1 or not isinstance(item[0], nodes.paragraph):
raise TransformError
authors.append(item[0].children)
if not authors:
raise TransformError
return authors
def authors_from_paragraphs(self, field):
for item in field[1]:
if not isinstance(item, nodes.paragraph):
raise TransformError
authors = [item.children for item in field[1]]
return authors
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:55 $
# Copyright: This module has been placed in the public domain.
"""
Miscellaneous transforms.
"""
__docformat__ = 'reStructuredText'
from docutils import nodes
from docutils.transforms import Transform, TransformError
class CallBack(Transform):
"""
Inserts a callback into a document. The callback is called when the
transform is applied, which is determined by its priority.
For use with `nodes.pending` elements. Requires a ``details['callback']``
entry, a bound method or function which takes one parameter: the pending
node. Other data can be stored in the ``details`` attribute or in the
object hosting the callback method.
"""
default_priority = 990
def apply(self):
pending = self.startnode
pending.details['callback'](pending)
pending.parent.remove(pending)
class ClassAttribute(Transform):
default_priority = 210
def apply(self):
pending = self.startnode
class_value = pending.details['class']
parent = pending.parent
child = pending
while parent:
for index in range(parent.index(child) + 1, len(parent)):
element = parent[index]
if isinstance(element, nodes.comment):
continue
element.set_class(class_value)
pending.parent.remove(pending)
return
else:
child = parent
parent = parent.parent
error = self.document.reporter.error(
'No suitable element following "%s" directive'
% pending.details['directive'],
nodes.literal_block(pending.rawsource, pending.rawsource),
line=pending.line)
pending.parent.replace(pending, error)
# Authors: David Goodger, Ueli Schlaepfer, Dmitry Jemerov
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:55 $
# Copyright: This module has been placed in the public domain.
"""
Transforms related to document parts.
"""
__docformat__ = 'reStructuredText'
import re
import sys
from docutils import nodes, utils
from docutils.transforms import TransformError, Transform
class SectNum(Transform):
"""
Automatically assigns numbers to the titles of document sections.
It is possible to limit the maximum section level for which the numbers
are added. For those sections that are auto-numbered, the "autonum"
attribute is set, informing the contents table generator that a different
form of the TOC should be used.
"""
default_priority = 710
"""Should be applied before `Contents`."""
def apply(self):
self.maxdepth = self.startnode.details.get('depth', sys.maxint)
self.startnode.parent.remove(self.startnode)
if self.document.settings.sectnum_xform:
self.update_section_numbers(self.document)
def update_section_numbers(self, node, prefix=(), depth=0):
depth += 1
sectnum = 1
for child in node:
if isinstance(child, nodes.section):
numbers = prefix + (str(sectnum),)
title = child[0]
# Use &nbsp; for spacing:
generated = nodes.generated(
'', '.'.join(numbers) + u'\u00a0' * 3, CLASS='sectnum')
title.insert(0, generated)
title['auto'] = 1
if depth < self.maxdepth:
self.update_section_numbers(child, numbers, depth)
sectnum += 1
class Contents(Transform):
"""
This transform generates a table of contents from the entire document tree
or from a single branch. It locates "section" elements and builds them
into a nested bullet list, which is placed within a "topic" created by the
contents directive. A title is either explicitly specified, taken from
the appropriate language module, or omitted (local table of contents).
The depth may be specified. Two-way references between the table of
contents and section titles are generated (requires Writer support).
This transform requires a startnode, which which contains generation
options and provides the location for the generated table of contents (the
startnode is replaced by the table of contents "topic").
"""
default_priority = 720
def apply(self):
details = self.startnode.details
if details.has_key('local'):
startnode = self.startnode.parent.parent
# @@@ generate an error if the startnode (directive) not at
# section/document top-level? Drag it up until it is?
while not isinstance(startnode, nodes.Structural):
startnode = startnode.parent
else:
startnode = self.document
self.toc_id = self.startnode.parent['id']
if details.has_key('backlinks'):
self.backlinks = details['backlinks']
else:
self.backlinks = self.document.settings.toc_backlinks
contents = self.build_contents(startnode)
if len(contents):
self.startnode.parent.replace(self.startnode, contents)
else:
self.startnode.parent.parent.remove(self.startnode.parent)
def build_contents(self, node, level=0):
level += 1
sections = []
i = len(node) - 1
while i >= 0 and isinstance(node[i], nodes.section):
sections.append(node[i])
i -= 1
sections.reverse()
entries = []
autonum = 0
depth = self.startnode.details.get('depth', sys.maxint)
for section in sections:
title = section[0]
auto = title.get('auto') # May be set by SectNum.
entrytext = self.copy_and_filter(title)
reference = nodes.reference('', '', refid=section['id'],
*entrytext)
ref_id = self.document.set_id(reference)
entry = nodes.paragraph('', '', reference)
item = nodes.list_item('', entry)
if self.backlinks == 'entry':
title['refid'] = ref_id
elif self.backlinks == 'top':
title['refid'] = self.toc_id
if level < depth:
subsects = self.build_contents(section, level)
item += subsects
entries.append(item)
if entries:
contents = nodes.bullet_list('', *entries)
if auto:
contents.set_class('auto-toc')
return contents
else:
return []
def copy_and_filter(self, node):
"""Return a copy of a title, with references, images, etc. removed."""
visitor = ContentsFilter(self.document)
node.walkabout(visitor)
return visitor.get_entry_text()
class ContentsFilter(nodes.TreeCopyVisitor):
def get_entry_text(self):
return self.get_tree_copy().get_children()
def visit_citation_reference(self, node):
raise nodes.SkipNode
def visit_footnote_reference(self, node):
raise nodes.SkipNode
def visit_image(self, node):
if node.hasattr('alt'):
self.parent.append(nodes.Text(node['alt']))
raise nodes.SkipNode
def ignore_node_but_process_children(self, node):
raise nodes.SkipDeparture
visit_interpreted = ignore_node_but_process_children
visit_problematic = ignore_node_but_process_children
visit_reference = ignore_node_but_process_children
visit_target = ignore_node_but_process_children
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:55 $
# Copyright: This module has been placed in the public domain.
"""
Transforms for PEP processing.
- `Headers`: Used to transform a PEP's initial RFC-2822 header. It remains a
field list, but some entries get processed.
- `Contents`: Auto-inserts a table of contents.
- `PEPZero`: Special processing for PEP 0.
"""
__docformat__ = 'reStructuredText'
import sys
import os
import re
import time
from docutils import nodes, utils, languages
from docutils import ApplicationError, DataError
from docutils.transforms import Transform, TransformError
from docutils.transforms import parts, references, misc
class Headers(Transform):
"""
Process fields in a PEP's initial RFC-2822 header.
"""
default_priority = 360
pep_url = 'pep-%04d.html'
pep_cvs_url = ('http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/python/'
'python/nondist/peps/pep-%04d.txt')
rcs_keyword_substitutions = (
(re.compile(r'\$' r'RCSfile: (.+),v \$$', re.IGNORECASE), r'\1'),
(re.compile(r'\$[a-zA-Z]+: (.+) \$$'), r'\1'),)
def apply(self):
if not len(self.document):
# @@@ replace these DataErrors with proper system messages
raise DataError('Document tree is empty.')
header = self.document[0]
if not isinstance(header, nodes.field_list) or \
header.get('class') != 'rfc2822':
raise DataError('Document does not begin with an RFC-2822 '
'header; it is not a PEP.')
pep = None
for field in header:
if field[0].astext().lower() == 'pep': # should be the first field
value = field[1].astext()
try:
pep = int(value)
cvs_url = self.pep_cvs_url % pep
except ValueError:
pep = value
cvs_url = None
msg = self.document.reporter.warning(
'"PEP" header must contain an integer; "%s" is an '
'invalid value.' % pep, base_node=field)
msgid = self.document.set_id(msg)
prb = nodes.problematic(value, value or '(none)',
refid=msgid)
prbid = self.document.set_id(prb)
msg.add_backref(prbid)
if len(field[1]):
field[1][0][:] = [prb]
else:
field[1] += nodes.paragraph('', '', prb)
break
if pep is None:
raise DataError('Document does not contain an RFC-2822 "PEP" '
'header.')
if pep == 0:
# Special processing for PEP 0.
pending = nodes.pending(PEPZero)
self.document.insert(1, pending)
self.document.note_pending(pending)
if len(header) < 2 or header[1][0].astext().lower() != 'title':
raise DataError('No title!')
for field in header:
name = field[0].astext().lower()
body = field[1]
if len(body) > 1:
raise DataError('PEP header field body contains multiple '
'elements:\n%s' % field.pformat(level=1))
elif len(body) == 1:
if not isinstance(body[0], nodes.paragraph):
raise DataError('PEP header field body may only contain '
'a single paragraph:\n%s'
% field.pformat(level=1))
elif name == 'last-modified':
date = time.strftime(
'%d-%b-%Y',
time.localtime(os.stat(self.document['source'])[8]))
if cvs_url:
body += nodes.paragraph(
'', '', nodes.reference('', date, refuri=cvs_url))
else:
# empty
continue
para = body[0]
if name == 'author':
for node in para:
if isinstance(node, nodes.reference):
node.parent.replace(node, mask_email(node))
elif name == 'discussions-to':
for node in para:
if isinstance(node, nodes.reference):
node.parent.replace(node, mask_email(node, pep))
elif name in ('replaces', 'replaced-by', 'requires'):
newbody = []
space = nodes.Text(' ')
for refpep in re.split(',?\s+', body.astext()):
pepno = int(refpep)
newbody.append(nodes.reference(
refpep, refpep,
refuri=(self.document.settings.pep_base_url
+ self.pep_url % pepno)))
newbody.append(space)
para[:] = newbody[:-1] # drop trailing space
elif name == 'last-modified':
utils.clean_rcs_keywords(para, self.rcs_keyword_substitutions)
if cvs_url:
date = para.astext()
para[:] = [nodes.reference('', date, refuri=cvs_url)]
elif name == 'content-type':
pep_type = para.astext()
uri = self.document.settings.pep_base_url + self.pep_url % 12
para[:] = [nodes.reference('', pep_type, refuri=uri)]
elif name == 'version' and len(body):
utils.clean_rcs_keywords(para, self.rcs_keyword_substitutions)
class Contents(Transform):
"""
Insert an empty table of contents topic and a transform placeholder into
the document after the RFC 2822 header.
"""
default_priority = 380
def apply(self):
language = languages.get_language(self.document.settings.language_code)
name = language.labels['contents']
title = nodes.title('', name)
topic = nodes.topic('', title, CLASS='contents')
name = nodes.fully_normalize_name(name)
if not self.document.has_name(name):
topic['name'] = name
self.document.note_implicit_target(topic)
pending = nodes.pending(parts.Contents)
topic += pending
self.document.insert(1, topic)
self.document.note_pending(pending)
class TargetNotes(Transform):
"""
Locate the "References" section, insert a placeholder for an external
target footnote insertion transform at the end, and schedule the
transform to run immediately.
"""
default_priority = 520
def apply(self):
doc = self.document
i = len(doc) - 1
refsect = copyright = None
while i >= 0 and isinstance(doc[i], nodes.section):
title_words = doc[i][0].astext().lower().split()
if 'references' in title_words:
refsect = doc[i]
break
elif 'copyright' in title_words:
copyright = i
i -= 1
if not refsect:
refsect = nodes.section()
refsect += nodes.title('', 'References')
doc.set_id(refsect)
if copyright:
# Put the new "References" section before "Copyright":
doc.insert(copyright, refsect)
else:
# Put the new "References" section at end of doc:
doc.append(refsect)
pending = nodes.pending(references.TargetNotes)
refsect.append(pending)
self.document.note_pending(pending, 0)
pending = nodes.pending(misc.CallBack,
details={'callback': self.cleanup_callback})
refsect.append(pending)
self.document.note_pending(pending, 1)
def cleanup_callback(self, pending):
"""
Remove an empty "References" section.
Called after the `references.TargetNotes` transform is complete.
"""
if len(pending.parent) == 2: # <title> and <pending>
pending.parent.parent.remove(pending.parent)
class PEPZero(Transform):
"""
Special processing for PEP 0.
"""
default_priority =760
def apply(self):
visitor = PEPZeroSpecial(self.document)
self.document.walk(visitor)
self.startnode.parent.remove(self.startnode)
class PEPZeroSpecial(nodes.SparseNodeVisitor):
"""
Perform the special processing needed by PEP 0:
- Mask email addresses.
- Link PEP numbers in the second column of 4-column tables to the PEPs
themselves.
"""
pep_url = Headers.pep_url
def unknown_visit(self, node):
pass
def visit_reference(self, node):
node.parent.replace(node, mask_email(node))
def visit_field_list(self, node):
if node.hasattr('class') and node['class'] == 'rfc2822':
raise nodes.SkipNode
def visit_tgroup(self, node):
self.pep_table = node['cols'] == 4
self.entry = 0
def visit_colspec(self, node):
self.entry += 1
if self.pep_table and self.entry == 2:
node['class'] = 'num'
def visit_row(self, node):
self.entry = 0
def visit_entry(self, node):
self.entry += 1
if self.pep_table and self.entry == 2 and len(node) == 1:
node['class'] = 'num'
p = node[0]
if isinstance(p, nodes.paragraph) and len(p) == 1:
text = p.astext()
try:
pep = int(text)
ref = (self.document.settings.pep_base_url
+ self.pep_url % pep)
p[0] = nodes.reference(text, text, refuri=ref)
except ValueError:
pass
non_masked_addresses = ('peps@python.org',
'python-list@python.org',
'python-dev@python.org')
def mask_email(ref, pepno=None):
"""
Mask the email address in `ref` and return a replacement node.
`ref` is returned unchanged if it contains no email address.
For email addresses such as "user@host", mask the address as "user at
host" (text) to thwart simple email address harvesters (except for those
listed in `non_masked_addresses`). If a PEP number (`pepno`) is given,
return a reference including a default email subject.
"""
if ref.hasattr('refuri') and ref['refuri'].startswith('mailto:'):
if ref['refuri'][8:] in non_masked_addresses:
replacement = ref[0]
else:
replacement_text = ref.astext().replace('@', '&#32;&#97;t&#32;')
replacement = nodes.raw('', replacement_text, format='html')
if pepno is None:
return replacement
else:
ref['refuri'] += '?subject=PEP%%20%s' % pepno
ref[:] = [replacement]
return ref
else:
return ref
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:55 $
# Copyright: This module has been placed in the public domain.
"""
Transforms for resolving references.
"""
__docformat__ = 'reStructuredText'
import sys
import re
from docutils import nodes, utils
from docutils.transforms import TransformError, Transform
indices = xrange(sys.maxint)
class ChainedTargets(Transform):
"""
Attributes "refuri" and "refname" are migrated from the final direct
target up the chain of contiguous adjacent internal targets, using
`ChainedTargetResolver`.
"""
default_priority = 420
def apply(self):
visitor = ChainedTargetResolver(self.document)
self.document.walk(visitor)
class ChainedTargetResolver(nodes.SparseNodeVisitor):
"""
Copy reference attributes up the length of a hyperlink target chain.
"Chained targets" are multiple adjacent internal hyperlink targets which
"point to" an external or indirect target. After the transform, all
chained targets will effectively point to the same place.
Given the following ``document`` as input::
<document>
<target id="a" name="a">
<target id="b" name="b">
<target id="c" name="c" refuri="http://chained.external.targets">
<target id="d" name="d">
<paragraph>
I'm known as "d".
<target id="e" name="e">
<target id="id1">
<target id="f" name="f" refname="d">
``ChainedTargetResolver(document).walk()`` will transform the above into::
<document>
<target id="a" name="a" refuri="http://chained.external.targets">
<target id="b" name="b" refuri="http://chained.external.targets">
<target id="c" name="c" refuri="http://chained.external.targets">
<target id="d" name="d">
<paragraph>
I'm known as "d".
<target id="e" name="e" refname="d">
<target id="id1" refname="d">
<target id="f" name="f" refname="d">
"""
def unknown_visit(self, node):
pass
def visit_target(self, node):
if node.hasattr('refuri'):
attname = 'refuri'
call_if_named = self.document.note_external_target
elif node.hasattr('refname'):
attname = 'refname'
call_if_named = self.document.note_indirect_target
elif node.hasattr('refid'):
attname = 'refid'
call_if_named = None
else:
return
attval = node[attname]
index = node.parent.index(node)
for i in range(index - 1, -1, -1):
sibling = node.parent[i]
if not isinstance(sibling, nodes.target) \
or sibling.hasattr('refuri') \
or sibling.hasattr('refname') \
or sibling.hasattr('refid'):
break
sibling[attname] = attval
if sibling.hasattr('name') and call_if_named:
call_if_named(sibling)
class AnonymousHyperlinks(Transform):
"""
Link anonymous references to targets. Given::
<paragraph>
<reference anonymous="1">
internal
<reference anonymous="1">
external
<target anonymous="1" id="id1">
<target anonymous="1" id="id2" refuri="http://external">
Corresponding references are linked via "refid" or resolved via "refuri"::
<paragraph>
<reference anonymous="1" refid="id1">
text
<reference anonymous="1" refuri="http://external">
external
<target anonymous="1" id="id1">
<target anonymous="1" id="id2" refuri="http://external">
"""
default_priority = 440
def apply(self):
if len(self.document.anonymous_refs) \
!= len(self.document.anonymous_targets):
msg = self.document.reporter.error(
'Anonymous hyperlink mismatch: %s references but %s '
'targets.\nSee "backrefs" attribute for IDs.'
% (len(self.document.anonymous_refs),
len(self.document.anonymous_targets)))
msgid = self.document.set_id(msg)
for ref in self.document.anonymous_refs:
prb = nodes.problematic(
ref.rawsource, ref.rawsource, refid=msgid)
prbid = self.document.set_id(prb)
msg.add_backref(prbid)
ref.parent.replace(ref, prb)
return
for ref, target in zip(self.document.anonymous_refs,
self.document.anonymous_targets):
if target.hasattr('refuri'):
ref['refuri'] = target['refuri']
ref.resolved = 1
else:
ref['refid'] = target['id']
self.document.note_refid(ref)
target.referenced = 1
class IndirectHyperlinks(Transform):
"""
a) Indirect external references::
<paragraph>
<reference refname="indirect external">
indirect external
<target id="id1" name="direct external"
refuri="http://indirect">
<target id="id2" name="indirect external"
refname="direct external">
The "refuri" attribute is migrated back to all indirect targets
from the final direct target (i.e. a target not referring to
another indirect target)::
<paragraph>
<reference refname="indirect external">
indirect external
<target id="id1" name="direct external"
refuri="http://indirect">
<target id="id2" name="indirect external"
refuri="http://indirect">
Once the attribute is migrated, the preexisting "refname" attribute
is dropped.
b) Indirect internal references::
<target id="id1" name="final target">
<paragraph>
<reference refname="indirect internal">
indirect internal
<target id="id2" name="indirect internal 2"
refname="final target">
<target id="id3" name="indirect internal"
refname="indirect internal 2">
Targets which indirectly refer to an internal target become one-hop
indirect (their "refid" attributes are directly set to the internal
target's "id"). References which indirectly refer to an internal
target become direct internal references::
<target id="id1" name="final target">
<paragraph>
<reference refid="id1">
indirect internal
<target id="id2" name="indirect internal 2" refid="id1">
<target id="id3" name="indirect internal" refid="id1">
"""
default_priority = 460
def apply(self):
for target in self.document.indirect_targets:
if not target.resolved:
self.resolve_indirect_target(target)
self.resolve_indirect_references(target)
def resolve_indirect_target(self, target):
refname = target['refname']
reftarget_id = self.document.nameids.get(refname)
if not reftarget_id:
# Check the unknown_reference_resolvers
for resolver_function in (self.document.transformer
.unknown_reference_resolvers):
if resolver_function(target):
break
else:
self.nonexistent_indirect_target(target)
return
reftarget = self.document.ids[reftarget_id]
if isinstance(reftarget, nodes.target) \
and not reftarget.resolved and reftarget.hasattr('refname'):
if hasattr(target, 'multiply_indirect'):
#and target.multiply_indirect):
#del target.multiply_indirect
self.circular_indirect_reference(target)
return
target.multiply_indirect = 1
self.resolve_indirect_target(reftarget) # multiply indirect
del target.multiply_indirect
if reftarget.hasattr('refuri'):
target['refuri'] = reftarget['refuri']
if target.hasattr('name'):
self.document.note_external_target(target)
elif reftarget.hasattr('refid'):
target['refid'] = reftarget['refid']
self.document.note_refid(target)
else:
try:
target['refid'] = reftarget['id']
self.document.note_refid(target)
except KeyError:
self.nonexistent_indirect_target(target)
return
del target['refname']
target.resolved = 1
reftarget.referenced = 1
def nonexistent_indirect_target(self, target):
if self.document.nameids.has_key(target['refname']):
self.indirect_target_error(target, 'which is a duplicate, and '
'cannot be used as a unique reference')
else:
self.indirect_target_error(target, 'which does not exist')
def circular_indirect_reference(self, target):
self.indirect_target_error(target, 'forming a circular reference')
def indirect_target_error(self, target, explanation):
naming = ''
if target.hasattr('name'):
naming = '"%s" ' % target['name']
reflist = self.document.refnames.get(target['name'], [])
else:
reflist = self.document.refids.get(target['id'], [])
naming += '(id="%s")' % target['id']
msg = self.document.reporter.error(
'Indirect hyperlink target %s refers to target "%s", %s.'
% (naming, target['refname'], explanation),
base_node=target)
msgid = self.document.set_id(msg)
for ref in reflist:
prb = nodes.problematic(
ref.rawsource, ref.rawsource, refid=msgid)
prbid = self.document.set_id(prb)
msg.add_backref(prbid)
ref.parent.replace(ref, prb)
target.resolved = 1
def resolve_indirect_references(self, target):
if target.hasattr('refid'):
attname = 'refid'
call_if_named = 0
call_method = self.document.note_refid
elif target.hasattr('refuri'):
attname = 'refuri'
call_if_named = 1
call_method = self.document.note_external_target
else:
return
attval = target[attname]
if target.hasattr('name'):
name = target['name']
try:
reflist = self.document.refnames[name]
except KeyError, instance:
if target.referenced:
return
msg = self.document.reporter.info(
'Indirect hyperlink target "%s" is not referenced.'
% name, base_node=target)
target.referenced = 1
return
delatt = 'refname'
else:
id = target['id']
try:
reflist = self.document.refids[id]
except KeyError, instance:
if target.referenced:
return
msg = self.document.reporter.info(
'Indirect hyperlink target id="%s" is not referenced.'
% id, base_node=target)
target.referenced = 1
return
delatt = 'refid'
for ref in reflist:
if ref.resolved:
continue
del ref[delatt]
ref[attname] = attval
if not call_if_named or ref.hasattr('name'):
call_method(ref)
ref.resolved = 1
if isinstance(ref, nodes.target):
self.resolve_indirect_references(ref)
target.referenced = 1
class ExternalTargets(Transform):
"""
Given::
<paragraph>
<reference refname="direct external">
direct external
<target id="id1" name="direct external" refuri="http://direct">
The "refname" attribute is replaced by the direct "refuri" attribute::
<paragraph>
<reference refuri="http://direct">
direct external
<target id="id1" name="direct external" refuri="http://direct">
"""
default_priority = 640
def apply(self):
for target in self.document.external_targets:
if target.hasattr('refuri') and target.hasattr('name'):
name = target['name']
refuri = target['refuri']
try:
reflist = self.document.refnames[name]
except KeyError, instance:
# @@@ First clause correct???
if not isinstance(target, nodes.target) or target.referenced:
continue
msg = self.document.reporter.info(
'External hyperlink target "%s" is not referenced.'
% name, base_node=target)
target.referenced = 1
continue
for ref in reflist:
if ref.resolved:
continue
del ref['refname']
ref['refuri'] = refuri
ref.resolved = 1
target.referenced = 1
class InternalTargets(Transform):
"""
Given::
<paragraph>
<reference refname="direct internal">
direct internal
<target id="id1" name="direct internal">
The "refname" attribute is replaced by "refid" linking to the target's
"id"::
<paragraph>
<reference refid="id1">
direct internal
<target id="id1" name="direct internal">
"""
default_priority = 660
def apply(self):
for target in self.document.internal_targets:
if target.hasattr('refuri') or target.hasattr('refid') \
or not target.hasattr('name'):
continue
name = target['name']
refid = target['id']
try:
reflist = self.document.refnames[name]
except KeyError, instance:
if target.referenced:
continue
msg = self.document.reporter.info(
'Internal hyperlink target "%s" is not referenced.'
% name, base_node=target)
target.referenced = 1
continue
for ref in reflist:
if ref.resolved:
continue
del ref['refname']
ref['refid'] = refid
ref.resolved = 1
target.referenced = 1
class Footnotes(Transform):
"""
Assign numbers to autonumbered footnotes, and resolve links to footnotes,
citations, and their references.
Given the following ``document`` as input::
<document>
<paragraph>
A labeled autonumbered footnote referece:
<footnote_reference auto="1" id="id1" refname="footnote">
<paragraph>
An unlabeled autonumbered footnote referece:
<footnote_reference auto="1" id="id2">
<footnote auto="1" id="id3">
<paragraph>
Unlabeled autonumbered footnote.
<footnote auto="1" id="footnote" name="footnote">
<paragraph>
Labeled autonumbered footnote.
Auto-numbered footnotes have attribute ``auto="1"`` and no label.
Auto-numbered footnote_references have no reference text (they're
empty elements). When resolving the numbering, a ``label`` element
is added to the beginning of the ``footnote``, and reference text
to the ``footnote_reference``.
The transformed result will be::
<document>
<paragraph>
A labeled autonumbered footnote referece:
<footnote_reference auto="1" id="id1" refid="footnote">
2
<paragraph>
An unlabeled autonumbered footnote referece:
<footnote_reference auto="1" id="id2" refid="id3">
1
<footnote auto="1" id="id3" backrefs="id2">
<label>
1
<paragraph>
Unlabeled autonumbered footnote.
<footnote auto="1" id="footnote" name="footnote" backrefs="id1">
<label>
2
<paragraph>
Labeled autonumbered footnote.
Note that the footnotes are not in the same order as the references.
The labels and reference text are added to the auto-numbered ``footnote``
and ``footnote_reference`` elements. Footnote elements are backlinked to
their references via "refids" attributes. References are assigned "id"
and "refid" attributes.
After adding labels and reference text, the "auto" attributes can be
ignored.
"""
default_priority = 620
autofootnote_labels = None
"""Keep track of unlabeled autonumbered footnotes."""
symbols = [
# Entries 1-4 and 6 below are from section 12.51 of
# The Chicago Manual of Style, 14th edition.
'*', # asterisk/star
u'\u2020', # dagger &dagger;
u'\u2021', # double dagger &Dagger;
u'\u00A7', # section mark &sect;
u'\u00B6', # paragraph mark (pilcrow) &para;
# (parallels ['||'] in CMoS)
'#', # number sign
# The entries below were chosen arbitrarily.
u'\u2660', # spade suit &spades;
u'\u2665', # heart suit &hearts;
u'\u2666', # diamond suit &diams;
u'\u2663', # club suit &clubs;
]
def apply(self):
self.autofootnote_labels = []
startnum = self.document.autofootnote_start
self.document.autofootnote_start = self.number_footnotes(startnum)
self.number_footnote_references(startnum)
self.symbolize_footnotes()
self.resolve_footnotes_and_citations()
def number_footnotes(self, startnum):
"""
Assign numbers to autonumbered footnotes.
For labeled autonumbered footnotes, copy the number over to
corresponding footnote references.
"""
for footnote in self.document.autofootnotes:
while 1:
label = str(startnum)
startnum += 1
if not self.document.nameids.has_key(label):
break
footnote.insert(0, nodes.label('', label))
if footnote.hasattr('dupname'):
continue
if footnote.hasattr('name'):
name = footnote['name']
for ref in self.document.footnote_refs.get(name, []):
ref += nodes.Text(label)
ref.delattr('refname')
ref['refid'] = footnote['id']
footnote.add_backref(ref['id'])
self.document.note_refid(ref)
ref.resolved = 1
else:
footnote['name'] = label
self.document.note_explicit_target(footnote, footnote)
self.autofootnote_labels.append(label)
return startnum
def number_footnote_references(self, startnum):
"""Assign numbers to autonumbered footnote references."""
i = 0
for ref in self.document.autofootnote_refs:
if ref.resolved or ref.hasattr('refid'):
continue
try:
label = self.autofootnote_labels[i]
except IndexError:
msg = self.document.reporter.error(
'Too many autonumbered footnote references: only %s '
'corresponding footnotes available.'
% len(self.autofootnote_labels), base_node=ref)
msgid = self.document.set_id(msg)
for ref in self.document.autofootnote_refs[i:]:
if ref.resolved or ref.hasattr('refname'):
continue
prb = nodes.problematic(
ref.rawsource, ref.rawsource, refid=msgid)
prbid = self.document.set_id(prb)
msg.add_backref(prbid)
ref.parent.replace(ref, prb)
break
ref += nodes.Text(label)
id = self.document.nameids[label]
footnote = self.document.ids[id]
ref['refid'] = id
self.document.note_refid(ref)
footnote.add_backref(ref['id'])
ref.resolved = 1
i += 1
def symbolize_footnotes(self):
"""Add symbols indexes to "[*]"-style footnotes and references."""
labels = []
for footnote in self.document.symbol_footnotes:
reps, index = divmod(self.document.symbol_footnote_start,
len(self.symbols))
labeltext = self.symbols[index] * (reps + 1)
labels.append(labeltext)
footnote.insert(0, nodes.label('', labeltext))
self.document.symbol_footnote_start += 1
self.document.set_id(footnote)
i = 0
for ref in self.document.symbol_footnote_refs:
try:
ref += nodes.Text(labels[i])
except IndexError:
msg = self.document.reporter.error(
'Too many symbol footnote references: only %s '
'corresponding footnotes available.' % len(labels),
base_node=ref)
msgid = self.document.set_id(msg)
for ref in self.document.symbol_footnote_refs[i:]:
if ref.resolved or ref.hasattr('refid'):
continue
prb = nodes.problematic(
ref.rawsource, ref.rawsource, refid=msgid)
prbid = self.document.set_id(prb)
msg.add_backref(prbid)
ref.parent.replace(ref, prb)
break
footnote = self.document.symbol_footnotes[i]
ref['refid'] = footnote['id']
self.document.note_refid(ref)
footnote.add_backref(ref['id'])
i += 1
def resolve_footnotes_and_citations(self):
"""
Link manually-labeled footnotes and citations to/from their
references.
"""
for footnote in self.document.footnotes:
label = footnote['name']
if self.document.footnote_refs.has_key(label):
reflist = self.document.footnote_refs[label]
self.resolve_references(footnote, reflist)
for citation in self.document.citations:
label = citation['name']
if self.document.citation_refs.has_key(label):
reflist = self.document.citation_refs[label]
self.resolve_references(citation, reflist)
def resolve_references(self, note, reflist):
id = note['id']
for ref in reflist:
if ref.resolved:
continue
ref.delattr('refname')
ref['refid'] = id
note.add_backref(ref['id'])
ref.resolved = 1
note.resolved = 1
class Substitutions(Transform):
"""
Given the following ``document`` as input::
<document>
<paragraph>
The
<substitution_reference refname="biohazard">
biohazard
symbol is deservedly scary-looking.
<substitution_definition name="biohazard">
<image alt="biohazard" uri="biohazard.png">
The ``substitution_reference`` will simply be replaced by the
contents of the corresponding ``substitution_definition``.
The transformed result will be::
<document>
<paragraph>
The
<image alt="biohazard" uri="biohazard.png">
symbol is deservedly scary-looking.
<substitution_definition name="biohazard">
<image alt="biohazard" uri="biohazard.png">
"""
default_priority = 220
"""The Substitutions transform has to be applied very early, before
`docutils.tranforms.frontmatter.DocTitle` and others."""
def apply(self):
defs = self.document.substitution_defs
normed = self.document.substitution_names
for refname, refs in self.document.substitution_refs.items():
for ref in refs:
key = None
if defs.has_key(refname):
key = refname
else:
normed_name = refname.lower()
if normed.has_key(normed_name):
key = normed[normed_name]
if key is None:
msg = self.document.reporter.error(
'Undefined substitution referenced: "%s".'
% refname, base_node=ref)
msgid = self.document.set_id(msg)
prb = nodes.problematic(
ref.rawsource, ref.rawsource, refid=msgid)
prbid = self.document.set_id(prb)
msg.add_backref(prbid)
ref.parent.replace(ref, prb)
else:
ref.parent.replace(ref, defs[key].get_children())
self.document.substitution_refs = None # release replaced references
class TargetNotes(Transform):
"""
Creates a footnote for each external target in the text, and corresponding
footnote references after each reference.
"""
default_priority = 540
"""The TargetNotes transform has to be applied after `IndirectHyperlinks`
but before `Footnotes`."""
def apply(self):
notes = {}
nodelist = []
for target in self.document.external_targets:
name = target.get('name')
if not name:
print >>sys.stderr, 'no name on target: %r' % target
continue
refs = self.document.refnames.get(name, [])
if not refs:
continue
footnote = self.make_target_footnote(target, refs, notes)
if not notes.has_key(target['refuri']):
notes[target['refuri']] = footnote
nodelist.append(footnote)
if len(self.document.anonymous_targets) \
== len(self.document.anonymous_refs):
for target, ref in zip(self.document.anonymous_targets,
self.document.anonymous_refs):
if target.hasattr('refuri'):
footnote = self.make_target_footnote(target, [ref], notes)
if not notes.has_key(target['refuri']):
notes[target['refuri']] = footnote
nodelist.append(footnote)
self.startnode.parent.replace(self.startnode, nodelist)
def make_target_footnote(self, target, refs, notes):
refuri = target['refuri']
if notes.has_key(refuri): # duplicate?
footnote = notes[refuri]
footnote_name = footnote['name']
else: # original
footnote = nodes.footnote()
footnote_id = self.document.set_id(footnote)
# Use a colon; they can't be produced inside names by the parser:
footnote_name = 'target_note: ' + footnote_id
footnote['auto'] = 1
footnote['name'] = footnote_name
footnote_paragraph = nodes.paragraph()
footnote_paragraph += nodes.reference('', refuri, refuri=refuri)
footnote += footnote_paragraph
self.document.note_autofootnote(footnote)
self.document.note_explicit_target(footnote, footnote)
for ref in refs:
if isinstance(ref, nodes.target):
continue
refnode = nodes.footnote_reference(
refname=footnote_name, auto=1)
self.document.note_autofootnote_ref(refnode)
self.document.note_footnote_ref(refnode)
index = ref.parent.index(ref) + 1
reflist = [refnode]
if not self.document.settings.trim_footnote_reference_space:
reflist.insert(0, nodes.Text(' '))
ref.parent.insert(index, reflist)
return footnote
# Authors: David Goodger, Ueli Schlaepfer
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:55 $
# Copyright: This module has been placed in the public domain.
"""
Transforms needed by most or all documents:
- `Decorations`: Generate a document's header & footer.
- `Messages`: Placement of system messages stored in
`nodes.document.transform_messages`.
- `TestMessages`: Like `Messages`, used on test runs.
- `FinalReferences`: Resolve remaining references.
"""
__docformat__ = 'reStructuredText'
import re
import sys
import time
from docutils import nodes, utils
from docutils.transforms import TransformError, Transform
class Decorations(Transform):
"""
Populate a document's decoration element (header, footer).
"""
default_priority = 820
def apply(self):
header = self.generate_header()
footer = self.generate_footer()
if header or footer:
decoration = nodes.decoration()
decoration += header
decoration += footer
document = self.document
index = document.first_child_not_matching_class(
nodes.PreDecorative)
if index is None:
document += decoration
else:
document[index:index] = [decoration]
def generate_header(self):
return None
def generate_footer(self):
# @@@ Text is hard-coded for now.
# Should be made dynamic (language-dependent).
settings = self.document.settings
if settings.generator or settings.datestamp or settings.source_link \
or settings.source_url:
text = []
if settings.source_link and settings._source \
or settings.source_url:
if settings.source_url:
source = settings.source_url
else:
source = utils.relative_path(settings._destination,
settings._source)
text.extend([
nodes.reference('', 'View document source',
refuri=source),
nodes.Text('.\n')])
if settings.datestamp:
datestamp = time.strftime(settings.datestamp, time.gmtime())
text.append(nodes.Text('Generated on: ' + datestamp + '.\n'))
if settings.generator:
text.extend([
nodes.Text('Generated by '),
nodes.reference('', 'Docutils', refuri=
'http://docutils.sourceforge.net/'),
nodes.Text(' from '),
nodes.reference('', 'reStructuredText', refuri='http://'
'docutils.sourceforge.net/rst.html'),
nodes.Text(' source.\n')])
footer = nodes.footer()
footer += nodes.paragraph('', '', *text)
return footer
else:
return None
class Messages(Transform):
"""
Place any system messages generated after parsing into a dedicated section
of the document.
"""
default_priority = 860
def apply(self):
unfiltered = self.document.transform_messages
threshold = self.document.reporter['writer'].report_level
messages = []
for msg in unfiltered:
if msg['level'] >= threshold and not msg.parent:
messages.append(msg)
if messages:
section = nodes.section(CLASS='system-messages')
# @@@ get this from the language module?
section += nodes.title('', 'Docutils System Messages')
section += messages
self.document.transform_messages[:] = []
self.document += section
class FilterMessages(Transform):
"""
Remove system messages below verbosity threshold.
"""
default_priority = 870
def apply(self):
visitor = SystemMessageFilterVisitor(self.document)
self.document.walk(visitor)
class SystemMessageFilterVisitor(nodes.SparseNodeVisitor):
def unknown_visit(self, node):
pass
def visit_system_message(self, node):
if node['level'] < self.document.reporter['writer'].report_level:
node.parent.remove(node)
class TestMessages(Transform):
"""
Append all post-parse system messages to the end of the document.
"""
default_priority = 890
def apply(self):
for msg in self.document.transform_messages:
if not msg.parent:
self.document += msg
class FinalChecks(Transform):
"""
Perform last-minute checks.
- Check for dangling references (incl. footnote & citation).
"""
default_priority = 840
def apply(self):
visitor = FinalCheckVisitor(
self.document,
self.document.transformer.unknown_reference_resolvers)
self.document.walk(visitor)
if self.document.settings.expose_internals:
visitor = InternalAttributeExposer(self.document)
self.document.walk(visitor)
class FinalCheckVisitor(nodes.SparseNodeVisitor):
def __init__(self, document, unknown_reference_resolvers):
nodes.SparseNodeVisitor.__init__(self, document)
self.document = document
self.unknown_reference_resolvers = unknown_reference_resolvers
def unknown_visit(self, node):
pass
def visit_reference(self, node):
if node.resolved or not node.hasattr('refname'):
return
refname = node['refname']
id = self.document.nameids.get(refname)
if id is None:
for resolver_function in self.unknown_reference_resolvers:
if resolver_function(node):
break
else:
if self.document.nameids.has_key(refname):
msg = self.document.reporter.error(
'Duplicate target name, cannot be used as a unique '
'reference: "%s".' % (node['refname']), base_node=node)
else:
msg = self.document.reporter.error(
'Unknown target name: "%s".' % (node['refname']),
base_node=node)
msgid = self.document.set_id(msg)
prb = nodes.problematic(
node.rawsource, node.rawsource, refid=msgid)
prbid = self.document.set_id(prb)
msg.add_backref(prbid)
node.parent.replace(node, prb)
else:
del node['refname']
node['refid'] = id
self.document.ids[id].referenced = 1
node.resolved = 1
visit_footnote_reference = visit_citation_reference = visit_reference
class InternalAttributeExposer(nodes.GenericNodeVisitor):
def __init__(self, document):
nodes.GenericNodeVisitor.__init__(self, document)
self.internal_attributes = document.settings.expose_internals
def default_visit(self, node):
for att in self.internal_attributes:
value = getattr(node, att, None)
if value is not None:
node['internal:' + att] = value
"""
`schemes` is a dictionary with lowercase URI addressing schemes as
keys and descriptions as values. It was compiled from the index at
http://www.iana.org/assignments/uri-schemes (revised 2003-11-26)
and an older list at http://www.w3.org/Addressing/schemes.html.
"""
# 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'),
'feed' : 'NetNewsWire feed',
'file': 'Host-specific file names',
'finger': '',
'freenet': '',
'ftp': 'File Transfer Protocol',
'go': 'go; RFC3368',
'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',
'hydra': 'SubEthaEdit URI. See http://www.codingmonkeys.de/subethaedit.',
'iioploc': 'Internet Inter-ORB Protocol Location?',
'ilu': 'Inter-Language Unification',
'im': 'Instant Messaging',
'imap': 'Internet Message Access Protocol',
'ior': 'CORBA interoperable object reference',
'ipp': 'Internet Printing Protocol',
'irc': 'Internet Relay Chat',
'iseek' : 'See www.ambrosiasw.com; a little util for OS X.',
'jar': 'Java archive',
'javascript': ('JavaScript code; evaluates the expression after the '
'colon'),
'jdbc': 'JDBC connection URI.',
'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'),
'mupdate': 'Mailbox Update (MUPDATE) Protocol',
'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',
'pres': 'Presence',
'printer': '',
'prospero': 'Prospero Directory Service',
'rdar' : 'URLs found in Darwin source (http://www.opensource.apple.com/darwinsource/).',
'res': '',
'rtsp': 'real time streaming protocol',
'rvp': '',
'rwhois': '',
'rx': 'Remote Execution',
'sdp': '',
'service': 'service location',
'shttp': 'secure hypertext transfer protocol',
'sip': 'Session Initiation Protocol',
'sips': 'secure session intitiaion protocol',
'smb': 'SAMBA filesystems.',
'snews': 'For NNTP postings via SSL',
'soap.beep': '',
'soap.beeps': '',
'ssh': 'Reference to interactive sessions via ssh.',
'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',
'tftp': 'Trivial File Transfer Protocol',
'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.',
'x-man-page': 'Opens man page in Terminal.app on OS X (see macosxhints.com)',
'xmlrpc.beep': '',
'xmlrpc.beeps': '',
'z39.50r': 'Z39.50 Retrieval',
'z39.50s': 'Z39.50 Session',}
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:39 $
# 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 SystemMessagePropagation(ApplicationError): pass
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 if not set to ''.
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),
'' (empty string, for discarding all stream messages) 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):
# Leave stream untouched if it's ''.
if stream != '':
if type(stream) == StringType:
stream = open(stream, 'w')
elif type(stream) == UnicodeType:
stream = open(stream.encode(), 'w')
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) and stream:
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).
- `TypeError` for invalid option value types (raised by 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).
- `TypeError` for invalid option value types (raised by conversion
function).
"""
options = {}
for name, value in option_list:
convertor = options_spec[name] # raises KeyError if unknown
if convertor is None:
raise KeyError(name) # or if explicitly disabled
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.search(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
# Authors: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:57 $
# Copyright: This module has been placed in the public domain.
"""
This package contains Docutils Writer modules.
"""
__docformat__ = 'reStructuredText'
import sys
import docutils
from docutils import languages, Component
from docutils.transforms import universal
class Writer(Component):
"""
Abstract base class for docutils Writers.
Each writer module or package must export a subclass also called 'Writer'.
Each writer must support all standard node types listed in
`docutils.nodes.node_class_names`.
The `write()` method is the main entry point.
"""
component_type = 'writer'
config_section = 'writers'
document = None
"""The document to write (Docutils doctree); set by `write`."""
output = None
"""Final translated form of `document` (Unicode string);
set by `translate`."""
language = None
"""Language module for the document; set by `write`."""
destination = None
"""`docutils.io` Output object; where to write the document.
Set by `write`."""
def __init__(self):
# Currently only used by HTML writer for output fragments:
self.parts = {}
"""Mapping of document part names to fragments of `self.output`.
Values are Unicode strings; encoding is up to the client. The 'whole'
key should contain the entire document output.
"""
def write(self, document, destination):
"""
Process a document into its final form.
Translate `document` (a Docutils document tree) into the Writer's
native format, and write it out to its `destination` (a
`docutils.io.Output` subclass object).
Normally not overridden or extended in subclasses.
"""
self.document = document
self.language = languages.get_language(
document.settings.language_code)
self.destination = destination
self.translate()
output = self.destination.write(self.output)
return output
def translate(self):
"""
Do final translation of `self.document` into `self.output` (Unicode
string). Called from `write`. Override in subclasses.
Usually done with a `docutils.nodes.NodeVisitor` subclass, in
combination with a call to `docutils.nodes.Node.walk()` or
`docutils.nodes.Node.walkabout()`. The ``NodeVisitor`` subclass must
support all standard elements (listed in
`docutils.nodes.node_class_names`) and possibly non-standard elements
used by the current Reader as well.
"""
raise NotImplementedError('subclass must override this method')
def assemble_parts(self):
"""Assemble the `self.parts` dictionary. Extend in subclasses."""
self.parts['whole'] = self.output
_writer_aliases = {
'html': 'html4css1',
'latex': 'latex2e',
'pprint': 'pseudoxml',
'pformat': 'pseudoxml',
'pdf': 'rlpdf',
'xml': 'docutils_xml',}
def get_writer_class(writer_name):
"""Return the Writer class from the `writer_name` module."""
writer_name = writer_name.lower()
if _writer_aliases.has_key(writer_name):
writer_name = _writer_aliases[writer_name]
module = __import__(writer_name, globals(), locals())
return module.Writer
# Authors: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:57 $
# Copyright: This module has been placed in the public domain.
"""
Simple internal document tree Writer, writes Docutils XML.
"""
__docformat__ = 'reStructuredText'
import docutils
from docutils import frontend, writers
class Writer(writers.Writer):
supported = ('xml',)
"""Formats this writer supports."""
settings_spec = (
'"Docutils XML" Writer Options',
'Warning: the --newlines and --indents options may adversely affect '
'whitespace; use them only for reading convenience.',
(('Generate XML with newlines before and after tags.',
['--newlines'],
{'action': 'store_true', 'validator': frontend.validate_boolean}),
('Generate XML with indents and newlines.',
['--indents'],
{'action': 'store_true', 'validator': frontend.validate_boolean}),
('Omit the XML declaration. Use with caution.',
['--no-xml-declaration'],
{'dest': 'xml_declaration', 'default': 1, 'action': 'store_false',
'validator': frontend.validate_boolean}),
('Omit the DOCTYPE declaration.',
['--no-doctype'],
{'dest': 'doctype_declaration', 'default': 1,
'action': 'store_false', 'validator': frontend.validate_boolean}),))
config_section = 'docutils_xml writer'
config_section_dependencies = ('writers',)
output = None
"""Final translated form of `document`."""
xml_declaration = '<?xml version="1.0" encoding="%s"?>\n'
#xml_stylesheet = '<?xml-stylesheet type="text/xsl" href="%s"?>\n'
doctype = (
'<!DOCTYPE document PUBLIC'
' "+//IDN docutils.sourceforge.net//DTD Docutils Generic//EN//XML"'
' "http://docutils.sourceforge.net/docs/ref/docutils.dtd">\n')
generator = '<!-- Generated by Docutils %s -->\n'
def translate(self):
settings = self.document.settings
indent = newline = ''
if settings.newlines:
newline = '\n'
if settings.indents:
newline = '\n'
indent = ' '
output_prefix = []
if settings.xml_declaration:
output_prefix.append(
self.xml_declaration % settings.output_encoding)
if settings.doctype_declaration:
output_prefix.append(self.doctype)
output_prefix.append(self.generator % docutils.__version__)
docnode = self.document.asdom().childNodes[0]
self.output = (''.join(output_prefix)
+ docnode.toprettyxml(indent, newline))
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:57 $
# Copyright: This module has been placed in the public domain.
"""
Simple HyperText Markup Language document tree Writer.
The output conforms to the HTML 4.01 Transitional DTD and to the Extensible
HTML version 1.0 Transitional DTD (*almost* strict). The output contains a
minimum of formatting information. A cascading style sheet ("default.css" by
default) is required for proper viewing with a modern graphical browser.
"""
__docformat__ = 'reStructuredText'
import sys
import os
import os.path
import time
import re
from types import ListType
try:
import Image # check for the Python Imaging Library
except ImportError:
Image = None
import docutils
from docutils import frontend, nodes, utils, writers, languages
class Writer(writers.Writer):
supported = ('html', 'html4css1', 'xhtml')
"""Formats this writer supports."""
settings_spec = (
'HTML-Specific Options',
None,
(('Specify a stylesheet URL, used verbatim. Default is '
'"default.css". Overridden by --stylesheet-path.',
['--stylesheet'],
{'default': 'default.css', 'metavar': '<URL>'}),
('Specify a stylesheet file, relative to the current working '
'directory. The path is adjusted relative to the output HTML '
'file. Overrides --stylesheet.',
['--stylesheet-path'],
{'metavar': '<file>'}),
('Link to the stylesheet in the output HTML file. This is the '
'default.',
['--link-stylesheet'],
{'dest': 'embed_stylesheet', 'action': 'store_false',
'validator': frontend.validate_boolean}),
('Embed the stylesheet in the output HTML file. The stylesheet '
'file must be accessible during processing (--stylesheet-path is '
'recommended). The stylesheet is embedded inside a comment, so it '
'must not contain the text "--" (two hyphens). Default: link the '
'stylesheet, do not embed it.',
['--embed-stylesheet'],
{'action': 'store_true', 'validator': frontend.validate_boolean}),
('Specify the initial header level. Default is 1 for "<h1>". '
'Does not affect document title & subtitle (see --no-doc-title).',
['--initial-header-level'],
{'choices': '1 2 3 4 5 6'.split(), 'default': '1',
'metavar': '<level>'}),
('Format for footnote references: one of "superscript" or '
'"brackets". Default is "superscript".',
['--footnote-references'],
{'choices': ['superscript', 'brackets'], 'default': 'superscript',
'metavar': '<format>'}),
('Format for block quote attributions: one of "dash" (em-dash '
'prefix), "parentheses"/"parens", or "none". Default is "dash".',
['--attribution'],
{'choices': ['dash', 'parentheses', 'parens', 'none'],
'default': 'dash', 'metavar': '<format>'}),
('Remove extra vertical whitespace between items of bullet lists '
'and enumerated lists, when list items are "simple" (i.e., all '
'items each contain one paragraph and/or one "simple" sublist '
'only). Default: enabled.',
['--compact-lists'],
{'default': 1, 'action': 'store_true',
'validator': frontend.validate_boolean}),
('Disable compact simple bullet and enumerated lists.',
['--no-compact-lists'],
{'dest': 'compact_lists', 'action': 'store_false'}),
('Omit the XML declaration. Use with caution.',
['--no-xml-declaration'],
{'dest': 'xml_declaration', 'default': 1, 'action': 'store_false',
'validator': frontend.validate_boolean}),))
relative_path_settings = ('stylesheet_path',)
config_section = 'html4css1 writer'
config_section_dependencies = ('writers',)
def __init__(self):
writers.Writer.__init__(self)
self.translator_class = HTMLTranslator
def translate(self):
visitor = self.translator_class(self.document)
self.document.walkabout(visitor)
self.output = visitor.astext()
self.visitor = visitor
for attr in ('head_prefix', 'stylesheet', 'head', 'body_prefix',
'body_pre_docinfo', 'docinfo', 'body', 'fragment',
'body_suffix'):
setattr(self, attr, getattr(visitor, attr))
def assemble_parts(self):
writers.Writer.assemble_parts(self)
for part in ('title', 'subtitle', 'docinfo', 'body', 'header',
'footer', 'meta', 'stylesheet', 'fragment'):
self.parts[part] = ''.join(getattr(self.visitor, part))
class HTMLTranslator(nodes.NodeVisitor):
"""
This HTML writer has been optimized to produce visually compact
lists (less vertical whitespace). HTML's mixed content models
allow list items to contain "<li><p>body elements</p></li>" or
"<li>just text</li>" or even "<li>text<p>and body
elements</p>combined</li>", each with different effects. It would
be best to stick with strict body elements in list items, but they
affect vertical spacing in browsers (although they really
shouldn't).
Here is an outline of the optimization:
- Check for and omit <p> tags in "simple" lists: list items
contain either a single paragraph, a nested simple list, or a
paragraph followed by a nested simple list. This means that
this list can be compact:
- Item 1.
- Item 2.
But this list cannot be compact:
- Item 1.
This second paragraph forces space between list items.
- Item 2.
- In non-list contexts, omit <p> tags on a paragraph if that
paragraph is the only child of its parent (footnotes & citations
are allowed a label first).
- Regardless of the above, in definitions, table cells, field bodies,
option descriptions, and list items, mark the first child with
'class="first"' and the last child with 'class="last"'. The stylesheet
sets the margins (top & bottom respectively) to 0 for these elements.
The ``no_compact_lists`` setting (``--no-compact-lists`` command-line
option) disables list whitespace optimization.
"""
xml_declaration = '<?xml version="1.0" encoding="%s" ?>\n'
doctype = ('<!DOCTYPE html'
' PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'
' "http://www.w3.org/TR/xhtml1/DTD/'
'xhtml1-transitional.dtd">\n')
html_head = ('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="%s" '
'lang="%s">\n<head>\n')
content_type = ('<meta http-equiv="Content-Type" content="text/html; '
'charset=%s" />\n')
generator = ('<meta name="generator" content="Docutils %s: '
'http://docutils.sourceforge.net/" />\n')
stylesheet_link = '<link rel="stylesheet" href="%s" type="text/css" />\n'
embedded_stylesheet = '<style type="text/css"><!--\n\n%s\n--></style>\n'
named_tags = {'a': 1, 'applet': 1, 'form': 1, 'frame': 1, 'iframe': 1,
'img': 1, 'map': 1}
words_and_spaces = re.compile(r'\S+| +|\n')
def __init__(self, document):
nodes.NodeVisitor.__init__(self, document)
self.settings = settings = document.settings
lcode = settings.language_code
self.language = languages.get_language(lcode)
self.meta = [self.content_type % settings.output_encoding,
self.generator % docutils.__version__]
self.head_prefix = [
self.doctype,
self.html_head % (lcode, lcode)]
self.head_prefix.extend(self.meta)
if settings.xml_declaration:
self.head_prefix.insert(0, self.xml_declaration
% settings.output_encoding)
self.head = []
if settings.embed_stylesheet:
stylesheet = self.get_stylesheet_reference(
os.path.join(os.getcwd(), 'dummy'))
stylesheet_text = open(stylesheet).read()
self.stylesheet = [self.embedded_stylesheet % stylesheet_text]
else:
stylesheet = self.get_stylesheet_reference()
if stylesheet:
self.stylesheet = [self.stylesheet_link % stylesheet]
else:
self.stylesheet = []
self.body_prefix = ['</head>\n<body>\n']
# document title, subtitle display
self.body_pre_docinfo = []
# author, date, etc.
self.docinfo = []
self.body = []
self.fragment = []
self.body_suffix = ['</body>\n</html>\n']
self.section_level = 0
self.initial_header_level = int(settings.initial_header_level)
# A heterogenous stack used in conjunction with the tree traversal.
# Make sure that the pops correspond to the pushes:
self.context = []
self.topic_class = ''
self.colspecs = []
self.compact_p = 1
self.compact_simple = None
self.in_docinfo = None
self.in_sidebar = None
self.title = []
self.subtitle = []
self.header = []
self.footer = []
self.in_document_title = 0
def get_stylesheet_reference(self, relative_to=None):
settings = self.settings
if settings.stylesheet_path:
if relative_to == None:
relative_to = settings._destination
return utils.relative_path(relative_to, settings.stylesheet_path)
else:
return settings.stylesheet
def astext(self):
return ''.join(self.head_prefix + self.head
+ self.stylesheet + self.body_prefix
+ self.body_pre_docinfo + self.docinfo
+ self.body + self.body_suffix)
def encode(self, text):
"""Encode special characters in `text` & return."""
# @@@ A codec to do these and all other HTML entities would be nice.
text = text.replace("&", "&amp;")
text = text.replace("<", "&lt;")
text = text.replace('"', "&quot;")
text = text.replace(">", "&gt;")
text = text.replace("@", "&#64;") # may thwart some address harvesters
return text
def attval(self, text,
whitespace=re.compile('[\n\r\t\v\f]')):
"""Cleanse, HTML encode, and return attribute value text."""
return self.encode(whitespace.sub(' ', text))
def starttag(self, node, tagname, suffix='\n', infix='', **attributes):
"""
Construct and return a start tag given a node (id & class attributes
are extracted), tag name, and optional attributes.
"""
tagname = tagname.lower()
atts = {}
for (name, value) in attributes.items():
atts[name.lower()] = value
for att in ('class',): # append to node attribute
if node.has_key(att) or atts.has_key(att):
atts[att] = \
(node.get(att, '') + ' ' + atts.get(att, '')).strip()
for att in ('id',): # node attribute overrides
if node.has_key(att):
atts[att] = node[att]
if atts.has_key('id') and self.named_tags.has_key(tagname):
atts['name'] = atts['id'] # for compatibility with old browsers
attlist = atts.items()
attlist.sort()
parts = [tagname]
for name, value in attlist:
if value is None: # boolean attribute
# According to the HTML spec, ``<element boolean>`` is good,
# ``<element boolean="boolean">`` is bad.
# (But the XHTML (XML) spec says the opposite. <sigh>)
parts.append(name.lower())
elif isinstance(value, ListType):
values = [unicode(v) for v in value]
parts.append('%s="%s"' % (name.lower(),
self.attval(' '.join(values))))
else:
try:
uval = unicode(value)
except TypeError: # for Python 2.1 compatibility:
uval = unicode(str(value))
parts.append('%s="%s"' % (name.lower(), self.attval(uval)))
return '<%s%s>%s' % (' '.join(parts), infix, suffix)
def emptytag(self, node, tagname, suffix='\n', **attributes):
"""Construct and return an XML-compatible empty tag."""
return self.starttag(node, tagname, suffix, infix=' /', **attributes)
def visit_Text(self, node):
self.body.append(self.encode(node.astext()))
def depart_Text(self, node):
pass
def visit_abbreviation(self, node):
# @@@ implementation incomplete ("title" attribute)
self.body.append(self.starttag(node, 'abbr', ''))
def depart_abbreviation(self, node):
self.body.append('</abbr>')
def visit_acronym(self, node):
# @@@ implementation incomplete ("title" attribute)
self.body.append(self.starttag(node, 'acronym', ''))
def depart_acronym(self, node):
self.body.append('</acronym>')
def visit_address(self, node):
self.visit_docinfo_item(node, 'address', meta=None)
self.body.append(self.starttag(node, 'pre', CLASS='address'))
def depart_address(self, node):
self.body.append('\n</pre>\n')
self.depart_docinfo_item()
def visit_admonition(self, node, name=''):
self.body.append(self.starttag(node, 'div',
CLASS=(name or 'admonition')))
if name:
self.body.append('<p class="admonition-title first">'
+ self.language.labels[name] + '</p>\n')
def depart_admonition(self, node=None):
self.body.append('</div>\n')
def visit_attention(self, node):
self.visit_admonition(node, 'attention')
def depart_attention(self, node):
self.depart_admonition()
attribution_formats = {'dash': ('&mdash;', ''),
'parentheses': ('(', ')'),
'parens': ('(', ')'),
'none': ('', '')}
def visit_attribution(self, node):
prefix, suffix = self.attribution_formats[self.settings.attribution]
self.context.append(suffix)
self.body.append(
self.starttag(node, 'p', prefix, CLASS='attribution'))
def depart_attribution(self, node):
self.body.append(self.context.pop() + '</p>\n')
def visit_author(self, node):
self.visit_docinfo_item(node, 'author')
def depart_author(self, node):
self.depart_docinfo_item()
def visit_authors(self, node):
pass
def depart_authors(self, node):
pass
def visit_block_quote(self, node):
self.body.append(self.starttag(node, 'blockquote'))
def depart_block_quote(self, node):
self.body.append('</blockquote>\n')
def check_simple_list(self, node):
"""Check for a simple list that can be rendered compactly."""
visitor = SimpleListChecker(self.document)
try:
node.walk(visitor)
except nodes.NodeFound:
return None
else:
return 1
def visit_bullet_list(self, node):
atts = {}
old_compact_simple = self.compact_simple
self.context.append((self.compact_simple, self.compact_p))
self.compact_p = None
self.compact_simple = (self.settings.compact_lists and
(self.compact_simple
or self.topic_class == 'contents'
or self.check_simple_list(node)))
if self.compact_simple and not old_compact_simple:
atts['class'] = 'simple'
self.body.append(self.starttag(node, 'ul', **atts))
def depart_bullet_list(self, node):
self.compact_simple, self.compact_p = self.context.pop()
self.body.append('</ul>\n')
def visit_caption(self, node):
self.body.append(self.starttag(node, 'p', '', CLASS='caption'))
def depart_caption(self, node):
self.body.append('</p>\n')
def visit_caution(self, node):
self.visit_admonition(node, 'caution')
def depart_caution(self, node):
self.depart_admonition()
def visit_citation(self, node):
self.body.append(self.starttag(node, 'table', CLASS='citation',
frame="void", rules="none"))
self.body.append('<colgroup><col class="label" /><col /></colgroup>\n'
'<tbody valign="top">\n'
'<tr>')
self.footnote_backrefs(node)
def depart_citation(self, node):
self.body.append('</td></tr>\n'
'</tbody>\n</table>\n')
def visit_citation_reference(self, node):
href = ''
if node.has_key('refid'):
href = '#' + node['refid']
elif node.has_key('refname'):
href = '#' + self.document.nameids[node['refname']]
self.body.append(self.starttag(node, 'a', '[', href=href,
CLASS='citation-reference'))
def depart_citation_reference(self, node):
self.body.append(']</a>')
def visit_classifier(self, node):
self.body.append(' <span class="classifier-delimiter">:</span> ')
self.body.append(self.starttag(node, 'span', '', CLASS='classifier'))
def depart_classifier(self, node):
self.body.append('</span>')
def visit_colspec(self, node):
self.colspecs.append(node)
def depart_colspec(self, node):
pass
def write_colspecs(self):
width = 0
for node in self.colspecs:
width += node['colwidth']
for node in self.colspecs:
colwidth = int(node['colwidth'] * 100.0 / width + 0.5)
self.body.append(self.emptytag(node, 'col',
width='%i%%' % colwidth))
self.colspecs = []
def visit_comment(self, node,
sub=re.compile('-(?=-)').sub):
"""Escape double-dashes in comment text."""
self.body.append('<!-- %s -->\n' % sub('- ', node.astext()))
# Content already processed:
raise nodes.SkipNode
def visit_contact(self, node):
self.visit_docinfo_item(node, 'contact', meta=None)
def depart_contact(self, node):
self.depart_docinfo_item()
def visit_copyright(self, node):
self.visit_docinfo_item(node, 'copyright')
def depart_copyright(self, node):
self.depart_docinfo_item()
def visit_danger(self, node):
self.visit_admonition(node, 'danger')
def depart_danger(self, node):
self.depart_admonition()
def visit_date(self, node):
self.visit_docinfo_item(node, 'date')
def depart_date(self, node):
self.depart_docinfo_item()
def visit_decoration(self, node):
pass
def depart_decoration(self, node):
pass
def visit_definition(self, node):
self.body.append('</dt>\n')
self.body.append(self.starttag(node, 'dd', ''))
if len(node):
node[0].set_class('first')
node[-1].set_class('last')
def depart_definition(self, node):
self.body.append('</dd>\n')
def visit_definition_list(self, node):
self.body.append(self.starttag(node, 'dl'))
def depart_definition_list(self, node):
self.body.append('</dl>\n')
def visit_definition_list_item(self, node):
pass
def depart_definition_list_item(self, node):
pass
def visit_description(self, node):
self.body.append(self.starttag(node, 'td', ''))
if len(node):
node[0].set_class('first')
node[-1].set_class('last')
def depart_description(self, node):
self.body.append('</td>')
def visit_docinfo(self, node):
self.context.append(len(self.body))
self.body.append(self.starttag(node, 'table', CLASS='docinfo',
frame="void", rules="none"))
self.body.append('<col class="docinfo-name" />\n'
'<col class="docinfo-content" />\n'
'<tbody valign="top">\n')
self.in_docinfo = 1
def depart_docinfo(self, node):
self.body.append('</tbody>\n</table>\n')
self.in_docinfo = None
start = self.context.pop()
self.docinfo = self.body[start:]
self.body = []
def visit_docinfo_item(self, node, name, meta=1):
if meta:
meta_tag = '<meta name="%s" content="%s" />\n' \
% (name, self.attval(node.astext()))
self.add_meta(meta_tag)
self.body.append(self.starttag(node, 'tr', ''))
self.body.append('<th class="docinfo-name">%s:</th>\n<td>'
% self.language.labels[name])
if len(node):
if isinstance(node[0], nodes.Element):
node[0].set_class('first')
if isinstance(node[-1], nodes.Element):
node[-1].set_class('last')
def depart_docinfo_item(self):
self.body.append('</td></tr>\n')
def visit_doctest_block(self, node):
self.body.append(self.starttag(node, 'pre', CLASS='doctest-block'))
def depart_doctest_block(self, node):
self.body.append('\n</pre>\n')
def visit_document(self, node):
# empty or untitled document?
if not len(node) or not isinstance(node[0], nodes.title):
# for XHTML conformance, modulo IE6 appeasement:
self.head.insert(0, '<title></title>\n')
def depart_document(self, node):
self.fragment.extend(self.body)
self.body.insert(0, self.starttag(node, 'div', CLASS='document'))
self.body.append('</div>\n')
def visit_emphasis(self, node):
self.body.append('<em>')
def depart_emphasis(self, node):
self.body.append('</em>')
def visit_entry(self, node):
if isinstance(node.parent.parent, nodes.thead):
tagname = 'th'
else:
tagname = 'td'
atts = {}
if node.has_key('morerows'):
atts['rowspan'] = node['morerows'] + 1
if node.has_key('morecols'):
atts['colspan'] = node['morecols'] + 1
self.body.append(self.starttag(node, tagname, '', **atts))
self.context.append('</%s>\n' % tagname.lower())
if len(node) == 0: # empty cell
self.body.append('&nbsp;')
else:
node[0].set_class('first')
node[-1].set_class('last')
def depart_entry(self, node):
self.body.append(self.context.pop())
def visit_enumerated_list(self, node):
"""
The 'start' attribute does not conform to HTML 4.01's strict.dtd, but
CSS1 doesn't help. CSS2 isn't widely enough supported yet to be
usable.
"""
atts = {}
if node.has_key('start'):
atts['start'] = node['start']
if node.has_key('enumtype'):
atts['class'] = node['enumtype']
# @@@ To do: prefix, suffix. How? Change prefix/suffix to a
# single "format" attribute? Use CSS2?
old_compact_simple = self.compact_simple
self.context.append((self.compact_simple, self.compact_p))
self.compact_p = None
self.compact_simple = (self.settings.compact_lists and
(self.compact_simple
or self.topic_class == 'contents'
or self.check_simple_list(node)))
if self.compact_simple and not old_compact_simple:
atts['class'] = (atts.get('class', '') + ' simple').strip()
self.body.append(self.starttag(node, 'ol', **atts))
def depart_enumerated_list(self, node):
self.compact_simple, self.compact_p = self.context.pop()
self.body.append('</ol>\n')
def visit_error(self, node):
self.visit_admonition(node, 'error')
def depart_error(self, node):
self.depart_admonition()
def visit_field(self, node):
self.body.append(self.starttag(node, 'tr', '', CLASS='field'))
def depart_field(self, node):
self.body.append('</tr>\n')
def visit_field_body(self, node):
self.body.append(self.starttag(node, 'td', '', CLASS='field-body'))
if len(node):
node[0].set_class('first')
node[-1].set_class('last')
def depart_field_body(self, node):
self.body.append('</td>\n')
def visit_field_list(self, node):
self.body.append(self.starttag(node, 'table', frame='void',
rules='none', CLASS='field-list'))
self.body.append('<col class="field-name" />\n'
'<col class="field-body" />\n'
'<tbody valign="top">\n')
def depart_field_list(self, node):
self.body.append('</tbody>\n</table>\n')
def visit_field_name(self, node):
atts = {}
if self.in_docinfo:
atts['class'] = 'docinfo-name'
else:
atts['class'] = 'field-name'
if len(node.astext()) > 14:
atts['colspan'] = 2
self.context.append('</tr>\n<tr><td>&nbsp;</td>')
else:
self.context.append('')
self.body.append(self.starttag(node, 'th', '', **atts))
def depart_field_name(self, node):
self.body.append(':</th>')
self.body.append(self.context.pop())
def visit_figure(self, node):
atts = {'class': 'figure'}
if node.get('width'):
atts['style'] = 'width: %spx' % node['width']
self.body.append(self.starttag(node, 'div', **atts))
def depart_figure(self, node):
self.body.append('</div>\n')
def visit_footer(self, node):
self.context.append(len(self.body))
def depart_footer(self, node):
start = self.context.pop()
footer = (['<hr class="footer" />\n',
self.starttag(node, 'div', CLASS='footer')]
+ self.body[start:] + ['</div>\n'])
self.footer.extend(footer)
self.body_suffix[:0] = footer
del self.body[start:]
def visit_footnote(self, node):
self.body.append(self.starttag(node, 'table', CLASS='footnote',
frame="void", rules="none"))
self.body.append('<colgroup><col class="label" /><col /></colgroup>\n'
'<tbody valign="top">\n'
'<tr>')
self.footnote_backrefs(node)
def footnote_backrefs(self, node):
if self.settings.footnote_backlinks and node.hasattr('backrefs'):
backrefs = node['backrefs']
if len(backrefs) == 1:
self.context.append('')
self.context.append('<a class="fn-backref" href="#%s" '
'name="%s">' % (backrefs[0], node['id']))
else:
i = 1
backlinks = []
for backref in backrefs:
backlinks.append('<a class="fn-backref" href="#%s">%s</a>'
% (backref, i))
i += 1
self.context.append('<em>(%s)</em> ' % ', '.join(backlinks))
self.context.append('<a name="%s">' % node['id'])
else:
self.context.append('')
self.context.append('<a name="%s">' % node['id'])
def depart_footnote(self, node):
self.body.append('</td></tr>\n'
'</tbody>\n</table>\n')
def visit_footnote_reference(self, node):
href = ''
if node.has_key('refid'):
href = '#' + node['refid']
elif node.has_key('refname'):
href = '#' + self.document.nameids[node['refname']]
format = self.settings.footnote_references
if format == 'brackets':
suffix = '['
self.context.append(']')
elif format == 'superscript':
suffix = '<sup>'
self.context.append('</sup>')
else: # shouldn't happen
suffix = '???'
self.content.append('???')
self.body.append(self.starttag(node, 'a', suffix, href=href,
CLASS='footnote-reference'))
def depart_footnote_reference(self, node):
self.body.append(self.context.pop() + '</a>')
def visit_generated(self, node):
pass
def depart_generated(self, node):
pass
def visit_header(self, node):
self.context.append(len(self.body))
def depart_header(self, node):
start = self.context.pop()
header = [self.starttag(node, 'div', CLASS='header')]
header.extend(self.body[start:])
header.append('<hr />\n</div>\n')
self.body_prefix.extend(header)
self.header = header
del self.body[start:]
def visit_hint(self, node):
self.visit_admonition(node, 'hint')
def depart_hint(self, node):
self.depart_admonition()
def visit_image(self, node):
atts = node.attributes.copy()
if atts.has_key('class'):
del atts['class'] # prevent duplication with node attrs
atts['src'] = atts['uri']
del atts['uri']
if atts.has_key('scale'):
if Image and not (atts.has_key('width')
and atts.has_key('height')):
try:
im = Image.open(str(atts['src']))
except (IOError, # Source image can't be found or opened
UnicodeError): # PIL doesn't like Unicode paths.
pass
else:
if not atts.has_key('width'):
atts['width'] = im.size[0]
if not atts.has_key('height'):
atts['height'] = im.size[1]
del im
if atts.has_key('width'):
atts['width'] = int(round(atts['width']
* (float(atts['scale']) / 100)))
if atts.has_key('height'):
atts['height'] = int(round(atts['height']
* (float(atts['scale']) / 100)))
del atts['scale']
if not atts.has_key('alt'):
atts['alt'] = atts['src']
if isinstance(node.parent, nodes.TextElement):
self.context.append('')
else:
if atts.has_key('align'):
self.body.append('<p align="%s">' %
(self.attval(atts['align'],)))
else:
self.body.append('<p>')
self.context.append('</p>\n')
self.body.append(self.emptytag(node, 'img', '', **atts))
def depart_image(self, node):
self.body.append(self.context.pop())
def visit_important(self, node):
self.visit_admonition(node, 'important')
def depart_important(self, node):
self.depart_admonition()
def visit_inline(self, node):
self.body.append(self.starttag(node, 'span', ''))
def depart_inline(self, node):
self.body.append('</span>')
def visit_label(self, node):
self.body.append(self.starttag(node, 'td', '%s[' % self.context.pop(),
CLASS='label'))
def depart_label(self, node):
self.body.append(']</a></td><td>%s' % self.context.pop())
def visit_legend(self, node):
self.body.append(self.starttag(node, 'div', CLASS='legend'))
def depart_legend(self, node):
self.body.append('</div>\n')
def visit_line_block(self, node):
self.body.append(self.starttag(node, 'pre', CLASS='line-block'))
def depart_line_block(self, node):
self.body.append('\n</pre>\n')
def visit_list_item(self, node):
self.body.append(self.starttag(node, 'li', ''))
if len(node):
node[0].set_class('first')
def depart_list_item(self, node):
self.body.append('</li>\n')
def visit_literal(self, node):
"""Process text to prevent tokens from wrapping."""
self.body.append(self.starttag(node, 'tt', '', CLASS='literal'))
text = node.astext()
for token in self.words_and_spaces.findall(text):
if token.strip():
# Protect text like "--an-option" from bad line wrapping:
self.body.append('<span class="pre">%s</span>'
% self.encode(token))
elif token in ('\n', ' '):
# Allow breaks at whitespace:
self.body.append(token)
else:
# Protect runs of multiple spaces; the last space can wrap:
self.body.append('&nbsp;' * (len(token) - 1) + ' ')
self.body.append('</tt>')
# Content already processed:
raise nodes.SkipNode
def visit_literal_block(self, node):
self.body.append(self.starttag(node, 'pre', CLASS='literal-block'))
def depart_literal_block(self, node):
self.body.append('\n</pre>\n')
def visit_meta(self, node):
meta = self.emptytag(node, 'meta', **node.attributes)
self.add_meta(meta)
def depart_meta(self, node):
pass
def add_meta(self, tag):
self.meta.append(tag)
self.head.append(tag)
def visit_note(self, node):
self.visit_admonition(node, 'note')
def depart_note(self, node):
self.depart_admonition()
def visit_option(self, node):
if self.context[-1]:
self.body.append(', ')
def depart_option(self, node):
self.context[-1] += 1
def visit_option_argument(self, node):
self.body.append(node.get('delimiter', ' '))
self.body.append(self.starttag(node, 'var', ''))
def depart_option_argument(self, node):
self.body.append('</var>')
def visit_option_group(self, node):
atts = {}
if len(node.astext()) > 14:
atts['colspan'] = 2
self.context.append('</tr>\n<tr><td>&nbsp;</td>')
else:
self.context.append('')
self.body.append(self.starttag(node, 'td', **atts))
self.body.append('<kbd>')
self.context.append(0) # count number of options
def depart_option_group(self, node):
self.context.pop()
self.body.append('</kbd></td>\n')
self.body.append(self.context.pop())
def visit_option_list(self, node):
self.body.append(
self.starttag(node, 'table', CLASS='option-list',
frame="void", rules="none"))
self.body.append('<col class="option" />\n'
'<col class="description" />\n'
'<tbody valign="top">\n')
def depart_option_list(self, node):
self.body.append('</tbody>\n</table>\n')
def visit_option_list_item(self, node):
self.body.append(self.starttag(node, 'tr', ''))
def depart_option_list_item(self, node):
self.body.append('</tr>\n')
def visit_option_string(self, node):
self.body.append(self.starttag(node, 'span', '', CLASS='option'))
def depart_option_string(self, node):
self.body.append('</span>')
def visit_organization(self, node):
self.visit_docinfo_item(node, 'organization')
def depart_organization(self, node):
self.depart_docinfo_item()
def visit_paragraph(self, node):
# Omit <p> tags if this is an only child and optimizable.
if (self.compact_simple or
self.compact_p and (len(node.parent) == 1 or
len(node.parent) == 2 and
isinstance(node.parent[0], nodes.label))):
self.context.append('')
else:
self.body.append(self.starttag(node, 'p', ''))
self.context.append('</p>\n')
def depart_paragraph(self, node):
self.body.append(self.context.pop())
def visit_problematic(self, node):
if node.hasattr('refid'):
self.body.append('<a href="#%s" name="%s">' % (node['refid'],
node['id']))
self.context.append('</a>')
else:
self.context.append('')
self.body.append(self.starttag(node, 'span', '', CLASS='problematic'))
def depart_problematic(self, node):
self.body.append('</span>')
self.body.append(self.context.pop())
def visit_raw(self, node):
if node.get('format') == 'html':
self.body.append(node.astext())
# Keep non-HTML raw text out of output:
raise nodes.SkipNode
def visit_reference(self, node):
if isinstance(node.parent, nodes.TextElement):
self.context.append('')
else:
self.body.append('<p>')
self.context.append('</p>\n')
if node.has_key('refuri'):
href = node['refuri']
elif node.has_key('refid'):
href = '#' + node['refid']
elif node.has_key('refname'):
href = '#' + self.document.nameids[node['refname']]
self.body.append(self.starttag(node, 'a', '', href=href,
CLASS='reference'))
def depart_reference(self, node):
self.body.append('</a>')
self.body.append(self.context.pop())
def visit_revision(self, node):
self.visit_docinfo_item(node, 'revision', meta=None)
def depart_revision(self, node):
self.depart_docinfo_item()
def visit_row(self, node):
self.body.append(self.starttag(node, 'tr', ''))
def depart_row(self, node):
self.body.append('</tr>\n')
def visit_rubric(self, node):
self.body.append(self.starttag(node, 'p', '', CLASS='rubric'))
def depart_rubric(self, node):
self.body.append('</p>\n')
def visit_section(self, node):
self.section_level += 1
self.body.append(self.starttag(node, 'div', CLASS='section'))
def depart_section(self, node):
self.section_level -= 1
self.body.append('</div>\n')
def visit_sidebar(self, node):
self.body.append(self.starttag(node, 'div', CLASS='sidebar'))
self.in_sidebar = 1
def depart_sidebar(self, node):
self.body.append('</div>\n')
self.in_sidebar = None
def visit_status(self, node):
self.visit_docinfo_item(node, 'status', meta=None)
def depart_status(self, node):
self.depart_docinfo_item()
def visit_strong(self, node):
self.body.append('<strong>')
def depart_strong(self, node):
self.body.append('</strong>')
def visit_subscript(self, node):
self.body.append(self.starttag(node, 'sub', ''))
def depart_subscript(self, node):
self.body.append('</sub>')
def visit_substitution_definition(self, node):
"""Internal only."""
raise nodes.SkipNode
def visit_substitution_reference(self, node):
self.unimplemented_visit(node)
def visit_subtitle(self, node):
if isinstance(node.parent, nodes.sidebar):
self.body.append(self.starttag(node, 'p', '',
CLASS='sidebar-subtitle'))
self.context.append('</p>\n')
elif isinstance(node.parent, nodes.document):
self.body.append(self.starttag(node, 'h2', '', CLASS='subtitle'))
self.context.append('</h2>\n')
self.in_document_title = len(self.body)
def depart_subtitle(self, node):
self.body.append(self.context.pop())
if self.in_document_title:
self.subtitle = self.body[self.in_document_title:-1]
self.in_document_title = 0
self.body_pre_docinfo.extend(self.body)
del self.body[:]
def visit_superscript(self, node):
self.body.append(self.starttag(node, 'sup', ''))
def depart_superscript(self, node):
self.body.append('</sup>')
def visit_system_message(self, node):
if node['level'] < self.document.reporter['writer'].report_level:
# Level is too low to display:
raise nodes.SkipNode
self.body.append(self.starttag(node, 'div', CLASS='system-message'))
self.body.append('<p class="system-message-title">')
attr = {}
backref_text = ''
if node.hasattr('id'):
attr['name'] = node['id']
if node.hasattr('backrefs'):
backrefs = node['backrefs']
if len(backrefs) == 1:
backref_text = ('; <em><a href="#%s">backlink</a></em>'
% backrefs[0])
else:
i = 1
backlinks = []
for backref in backrefs:
backlinks.append('<a href="#%s">%s</a>' % (backref, i))
i += 1
backref_text = ('; <em>backlinks: %s</em>'
% ', '.join(backlinks))
if node.hasattr('line'):
line = ', line %s' % node['line']
else:
line = ''
if attr:
a_start = self.starttag({}, 'a', '', **attr)
a_end = '</a>'
else:
a_start = a_end = ''
self.body.append('System Message: %s%s/%s%s (<tt>%s</tt>%s)%s</p>\n'
% (a_start, node['type'], node['level'], a_end,
self.encode(node['source']), line, backref_text))
def depart_system_message(self, node):
self.body.append('</div>\n')
def visit_table(self, node):
self.body.append(
# "border=None" is a boolean attribute;
# it means "standard border", not "no border":
self.starttag(node, 'table', CLASS="table", border="1"))
def depart_table(self, node):
self.body.append('</table>\n')
def visit_target(self, node):
if not (node.has_key('refuri') or node.has_key('refid')
or node.has_key('refname')):
self.body.append(self.starttag(node, 'a', '', CLASS='target'))
self.context.append('</a>')
else:
self.context.append('')
def depart_target(self, node):
self.body.append(self.context.pop())
def visit_tbody(self, node):
self.write_colspecs()
self.body.append(self.context.pop()) # '</colgroup>\n' or ''
self.body.append(self.starttag(node, 'tbody', valign='top'))
def depart_tbody(self, node):
self.body.append('</tbody>\n')
def visit_term(self, node):
self.body.append(self.starttag(node, 'dt', ''))
def depart_term(self, node):
"""
Leave the end tag to `self.visit_definition()`, in case there's a
classifier.
"""
pass
def visit_tgroup(self, node):
# Mozilla needs <colgroup>:
self.body.append(self.starttag(node, 'colgroup'))
# Appended by thead or tbody:
self.context.append('</colgroup>\n')
def depart_tgroup(self, node):
pass
def visit_thead(self, node):
self.write_colspecs()
self.body.append(self.context.pop()) # '</colgroup>\n'
# There may or may not be a <thead>; this is for <tbody> to use:
self.context.append('')
self.body.append(self.starttag(node, 'thead', valign='bottom'))
def depart_thead(self, node):
self.body.append('</thead>\n')
def visit_tip(self, node):
self.visit_admonition(node, 'tip')
def depart_tip(self, node):
self.depart_admonition()
def visit_title(self, node):
"""Only 6 section levels are supported by HTML."""
check_id = 0
close_tag = '</p>\n'
if isinstance(node.parent, nodes.topic):
self.body.append(
self.starttag(node, 'p', '', CLASS='topic-title first'))
check_id = 1
elif isinstance(node.parent, nodes.sidebar):
self.body.append(
self.starttag(node, 'p', '', CLASS='sidebar-title first'))
check_id = 1
elif isinstance(node.parent, nodes.admonition):
self.body.append(
self.starttag(node, 'p', '', CLASS='admonition-title first'))
check_id = 1
elif isinstance(node.parent, nodes.table):
self.body.append(
self.starttag(node, 'caption', ''))
check_id = 1
close_tag = '</caption>\n'
elif self.section_level == 0:
# document title
self.head.append('<title>%s</title>\n'
% self.encode(node.astext()))
self.body.append(self.starttag(node, 'h1', '', CLASS='title'))
self.context.append('</h1>\n')
self.in_document_title = len(self.body)
else:
h_level = self.section_level + self.initial_header_level - 1
self.body.append(
self.starttag(node, 'h%s' % h_level, ''))
atts = {}
if node.parent.hasattr('id'):
atts['name'] = node.parent['id']
if node.hasattr('refid'):
atts['class'] = 'toc-backref'
atts['href'] = '#' + node['refid']
self.body.append(self.starttag({}, 'a', '', **atts))
self.context.append('</a></h%s>\n' % (h_level))
if check_id:
if node.parent.hasattr('id'):
self.body.append(
self.starttag({}, 'a', '', name=node.parent['id']))
self.context.append('</a>' + close_tag)
else:
self.context.append(close_tag)
def depart_title(self, node):
self.body.append(self.context.pop())
if self.in_document_title:
self.title = self.body[self.in_document_title:-1]
self.in_document_title = 0
self.body_pre_docinfo.extend(self.body)
del self.body[:]
def visit_title_reference(self, node):
self.body.append(self.starttag(node, 'cite', ''))
def depart_title_reference(self, node):
self.body.append('</cite>')
def visit_topic(self, node):
self.body.append(self.starttag(node, 'div', CLASS='topic'))
self.topic_class = node.get('class')
def depart_topic(self, node):
self.body.append('</div>\n')
self.topic_class = ''
def visit_transition(self, node):
self.body.append(self.emptytag(node, 'hr'))
def depart_transition(self, node):
pass
def visit_version(self, node):
self.visit_docinfo_item(node, 'version', meta=None)
def depart_version(self, node):
self.depart_docinfo_item()
def visit_warning(self, node):
self.visit_admonition(node, 'warning')
def depart_warning(self, node):
self.depart_admonition()
def unimplemented_visit(self, node):
raise NotImplementedError('visiting unimplemented node type: %s'
% node.__class__.__name__)
class SimpleListChecker(nodes.GenericNodeVisitor):
"""
Raise `nodes.SkipNode` if non-simple list item is encountered.
Here "simple" means a list item containing nothing other than a single
paragraph, a simple list, or a paragraph followed by a simple list.
"""
def default_visit(self, node):
raise nodes.NodeFound
def visit_bullet_list(self, node):
pass
def visit_enumerated_list(self, node):
pass
def visit_list_item(self, node):
children = []
for child in node.get_children():
if not isinstance(child, nodes.Invisible):
children.append(child)
if (children and isinstance(children[0], nodes.paragraph)
and (isinstance(children[-1], nodes.bullet_list)
or isinstance(children[-1], nodes.enumerated_list))):
children.pop()
if len(children) <= 1:
return
else:
raise nodes.NodeFound
def visit_paragraph(self, node):
raise nodes.SkipNode
def invisible_visit(self, node):
"""Invisible nodes should be ignored."""
pass
visit_comment = invisible_visit
visit_substitution_definition = invisible_visit
visit_target = invisible_visit
visit_pending = invisible_visit
"""
:Author: Engelbert Gruber
:Contact: grubert@users.sourceforge.net
:Revision: $Revision: 1.1.2.3.8.1 $
:Date: $Date: 2004/05/12 19:57:57 $
:Copyright: This module has been placed in the public domain.
LaTeX2e document tree Writer.
"""
__docformat__ = 'reStructuredText'
# code contributions from several people included, thanks too all.
# some named: David Abrahams, Julien Letessier, Lele Gaifax, and others.
#
# convention deactivate code by two # e.g. ##.
import sys
import time
import re
import string
from types import ListType
from docutils import frontend, nodes, languages, writers
class Writer(writers.Writer):
supported = ('latex','latex2e')
"""Formats this writer supports."""
settings_spec = (
'LaTeX-Specific Options',
'The LaTeX "--output-encoding" default is "latin-1:strict".',
(('Specify documentclass. Default is "article".',
['--documentclass'],
{'default': 'article', }),
('Specify document options. Multiple options can be given, '
'separated by commas. Default is "10pt,a4paper".',
['--documentoptions'],
{'default': '10pt,a4paper', }),
('Use LaTeX footnotes. LaTeX supports only numbered footnotes (does it?). '
'Default: no, uses figures.',
['--use-latex-footnotes'],
{'default': 0, 'action': 'store_true',
'validator': frontend.validate_boolean}),
('Format for footnote references: one of "superscript" or '
'"brackets". Default is "brackets".',
['--footnote-references'],
{'choices': ['superscript', 'brackets'], 'default': 'brackets',
'metavar': '<format>'}),
('Use LaTeX citations. '
'Default: no, uses figures which might get mixed with images.',
['--use-latex-citations'],
{'default': 0, 'action': 'store_true',
'validator': frontend.validate_boolean}),
('Format for block quote attributions: one of "dash" (em-dash '
'prefix), "parentheses"/"parens", or "none". Default is "dash".',
['--attribution'],
{'choices': ['dash', 'parentheses', 'parens', 'none'],
'default': 'dash', 'metavar': '<format>'}),
('Specify a stylesheet file. The file will be "input" by latex in '
'the document header. Default is no stylesheet (""). '
'Overridden by --stylesheet-path.',
['--stylesheet'],
{'default': '', 'metavar': '<file>'}),
('Specify a stylesheet file, relative to the current working '
'directory. Overrides --stylesheet.',
['--stylesheet-path'],
{'metavar': '<file>'}),
('Link to the stylesheet in the output LaTeX file. This is the '
'default.',
['--link-stylesheet'],
{'dest': 'embed_stylesheet', 'action': 'store_false',
'validator': frontend.validate_boolean}),
('Embed the stylesheet in the output LaTeX file. The stylesheet '
'file must be accessible during processing (--stylesheet-path is '
'recommended).',
['--embed-stylesheet'], {'action': 'store_true'}),
('Table of contents by docutils (default) or latex. Latex (writer) '
'supports only one ToC per document, but docutils does not write '
'pagenumbers.',
['--use-latex-toc'],
{'default': 0, 'action': 'store_true',
'validator': frontend.validate_boolean}),
('Let LaTeX print author and date, do not show it in docutils '
'document info.',
['--use-latex-docinfo'],
{'default': 0, 'action': 'store_true',
'validator': frontend.validate_boolean}),
('Color of any hyperlinks embedded in text '
'(default: "blue", "0" to disable).',
['--hyperlink-color'], {'default': 'blue'}),
('Enable compound enumerators for nested enumerated lists '
'(e.g. "1.2.a.ii"). Default: disabled.',
['--compound-enumerators'],
{'default': None, 'action': 'store_true',
'validator': frontend.validate_boolean}),
('Disable compound enumerators for nested enumerated lists. This is '
'the default.',
['--no-compound-enumerators'],
{'action': 'store_false', 'dest': 'compound_enumerators'}),
('Enable section ("." subsection ...) prefixes for compound '
'enumerators. This has no effect without --compound-enumerators. '
'Default: disabled.',
['--section-prefix-for-enumerators'],
{'default': None, 'action': 'store_true',
'validator': frontend.validate_boolean}),
('Disable section prefixes for compound enumerators. '
'This is the default.',
['--no-section-prefix-for-enumerators'],
{'action': 'store_false', 'dest': 'section_prefix_for_enumerators'}),
('Set the separator between section number and enumerator '
'for compound enumerated lists. Default is "-".',
['--section-enumerator-separator'],
{'default': '-', 'metavar': '<char>'}),
('When possibile, use verbatim for literal-blocks.'
'Default is to always use the mbox environment.',
['--use-verbatim-when-possible'],
{'default': 0, 'action': 'store_true',
'validator': frontend.validate_boolean}),
('Table style. "standard" with horizontal and vertical lines, '
'"booktabs" (LaTeX booktabs style) only horizontal lines '
'above and below the table and below the header or "nolines".'
'default: "standard"',
['--table-style'],
{'choices': ['standard', 'booktabs','nolines'], 'default': 'standard',
'metavar': '<format>'}),
('LaTeX graphicx package option.'
'Possible values are "dvips", "pdftex". "auto" includes LaTeX code '
'to use "pdftex" if processing with pdf(la)tex and dvips otherwise. '
'Default is no option.',
['--graphicx-option'],
{'default': ''}),
),)
settings_defaults = {'output_encoding': 'latin-1'}
config_section = 'latex2e writer'
config_section_dependencies = ('writers',)
output = None
"""Final translated form of `document`."""
def translate(self):
visitor = LaTeXTranslator(self.document)
self.document.walkabout(visitor)
self.output = visitor.astext()
self.head_prefix = visitor.head_prefix
self.head = visitor.head
self.body_prefix = visitor.body_prefix
self.body = visitor.body
self.body_suffix = visitor.body_suffix
"""
Notes on LaTeX
--------------
* latex does not support multiple tocs in one document.
(might be no limitation except for docutils documentation)
* width
* linewidth - width of a line in the local environment
* textwidth - the width of text on the page
Maybe always use linewidth ?
*Bug* inside a minipage a (e.g. Sidebar) the linewidth is
not changed, needs fix in docutils so that tables
are not too wide.
So we add locallinewidth set it initially and
on entering sidebar and reset on exit.
"""
class Babel:
"""Language specifics for LaTeX."""
# country code by a.schlock.
# partly manually converted from iso and babel stuff, dialects and some
_ISO639_TO_BABEL = {
'no': 'norsk', #XXX added by hand ( forget about nynorsk?)
'gd': 'scottish', #XXX added by hand
'hu': 'magyar', #XXX added by hand
'pt': 'portuguese',#XXX added by hand
'sl': 'slovenian',
'af': 'afrikaans',
'bg': 'bulgarian',
'br': 'breton',
'ca': 'catalan',
'cs': 'czech',
'cy': 'welsh',
'da': 'danish',
'fr': 'french',
# french, francais, canadien, acadian
'de': 'ngerman', #XXX rather than german
# ngerman, naustrian, german, germanb, austrian
'el': 'greek',
'en': 'english',
# english, USenglish, american, UKenglish, british, canadian
'eo': 'esperanto',
'es': 'spanish',
'et': 'estonian',
'eu': 'basque',
'fi': 'finnish',
'ga': 'irish',
'gl': 'galician',
'he': 'hebrew',
'hr': 'croatian',
'hu': 'hungarian',
'is': 'icelandic',
'it': 'italian',
'la': 'latin',
'nl': 'dutch',
'pl': 'polish',
'pt': 'portuguese',
'ro': 'romanian',
'ru': 'russian',
'sk': 'slovak',
'sr': 'serbian',
'sv': 'swedish',
'tr': 'turkish',
'uk': 'ukrainian'
}
def __init__(self,lang):
self.language = lang
# pdflatex does not produce double quotes for ngerman in tt.
self.double_quote_replacment = None
if re.search('^de',self.language):
# maybe use: {\glqq} {\grqq}.
self.quotes = ("\"`", "\"'")
self.double_quote_replacment = "{\\dq}"
else:
self.quotes = ("``", "''")
self.quote_index = 0
def next_quote(self):
q = self.quotes[self.quote_index]
self.quote_index = (self.quote_index+1)%2
return q
def quote_quotes(self,text):
t = None
for part in text.split('"'):
if t == None:
t = part
else:
t += self.next_quote() + part
return t
def double_quotes_in_tt (self,text):
if not self.double_quote_replacment:
return text
return text.replace('"', self.double_quote_replacment)
def get_language(self):
if self._ISO639_TO_BABEL.has_key(self.language):
return self._ISO639_TO_BABEL[self.language]
else:
# support dialects.
l = self.language.split("_")[0]
if self._ISO639_TO_BABEL.has_key(l):
return self._ISO639_TO_BABEL[l]
return None
latex_headings = {
'optionlist_environment' : [
'\\newcommand{\\optionlistlabel}[1]{\\bf #1 \\hfill}\n'
'\\newenvironment{optionlist}[1]\n'
'{\\begin{list}{}\n'
' {\\setlength{\\labelwidth}{#1}\n'
' \\setlength{\\rightmargin}{1cm}\n'
' \\setlength{\\leftmargin}{\\rightmargin}\n'
' \\addtolength{\\leftmargin}{\\labelwidth}\n'
' \\addtolength{\\leftmargin}{\\labelsep}\n'
' \\renewcommand{\\makelabel}{\\optionlistlabel}}\n'
'}{\\end{list}}\n',
],
'footnote_floats' : [
'% begin: floats for footnotes tweaking.\n',
'\\setlength{\\floatsep}{0.5em}\n',
'\\setlength{\\textfloatsep}{\\fill}\n',
'\\addtolength{\\textfloatsep}{3em}\n',
'\\renewcommand{\\textfraction}{0.5}\n',
'\\renewcommand{\\topfraction}{0.5}\n',
'\\renewcommand{\\bottomfraction}{0.5}\n',
'\\setcounter{totalnumber}{50}\n',
'\\setcounter{topnumber}{50}\n',
'\\setcounter{bottomnumber}{50}\n',
'% end floats for footnotes\n',
],
'some_commands' : [
'% some commands, that could be overwritten in the style file.\n'
'\\newcommand{\\rubric}[1]'
'{\\subsection*{~\\hfill {\\it #1} \\hfill ~}}\n'
'\\newcommand{\\titlereference}[1]{\\textsl{#1}}\n'
'% end of "some commands"\n',
]
}
class DocumentClass:
"""Details of a LaTeX document class."""
# BUG: LaTeX has no deeper sections (actually paragrah is no
# section either).
# BUG: No support for unknown document classes. Make 'article'
# default?
_class_sections = {
'book': ( 'chapter', 'section', 'subsection', 'subsubsection' ),
'scrbook': ( 'chapter', 'section', 'subsection', 'subsubsection' ),
'report': ( 'chapter', 'section', 'subsection', 'subsubsection' ),
'scrreprt': ( 'chapter', 'section', 'subsection', 'subsubsection' ),
'article': ( 'section', 'subsection', 'subsubsection' ),
'scrartcl': ( 'section', 'subsection', 'subsubsection' ),
}
_deepest_section = 'subsubsection'
def __init__(self, document_class):
self.document_class = document_class
def section(self, level):
""" Return the section name at the given level for the specific
document class.
Level is 1,2,3..., as level 0 is the title."""
sections = self._class_sections[self.document_class]
if level <= len(sections):
return sections[level-1]
else:
return self._deepest_section
class Table:
""" Manage a table while traversing.
Maybe change to a mixin defining the visit/departs, but then
class Table internal variables are in the Translator.
"""
def __init__(self,latex_type,table_style):
self._latex_type = latex_type
self._table_style = table_style
self._open = 0
# miscellaneous attributes
self._attrs = {}
self._col_width = []
self._rowspan = []
def open(self):
self._open = 1
self._col_specs = []
self.caption = None
self._attrs = {}
self._in_head = 0 # maybe context with search
def close(self):
self._open = 0
self._col_specs = None
self.caption = None
self._attrs = {}
def is_open(self):
return self._open
def used_packages(self):
if self._table_style == 'booktabs':
return '\\usepackage{booktabs}\n'
return ''
def is_open(self):
return self._open
def get_latex_type(self):
return self._latex_type
def set(self,attr,value):
self._attrs[attr] = value
def get(self,attr):
if self._attrs.has_key(attr):
return self._attrs[attr]
return None
def get_vertical_bar(self):
if self._table_style == 'standard':
return '|'
return ''
# horizontal lines are drawn below a row, because we.
def get_opening(self):
return '\\begin{%s}[c]' % self._latex_type
def get_closing(self):
line = ""
if self._table_style == 'booktabs':
line = '\\bottomrule\n'
elif self._table_style == 'standard':
lines = '\\hline\n'
return '%s\\end{%s}' % (line,self._latex_type)
def visit_colspec(self,node):
self._col_specs.append(node)
def get_colspecs(self):
"""
Return column specification for longtable.
Assumes reST line length being 80 characters.
Table width is hairy.
=== ===
ABC DEF
=== ===
usually gets to narrow, therefore we add 1 (fiddlefactor).
"""
width = 80
total_width = 0.0
# first see if we get too wide.
for node in self._col_specs:
colwidth = float(node['colwidth']+1) / width
total_width += colwidth
self._col_width = []
self._rowspan = []
# donot make it full linewidth
factor = 0.93
if total_width > 1.0:
factor /= total_width
bar = self.get_vertical_bar()
latex_table_spec = ""
for node in self._col_specs:
colwidth = factor * float(node['colwidth']+1) / width
self._col_width.append(colwidth+0.005)
self._rowspan.append(0)
latex_table_spec += "%sp{%.2f\\locallinewidth}" % (bar,colwidth+0.005)
return latex_table_spec+bar
def get_column_width(self):
""" return columnwidth for current cell (not multicell)
"""
return "%.2f\\locallinewidth" % self._col_width[self._cell_in_row-1]
def visit_thead(self):
self._in_thead = 1
if self._table_style == 'standard':
return ['\\hline\n']
elif self._table_style == 'booktabs':
return ['\\toprule\n']
return []
def depart_thead(self):
a = []
#if self._table_style == 'standard':
# a.append('\\hline\n')
if self._table_style == 'booktabs':
a.append('\\midrule\n')
a.append('\\endhead\n')
# for longtable one could add firsthead, foot and lastfoot
self._in_thead = 0
return a
def visit_row(self):
self._cell_in_row = 0
def depart_row(self):
res = [' \\\\\n']
self._cell_in_row = None # remove cell counter
for i in range(len(self._rowspan)):
if (self._rowspan[i]>0):
self._rowspan[i] -= 1
if self._table_style == 'standard':
rowspans = []
for i in range(len(self._rowspan)):
if (self._rowspan[i]<=0):
rowspans.append(i+1)
if len(rowspans)==len(self._rowspan):
res.append('\\hline\n')
else:
cline = ''
rowspans.reverse()
# TODO merge clines
while 1:
try:
c_start = rowspans.pop()
except:
break
cline += '\\cline{%d-%d}\n' % (c_start,c_start)
res.append(cline)
return res
def set_rowspan(self,cell,value):
try:
self._rowspan[cell] = value
except:
pass
def get_rowspan(self,cell):
try:
return self._rowspan[cell]
except:
return 0
def get_entry_number(self):
return self._cell_in_row
def visit_entry(self):
self._cell_in_row += 1
class LaTeXTranslator(nodes.NodeVisitor):
# When options are given to the documentclass, latex will pass them
# to other packages, as done with babel.
# Dummy settings might be taken from document settings
latex_head = '\\documentclass[%s]{%s}\n'
encoding = '\\usepackage[%s]{inputenc}\n'
linking = '\\usepackage[colorlinks=%s,linkcolor=%s,urlcolor=%s]{hyperref}\n'
stylesheet = '\\input{%s}\n'
# add a generated on day , machine by user using docutils version.
generator = '%% generator Docutils: http://docutils.sourceforge.net/\n'
# use latex tableofcontents or let docutils do it.
use_latex_toc = 0
# TODO: use mixins for different implementations.
# list environment for option-list. else tabularx
use_optionlist_for_option_list = 1
# list environment for docinfo. else tabularx
use_optionlist_for_docinfo = 0 # NOT YET IN USE
# Use compound enumerations (1.A.1.)
compound_enumerators = 0
# If using compound enumerations, include section information.
section_prefix_for_enumerators = 0
# This is the character that separates the section ("." subsection ...)
# prefix from the regular list enumerator.
section_enumerator_separator = '-'
# default link color
hyperlink_color = "blue"
def __init__(self, document):
nodes.NodeVisitor.__init__(self, document)
self.settings = settings = document.settings
self.use_latex_toc = settings.use_latex_toc
self.use_latex_docinfo = settings.use_latex_docinfo
self.use_latex_footnotes = settings.use_latex_footnotes
self._use_latex_citations = settings.use_latex_citations
self.hyperlink_color = settings.hyperlink_color
self.compound_enumerators = settings.compound_enumerators
self.fontenc = ''
self.section_prefix_for_enumerators = (
settings.section_prefix_for_enumerators)
self.section_enumerator_separator = (
settings.section_enumerator_separator.replace('_', '\\_'))
if self.hyperlink_color == '0':
self.hyperlink_color = 'black'
self.colorlinks = 'false'
else:
self.colorlinks = 'true'
# language: labels, bibliographic_fields, and author_separators.
# to allow writing labes for specific languages.
self.language = languages.get_language(settings.language_code)
self.babel = Babel(settings.language_code)
self.author_separator = self.language.author_separators[0]
self.d_options = self.settings.documentoptions
if self.babel.get_language():
self.d_options += ',%s' % \
self.babel.get_language()
self.d_class = DocumentClass(settings.documentclass)
# object for a table while proccessing.
self.active_table = Table('longtable',settings.table_style)
# HACK. Should have more sophisticated typearea handling.
if settings.documentclass.find('scr') == -1:
self.typearea = '\\usepackage[DIV12]{typearea}\n'
else:
if self.d_options.find('DIV') == -1 and self.d_options.find('BCOR') == -1:
self.typearea = '\\typearea{12}\n'
else:
self.typearea = ''
if self.fontenc == 'T1':
fontenc = '\\usepackage[T1]{fontenc}\n'
else:
fontenc = ''
if self.settings.graphicx_option == '':
self.graphicx_package = '\\usepackage{graphicx}\n'
elif self.settings.graphicx_option.lower() == 'auto':
self.graphicx_package = '\n'.join(
('%Check if we are compiling under latex or pdflatex',
'\\ifx\\pdftexversion\\undefined',
' \\usepackage{graphicx}',
'\\else',
' \\usepackage[pdftex]{graphicx}',
'\\fi\n'))
else:
self.graphicx_package = (
'\\usepackage[%s]{graphicx}\n' % self.settings.graphicx_option)
self.head_prefix = [
self.latex_head % (self.d_options,self.settings.documentclass),
'\\usepackage{babel}\n', # language is in documents settings.
fontenc,
'\\usepackage{shortvrb}\n', # allows verb in footnotes.
self.encoding % self.to_latex_encoding(settings.output_encoding),
# * tabularx: for docinfo, automatic width of columns, always on one page.
'\\usepackage{tabularx}\n',
'\\usepackage{longtable}\n',
self.active_table.used_packages(),
# possible other packages.
# * fancyhdr
# * ltxtable is a combination of tabularx and longtable (pagebreaks).
# but ??
#
# extra space between text in tables and the line above them
'\\setlength{\\extrarowheight}{2pt}\n',
'\\usepackage{amsmath}\n', # what fore amsmath.
self.graphicx_package,
'\\usepackage{color}\n',
'\\usepackage{multirow}\n',
'\\usepackage{ifthen}\n', # before hyperref!
self.linking % (self.colorlinks, self.hyperlink_color, self.hyperlink_color),
self.typearea,
self.generator,
# latex lengths
'\\newlength{\\admonitionwidth}\n',
'\\setlength{\\admonitionwidth}{0.9\\textwidth}\n'
# width for docinfo tablewidth
'\\newlength{\\docinfowidth}\n',
'\\setlength{\\docinfowidth}{0.9\\textwidth}\n'
# linewidth of current environment, so tables are not wider
# than the sidebar: using locallinewidth seems to defer evaluation
# of linewidth, this is fixing it.
'\\newlength{\\locallinewidth}\n',
# will be set later.
]
self.head_prefix.extend( latex_headings['optionlist_environment'] )
self.head_prefix.extend( latex_headings['footnote_floats'] )
self.head_prefix.extend( latex_headings['some_commands'] )
## stylesheet is last: so it might be possible to overwrite defaults.
stylesheet = self.get_stylesheet_reference()
if stylesheet:
self.head_prefix.append(self.stylesheet % (stylesheet))
if self.linking: # and maybe check for pdf
self.pdfinfo = [ ]
self.pdfauthor = None
# pdftitle, pdfsubject, pdfauthor, pdfkeywords, pdfcreator, pdfproducer
else:
self.pdfinfo = None
# NOTE: Latex wants a date and an author, rst puts this into
# docinfo, so normally we donot want latex author/date handling.
# latex article has its own handling of date and author, deactivate.
self.head = [ ]
if not self.use_latex_docinfo:
self.head.extend( [ '\\author{}\n', '\\date{}\n' ] )
self.body_prefix = ['\\raggedbottom\n']
# separate title, so we can appen subtitle.
self.title = ""
self.body = []
self.body_suffix = ['\n']
self.section_level = 0
self.context = []
self.topic_class = ''
# column specification for tables
self.table_caption = None
# do we have one or more authors
self.author_stack = None
# Flags to encode
# ---------------
# verbatim: to tell encode not to encode.
self.verbatim = 0
# insert_newline: to tell encode to replace blanks by "~".
self.insert_none_breaking_blanks = 0
# insert_newline: to tell encode to add latex newline.
self.insert_newline = 0
# mbox_newline: to tell encode to add mbox and newline.
self.mbox_newline = 0
# enumeration is done by list environment.
self._enum_cnt = 0
# Stack of section counters so that we don't have to use_latex_toc.
# This will grow and shrink as processing occurs.
# Initialized for potential first-level sections.
self._section_number = [0]
# The current stack of enumerations so that we can expand
# them into a compound enumeration
self._enumeration_counters = []
self._bibitems = []
# docinfo.
self.docinfo = None
# inside literal block: no quote mangling.
self.literal_block = 0
self.literal_block_stack = []
self.literal = 0
# true when encoding in math mode
self.mathmode = 0
def get_stylesheet_reference(self):
if self.settings.stylesheet_path:
return self.settings.stylesheet_path
else:
return self.settings.stylesheet
def to_latex_encoding(self,docutils_encoding):
"""
Translate docutils encoding name into latex's.
Default fallback method is remove "-" and "_" chars from docutils_encoding.
"""
tr = { "iso-8859-1": "latin1", # west european
"iso-8859-2": "latin2", # east european
"iso-8859-3": "latin3", # esperanto, maltese
"iso-8859-4": "latin4", # north european,scandinavian, baltic
"iso-8859-5": "iso88595", # cyrillic (ISO)
"iso-8859-9": "latin5", # turkish
"iso-8859-15": "latin9", # latin9, update to latin1.
"mac_cyrillic": "maccyr", # cyrillic (on Mac)
"windows-1251": "cp1251", # cyrillic (on Windows)
"koi8-r": "koi8-r", # cyrillic (Russian)
"koi8-u": "koi8-u", # cyrillic (Ukrainian)
"windows-1250": "cp1250", #
"windows-1252": "cp1252", #
"us-ascii": "ascii", # ASCII (US)
# unmatched encodings
#"": "applemac",
#"": "ansinew", # windows 3.1 ansi
#"": "ascii", # ASCII encoding for the range 32--127.
#"": "cp437", # dos latine us
#"": "cp850", # dos latin 1
#"": "cp852", # dos latin 2
#"": "decmulti",
#"": "latin10",
#"iso-8859-6": "" # arabic
#"iso-8859-7": "" # greek
#"iso-8859-8": "" # hebrew
#"iso-8859-10": "" # latin6, more complete iso-8859-4
}
if tr.has_key(docutils_encoding.lower()):
return tr[docutils_encoding.lower()]
return docutils_encoding.translate(string.maketrans("",""),"_-").lower()
def language_label(self, docutil_label):
return self.language.labels[docutil_label]
def utf8_to_latex(self,text):
# see LaTeX codec http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/252124
# Only some special chracters are translated, for documents with many utf-8
# chars one should use the LaTeX unicode package.
latex_equivalents = {
u'\u00A0' : '~',
u'\u00A9' : '{\\copyright}',
u'\u2013' : '{--}',
u'\u2014' : '{---}',
u'\u2020' : '{\\dag}',
u'\u2021' : '{\\ddag}',
u'\u21d4' : '{$\\Leftrightarrow$}',
}
for uchar in latex_equivalents.keys():
text = text.replace(uchar,latex_equivalents[uchar])
return text
def encode(self, text):
"""
Encode special characters in `text` & return.
# $ % & ~ _ ^ \ { }
Escaping with a backslash does not help with backslashes, ~ and ^.
< > are only available in math-mode or tt font. (really ?)
$ starts math- mode.
AND quotes:
"""
if self.verbatim:
return text
# compile the regexps once. do it here so one can see them.
#
# first the braces.
if not self.__dict__.has_key('encode_re_braces'):
self.encode_re_braces = re.compile(r'([{}])')
text = self.encode_re_braces.sub(r'{\\\1}',text)
if not self.__dict__.has_key('encode_re_bslash'):
# find backslash: except in the form '{\{}' or '{\}}'.
self.encode_re_bslash = re.compile(r'(?<!{)(\\)(?![{}]})')
# then the backslash: except in the form from line above:
# either '{\{}' or '{\}}'.
text = self.encode_re_bslash.sub(r'{\\textbackslash}', text)
# then dollar
text = text.replace("$", '{\\$}')
if not ( self.literal_block or self.literal or self.mathmode ):
# the vertical bar: in mathmode |,\vert or \mid
# in textmode \textbar
text = text.replace("|", '{\\textbar}')
text = text.replace("<", '{\\textless}')
text = text.replace(">", '{\\textgreater}')
# then
text = text.replace("&", '{\\&}')
# the ^:
# * verb|^| does not work in mbox.
# * mathmode has wedge. hat{~} would also work.
# text = text.replace("^", '{\\ensuremath{^\\wedge}}')
text = text.replace("^", '{\\textasciicircum}')
text = text.replace("%", '{\\%}')
text = text.replace("#", '{\\#}')
text = text.replace("~", '{\\textasciitilde}')
if self.literal_block or self.literal:
# pdflatex does not produce doublequotes for ngerman.
text = self.babel.double_quotes_in_tt(text)
if self.fontenc == 'T1':
# make sure "--" does not become a "-".
# the same for "<<" and ">>".
text = text.replace("--","-{}-").replace("--","-{}-")
text = text.replace(">>",">{}>").replace(">>",">{}>")
text = text.replace("<<","<{}<").replace("<<","<{}<")
# replace underline by underlined blank, because this has correct width.
text = text.replace("_", '{\\underline{ }}')
else:
text = self.babel.quote_quotes(text)
text = text.replace("_", '{\\_}')
if self.insert_newline or self.literal_block:
# Insert a blank before the newline, to avoid
# ! LaTeX Error: There's no line here to end.
text = text.replace("\n", '~\\\\\n')
elif self.mbox_newline:
if self.literal_block:
closings = "}" * len(self.literal_block_stack)
openings = "".join(self.literal_block_stack)
else:
closings = ""
openings = ""
text = text.replace("\n", "%s}\\\\\n\\mbox{%s" % (closings,openings))
# lines starting with "[" give errors.
text = text.replace('[', '{[}')
if self.insert_none_breaking_blanks:
text = text.replace(' ', '~')
if self.settings.output_encoding != 'utf-8':
text = self.utf8_to_latex(text)
return text
def attval(self, text,
whitespace=re.compile('[\n\r\t\v\f]')):
"""Cleanse, encode, and return attribute value text."""
return self.encode(whitespace.sub(' ', text))
def astext(self):
if self.pdfinfo:
if self.pdfauthor:
self.pdfinfo.append('pdfauthor={%s}' % self.pdfauthor)
pdfinfo = '\\hypersetup{\n' + ',\n'.join(self.pdfinfo) + '\n}\n'
else:
pdfinfo = ''
title = '\\title{%s}\n' % self.title
return ''.join(self.head_prefix + [title]
+ self.head + [pdfinfo]
+ self.body_prefix + self.body + self.body_suffix)
def visit_Text(self, node):
self.body.append(self.encode(node.astext()))
def depart_Text(self, node):
pass
def visit_address(self, node):
self.visit_docinfo_item(node, 'address')
def depart_address(self, node):
self.depart_docinfo_item(node)
def visit_admonition(self, node, name=''):
self.body.append('\\begin{center}\\begin{sffamily}\n')
self.body.append('\\fbox{\\parbox{\\admonitionwidth}{\n')
if name:
self.body.append('\\textbf{\\large '+ self.language.labels[name] + '}\n');
self.body.append('\\vspace{2mm}\n')
def depart_admonition(self, node=None):
self.body.append('}}\n') # end parbox fbox
self.body.append('\\end{sffamily}\n\\end{center}\n');
def visit_attention(self, node):
self.visit_admonition(node, 'attention')
def depart_attention(self, node):
self.depart_admonition()
def visit_author(self, node):
self.visit_docinfo_item(node, 'author')
def depart_author(self, node):
self.depart_docinfo_item(node)
def visit_authors(self, node):
# not used: visit_author is called anyway for each author.
if self.use_latex_docinfo:
self.author_stack = []
def depart_authors(self, node):
if self.use_latex_docinfo:
self.head.append('\\author{%s}\n' % \
' \\and '.join(self.author_stack) )
self.author_stack = None
def visit_block_quote(self, node):
self.body.append( '\\begin{quote}\n')
def depart_block_quote(self, node):
self.body.append( '\\end{quote}\n')
def visit_bullet_list(self, node):
if self.topic_class == 'contents':
if not self.use_latex_toc:
self.body.append( '\\begin{list}{}{}\n' )
else:
self.body.append( '\\begin{itemize}\n' )
def depart_bullet_list(self, node):
if self.topic_class == 'contents':
if not self.use_latex_toc:
self.body.append( '\\end{list}\n' )
else:
self.body.append( '\\end{itemize}\n' )
# Imperfect superscript/subscript handling: mathmode italicizes
# all letters by default.
def visit_superscript(self, node):
self.body.append('$^{')
self.mathmode = 1
def depart_superscript(self, node):
self.body.append('}$')
self.mathmode = 0
def visit_subscript(self, node):
self.body.append('$_{')
self.mathmode = 1
def depart_subscript(self, node):
self.body.append('}$')
self.mathmode = 0
def visit_caption(self, node):
self.body.append( '\\caption{' )
def depart_caption(self, node):
self.body.append('}')
def visit_caution(self, node):
self.visit_admonition(node, 'caution')
def depart_caution(self, node):
self.depart_admonition()
def visit_title_reference(self, node):
self.body.append( '\\titlereference{' )
def depart_title_reference(self, node):
self.body.append( '}' )
def visit_citation(self, node):
# TODO maybe use cite bibitems
if self._use_latex_citations:
self.context.append(len(self.body))
else:
self.body.append('\\begin{figure}[b]')
self.body.append('\\hypertarget{%s}' % node['id'])
def depart_citation(self, node):
if self._use_latex_citations:
size = self.context.pop()
label = self.body[size]
text = ''.join(self.body[size+1:])
del self.body[size:]
self._bibitems.append([label, text])
else:
self.body.append('\\end{figure}\n')
def visit_citation_reference(self, node):
if self._use_latex_citations:
self.body.append('\\cite{')
else:
href = ''
if node.has_key('refid'):
href = node['refid']
elif node.has_key('refname'):
href = self.document.nameids[node['refname']]
self.body.append('[\\hyperlink{%s}{' % href)
def depart_citation_reference(self, node):
if self._use_latex_citations:
self.body.append('}')
else:
self.body.append('}]')
def visit_classifier(self, node):
self.body.append( '(\\textbf{' )
def depart_classifier(self, node):
self.body.append( '})\n' )
def visit_colspec(self, node):
self.active_table.visit_colspec(node)
def depart_colspec(self, node):
pass
def visit_comment(self, node,
sub=re.compile('\n').sub):
"""Escape end of line by a ne comment start in comment text."""
self.body.append('%% %s \n' % sub('\n% ', node.astext()))
raise nodes.SkipNode
def visit_contact(self, node):
self.visit_docinfo_item(node, 'contact')
def depart_contact(self, node):
self.depart_docinfo_item(node)
def visit_copyright(self, node):
self.visit_docinfo_item(node, 'copyright')
def depart_copyright(self, node):
self.depart_docinfo_item(node)
def visit_danger(self, node):
self.visit_admonition(node, 'danger')
def depart_danger(self, node):
self.depart_admonition()
def visit_date(self, node):
self.visit_docinfo_item(node, 'date')
def depart_date(self, node):
self.depart_docinfo_item(node)
def visit_decoration(self, node):
pass
def depart_decoration(self, node):
pass
def visit_definition(self, node):
self.body.append('%[visit_definition]\n')
def depart_definition(self, node):
self.body.append('\n')
self.body.append('%[depart_definition]\n')
def visit_definition_list(self, node):
self.body.append( '\\begin{description}\n' )
def depart_definition_list(self, node):
self.body.append( '\\end{description}\n' )
def visit_definition_list_item(self, node):
self.body.append('%[visit_definition_list_item]\n')
def depart_definition_list_item(self, node):
self.body.append('%[depart_definition_list_item]\n')
def visit_description(self, node):
if self.use_optionlist_for_option_list:
self.body.append( ' ' )
else:
self.body.append( ' & ' )
def depart_description(self, node):
pass
def visit_docinfo(self, node):
self.docinfo = []
self.docinfo.append('%' + '_'*75 + '\n')
self.docinfo.append('\\begin{center}\n')
self.docinfo.append('\\begin{tabularx}{\\docinfowidth}{lX}\n')
def depart_docinfo(self, node):
self.docinfo.append('\\end{tabularx}\n')
self.docinfo.append('\\end{center}\n')
self.body = self.docinfo + self.body
# clear docinfo, so field names are no longer appended.
self.docinfo = None
def visit_docinfo_item(self, node, name):
if name == 'author':
if not self.pdfinfo == None:
if not self.pdfauthor:
self.pdfauthor = self.attval(node.astext())
else:
self.pdfauthor += self.author_separator + self.attval(node.astext())
if self.use_latex_docinfo:
if self.author_stack == None:
self.head.append('\\author{%s}\n' % self.attval(node.astext()))
else:
self.author_stack.append( self.attval(node.astext()) )
raise nodes.SkipNode
elif name == 'date':
if self.use_latex_docinfo:
self.head.append('\\date{%s}\n' % self.attval(node.astext()))
raise nodes.SkipNode
self.docinfo.append('\\textbf{%s}: &\n\t' % self.language_label(name))
if name == 'address':
self.insert_newline = 1
self.docinfo.append('{\\raggedright\n')
self.context.append(' } \\\\\n')
else:
self.context.append(' \\\\\n')
self.context.append(self.docinfo)
self.context.append(len(self.body))
def depart_docinfo_item(self, node):
size = self.context.pop()
dest = self.context.pop()
tail = self.context.pop()
tail = self.body[size:] + [tail]
del self.body[size:]
dest.extend(tail)
# for address we did set insert_newline
self.insert_newline = 0
def visit_doctest_block(self, node):
self.body.append( '\\begin{verbatim}' )
self.verbatim = 1
def depart_doctest_block(self, node):
self.body.append( '\\end{verbatim}\n' )
self.verbatim = 0
def visit_document(self, node):
self.body_prefix.append('\\begin{document}\n')
# BUG: \maketitle without title (i.e. --no-doc-title) adds
# unnecessary vspace.
self.body_prefix.append('\\maketitle\n\n')
# alternative use titlepage environment.
# \begin{titlepage}
self.body.append('\n\\setlength{\\locallinewidth}{\\linewidth}\n')
def depart_document(self, node):
# TODO insertion point of bibliography should none automatic.
if self._use_latex_citations and len(self._bibitems)>0:
widest_label = ""
for bi in self._bibitems:
if len(widest_label)<len(bi[0]):
widest_label = bi[0]
self.body.append('\n\\begin{thebibliography}{%s}\n'%widest_label)
for bi in self._bibitems:
self.body.append('\\bibitem[%s]{%s}{%s}\n' % (bi[0], bi[0], bi[1]))
self.body.append('\\end{thebibliography}\n')
self.body_suffix.append('\\end{document}\n')
def visit_emphasis(self, node):
self.body.append('\\emph{')
self.literal_block_stack.append('\\emph{')
def depart_emphasis(self, node):
self.body.append('}')
self.literal_block_stack.pop()
def visit_entry(self, node):
self.active_table.visit_entry()
# cell separation
if self.active_table.get_entry_number() == 1:
# if the firstrow is a multirow, this actually is the second row.
# this gets hairy if rowspans follow each other.
if self.active_table.get_rowspan(0):
self.body.append(' & ')
self.active_table.visit_entry() # increment cell count
else:
self.body.append(' & ')
# multi{row,column}
# IN WORK BUG TODO HACK continues here
# multirow in LaTeX simply will enlarge the cell over several rows
# (the following n if n is positive, the former if negative).
if node.has_key('morerows') and node.has_key('morecols'):
raise NotImplementedError('Cells that '
'span multiple rows *and* columns are not supported, sorry.')
if node.has_key('morerows'):
count = node['morerows'] + 1
self.active_table.set_rowspan(self.active_table.get_entry_number()-1,count)
self.body.append('\\multirow{%d}{%s}{' % \
(count,self.active_table.get_column_width()))
self.context.append('}')
# BUG following rows must have empty cells.
elif node.has_key('morecols'):
# the vertical bar before column is missing if it is the first column.
# the one after always.
if self.active_table.get_entry_number() == 1:
bar1 = self.active_table.get_vertical_bar()
else:
bar1 = ''
count = node['morecols'] + 1
self.body.append('\\multicolumn{%d}{%sl%s}{' % \
(count, bar1, self.active_table.get_vertical_bar()))
self.context.append('}')
else:
self.context.append('')
# header / not header
if isinstance(node.parent.parent, nodes.thead):
self.body.append('\\textbf{')
self.context.append('}')
else:
self.context.append('')
def depart_entry(self, node):
self.body.append(self.context.pop()) # header / not header
self.body.append(self.context.pop()) # multirow/column
# if following row is spanned from above.
if self.active_table.get_rowspan(self.active_table.get_entry_number()):
self.body.append(' & ')
self.active_table.visit_entry() # increment cell count
def visit_row(self, node):
self.active_table.visit_row()
def depart_row(self, node):
self.body.extend(self.active_table.depart_row())
def visit_enumerated_list(self, node):
# We create our own enumeration list environment.
# This allows to set the style and starting value
# and unlimited nesting.
self._enum_cnt += 1
enum_style = {'arabic':'arabic',
'loweralpha':'alph',
'upperalpha':'Alph',
'lowerroman':'roman',
'upperroman':'Roman' }
enum_suffix = ""
if node.has_key('suffix'):
enum_suffix = node['suffix']
enum_prefix = ""
if node.has_key('prefix'):
enum_prefix = node['prefix']
if self.compound_enumerators:
pref = ""
if self.section_prefix_for_enumerators and self.section_level:
for i in range(self.section_level):
pref += '%d.' % self._section_number[i]
pref = pref[:-1] + self.section_enumerator_separator
enum_prefix += pref
for counter in self._enumeration_counters:
enum_prefix += counter + '.'
enum_type = "arabic"
if node.has_key('enumtype'):
enum_type = node['enumtype']
if enum_style.has_key(enum_type):
enum_type = enum_style[enum_type]
counter_name = "listcnt%d" % self._enum_cnt;
self._enumeration_counters.append("\\%s{%s}" % (enum_type,counter_name))
self.body.append('\\newcounter{%s}\n' % counter_name)
self.body.append('\\begin{list}{%s\\%s{%s}%s}\n' % \
(enum_prefix,enum_type,counter_name,enum_suffix))
self.body.append('{\n')
self.body.append('\\usecounter{%s}\n' % counter_name)
# set start after usecounter, because it initializes to zero.
if node.has_key('start'):
self.body.append('\\addtocounter{%s}{%d}\n' \
% (counter_name,node['start']-1))
## set rightmargin equal to leftmargin
self.body.append('\\setlength{\\rightmargin}{\\leftmargin}\n')
self.body.append('}\n')
def depart_enumerated_list(self, node):
self.body.append('\\end{list}\n')
self._enumeration_counters.pop()
def visit_error(self, node):
self.visit_admonition(node, 'error')
def depart_error(self, node):
self.depart_admonition()
def visit_field(self, node):
# real output is done in siblings: _argument, _body, _name
pass
def depart_field(self, node):
self.body.append('\n')
##self.body.append('%[depart_field]\n')
def visit_field_argument(self, node):
self.body.append('%[visit_field_argument]\n')
def depart_field_argument(self, node):
self.body.append('%[depart_field_argument]\n')
def visit_field_body(self, node):
# BUG by attach as text we loose references.
if self.docinfo:
self.docinfo.append('%s \\\\\n' % node.astext())
raise nodes.SkipNode
# BUG: what happens if not docinfo
def depart_field_body(self, node):
self.body.append( '\n' )
def visit_field_list(self, node):
if not self.docinfo:
self.body.append('\\begin{quote}\n')
self.body.append('\\begin{description}\n')
def depart_field_list(self, node):
if not self.docinfo:
self.body.append('\\end{description}\n')
self.body.append('\\end{quote}\n')
def visit_field_name(self, node):
# BUG this duplicates docinfo_item
if self.docinfo:
self.docinfo.append('\\textbf{%s}: &\n\t' % node.astext())
raise nodes.SkipNode
else:
self.body.append('\\item [')
def depart_field_name(self, node):
if not self.docinfo:
self.body.append(':]')
def visit_figure(self, node):
self.body.append( '\\begin{figure}[htbp]\\begin{center}\n' )
def depart_figure(self, node):
self.body.append( '\\end{center}\\end{figure}\n' )
def visit_footer(self, node):
self.context.append(len(self.body))
def depart_footer(self, node):
start = self.context.pop()
footer = (['\n\\begin{center}\small\n']
+ self.body[start:] + ['\n\\end{center}\n'])
self.body_suffix[:0] = footer
del self.body[start:]
def visit_footnote(self, node):
if self.use_latex_footnotes:
num,text = node.astext().split(None,1)
num = self.encode(num.strip())
self.body.append('\\footnotetext['+num+']')
self.body.append('{')
else:
self.body.append('\\begin{figure}[b]')
self.body.append('\\hypertarget{%s}' % node['id'])
def depart_footnote(self, node):
if self.use_latex_footnotes:
self.body.append('}\n')
else:
self.body.append('\\end{figure}\n')
def visit_footnote_reference(self, node):
if self.use_latex_footnotes:
self.body.append("\\footnotemark["+self.encode(node.astext())+"]")
raise nodes.SkipNode
href = ''
if node.has_key('refid'):
href = node['refid']
elif node.has_key('refname'):
href = self.document.nameids[node['refname']]
format = self.settings.footnote_references
if format == 'brackets':
suffix = '['
self.context.append(']')
elif format == 'superscript':
suffix = '\\raisebox{.5em}[0em]{\\scriptsize'
self.context.append('}')
else: # shouldn't happen
raise AssertionError('Illegal footnote reference format.')
self.body.append('%s\\hyperlink{%s}{' % (suffix,href))
def depart_footnote_reference(self, node):
if self.use_latex_footnotes:
return
self.body.append('}%s' % self.context.pop())
# footnote/citation label
def visit_label(self, node):
if isinstance(node.parent, nodes.footnote) and self.use_latex_footnotes:
raise nodes.SkipNode
elif isinstance(node.parent, nodes.citation) and self._use_latex_citations:
pass
else:
self.body.append('[')
def depart_label(self, node):
if isinstance(node.parent, nodes.citation) and self._use_latex_citations:
return
self.body.append(']')
# elements generated by the framework e.g. section numbers.
def visit_generated(self, node):
pass
def depart_generated(self, node):
pass
def visit_header(self, node):
self.context.append(len(self.body))
def depart_header(self, node):
start = self.context.pop()
self.body_prefix.append('\n\\verb|begin_header|\n')
self.body_prefix.extend(self.body[start:])
self.body_prefix.append('\n\\verb|end_header|\n')
del self.body[start:]
def visit_hint(self, node):
self.visit_admonition(node, 'hint')
def depart_hint(self, node):
self.depart_admonition()
def visit_image(self, node):
attrs = node.attributes
pre = [] # in reverse order
post = ['\\includegraphics{%s}' % attrs['uri']]
inline = isinstance(node.parent, nodes.TextElement)
if attrs.has_key('scale'):
# Could also be done with ``scale`` option to
# ``\includegraphics``; doing it this way for consistency.
pre.append('\\scalebox{%f}{' % (attrs['scale'] / 100.0,))
post.append('}')
if attrs.has_key('align'):
align_prepost = {
# By default latex aligns the top of an image.
(1, 'top'): ('', ''),
(1, 'middle'): ('\\raisebox{-0.5\\height}{', '}'),
(1, 'bottom'): ('\\raisebox{-\\height}{', '}'),
(0, 'center'): ('{\\hfill', '\\hfill}'),
# These 2 don't exactly do the right thing. The image should
# be floated alongside the paragraph. See
# http://www.w3.org/TR/html4/struct/objects.html#adef-align-IMG
(0, 'left'): ('{', '\\hfill}'),
(0, 'right'): ('{\\hfill', '}'),}
try:
pre.append(align_prepost[inline, attrs['align']][0])
post.append(align_prepost[inline, attrs['align']][1])
except KeyError:
pass # XXX complain here?
if not inline:
pre.append('\n')
post.append('\n')
pre.reverse()
self.body.extend(pre + post)
def depart_image(self, node):
pass
def visit_important(self, node):
self.visit_admonition(node, 'important')
def depart_important(self, node):
self.depart_admonition()
def visit_interpreted(self, node):
# @@@ Incomplete, pending a proper implementation on the
# Parser/Reader end.
self.visit_literal(node)
def depart_interpreted(self, node):
self.depart_literal(node)
def visit_legend(self, node):
self.body.append('{\\small ')
def depart_legend(self, node):
self.body.append('}')
def visit_line_block(self, node):
"""line-block:
* whitespace (including linebreaks) is significant
* inline markup is supported.
* serif typeface
"""
self.body.append('\\begin{flushleft}\n')
self.insert_none_breaking_blanks = 1
# mbox would stop LaTeX from wrapping long lines.
# but line_blocks are allowed to wrap.
self.line_block_without_mbox = 1
if self.line_block_without_mbox:
self.insert_newline = 1
else:
self.mbox_newline = 1
self.body.append('\\mbox{')
def depart_line_block(self, node):
if self.line_block_without_mbox:
self.insert_newline = 0
else:
self.body.append('}')
self.mbox_newline = 0
self.insert_none_breaking_blanks = 0
self.body.append('\n\\end{flushleft}\n')
def visit_list_item(self, node):
# Append "{}" in case the next character is "[", which would break
# LaTeX's list environment (no numbering and the "[" is not printed).
self.body.append('\\item {} ')
def depart_list_item(self, node):
self.body.append('\n')
def visit_literal(self, node):
self.literal = 1
self.body.append('\\texttt{')
def depart_literal(self, node):
self.body.append('}')
self.literal = 0
def visit_literal_block(self, node):
"""
Render a literal-block.
Literal blocks are used for "::"-prefixed literal-indented
blocks of text, where the inline markup is not recognized,
but are also the product of the parsed-literal directive,
where the markup is respected.
"""
# In both cases, we want to use a typewriter/monospaced typeface.
# For "real" literal-blocks, we can use \verbatim, while for all
# the others we must use \mbox.
#
# We can distinguish between the two kinds by the number of
# siblings the compose this node: if it is composed by a
# single element, it's surely is either a real one, otherwise
# it's a parsed-literal that does not contain any markup.
#
if (self.settings.use_verbatim_when_possible and (len(node) == 1)
# in case of a parsed-literal containing just a "**bold**" word:
and isinstance(node[0], nodes.Text)):
self.verbatim = 1
self.body.append('\\begin{quote}\\begin{verbatim}\n')
else:
self.literal_block = 1
self.insert_none_breaking_blanks = 1
if self.active_table.is_open():
self.body.append('\n{\\ttfamily \\raggedright \\noindent\n')
else:
# no quote inside tables, to avoid vertical sppace between
# table border and literal block.
# BUG: fails if normal text preceeds the literal block.
self.body.append('\\begin{quote}')
self.body.append('{\\ttfamily \\raggedright \\noindent\n')
# * obey..: is from julien and never worked for me (grubert).
# self.body.append('{\\obeylines\\obeyspaces\\ttfamily\n')
def depart_literal_block(self, node):
if self.verbatim:
self.body.append('\n\\end{verbatim}\\end{quote}\n')
self.verbatim = 0
else:
if self.active_table.is_open():
self.body.append('\n}\n')
else:
self.body.append('\n')
self.body.append('}\\end{quote}\n')
self.insert_none_breaking_blanks = 0
self.literal_block = 0
# obey end: self.body.append('}\n')
def visit_meta(self, node):
self.body.append('[visit_meta]\n')
# BUG maybe set keywords for pdf
##self.head.append(self.starttag(node, 'meta', **node.attributes))
def depart_meta(self, node):
self.body.append('[depart_meta]\n')
def visit_note(self, node):
self.visit_admonition(node, 'note')
def depart_note(self, node):
self.depart_admonition()
def visit_option(self, node):
if self.context[-1]:
# this is not the first option
self.body.append(', ')
def depart_option(self, node):
# flag tha the first option is done.
self.context[-1] += 1
def visit_option_argument(self, node):
"""The delimiter betweeen an option and its argument."""
self.body.append(node.get('delimiter', ' '))
def depart_option_argument(self, node):
pass
def visit_option_group(self, node):
if self.use_optionlist_for_option_list:
self.body.append('\\item [')
else:
if len(node.astext()) > 14:
self.body.append('\\multicolumn{2}{l}{')
self.context.append('} \\\\\n ')
else:
self.context.append('')
self.body.append('\\texttt{')
# flag for first option
self.context.append(0)
def depart_option_group(self, node):
self.context.pop() # the flag
if self.use_optionlist_for_option_list:
self.body.append('] ')
else:
self.body.append('}')
self.body.append(self.context.pop())
def visit_option_list(self, node):
self.body.append('% [option list]\n')
if self.use_optionlist_for_option_list:
self.body.append('\\begin{optionlist}{3cm}\n')
else:
self.body.append('\\begin{center}\n')
# BUG: use admwidth or make it relative to textwidth ?
self.body.append('\\begin{tabularx}{.9\\linewidth}{lX}\n')
def depart_option_list(self, node):
if self.use_optionlist_for_option_list:
self.body.append('\\end{optionlist}\n')
else:
self.body.append('\\end{tabularx}\n')
self.body.append('\\end{center}\n')
def visit_option_list_item(self, node):
pass
def depart_option_list_item(self, node):
if not self.use_optionlist_for_option_list:
self.body.append('\\\\\n')
def visit_option_string(self, node):
##self.body.append(self.starttag(node, 'span', '', CLASS='option'))
pass
def depart_option_string(self, node):
##self.body.append('</span>')
pass
def visit_organization(self, node):
self.visit_docinfo_item(node, 'organization')
def depart_organization(self, node):
self.depart_docinfo_item(node)
def visit_paragraph(self, node):
if not self.topic_class == 'contents':
self.body.append('\n')
def depart_paragraph(self, node):
self.body.append('\n')
def visit_problematic(self, node):
self.body.append('{\\color{red}\\bfseries{}')
def depart_problematic(self, node):
self.body.append('}')
def visit_raw(self, node):
if node.has_key('format') and node['format'].lower() == 'latex':
self.body.append(node.astext())
raise nodes.SkipNode
def visit_reference(self, node):
# BUG: hash_char "#" is trouble some in LaTeX.
# mbox and other environment do not like the '#'.
hash_char = '\\#'
if node.has_key('refuri'):
href = node['refuri'].replace('#',hash_char)
elif node.has_key('refid'):
href = hash_char + node['refid']
elif node.has_key('refname'):
href = hash_char + self.document.nameids[node['refname']]
else:
raise AssertionError('Unknown reference.')
self.body.append('\\href{%s}{' % href)
def depart_reference(self, node):
self.body.append('}')
def visit_revision(self, node):
self.visit_docinfo_item(node, 'revision')
def depart_revision(self, node):
self.depart_docinfo_item(node)
def visit_section(self, node):
self.section_level += 1
# Initialize counter for potential subsections:
self._section_number.append(0)
# Counter for this section's level (initialized by parent section):
self._section_number[self.section_level - 1] += 1
def depart_section(self, node):
# Remove counter for potential subsections:
self._section_number.pop()
self.section_level -= 1
def visit_sidebar(self, node):
# BUG: this is just a hack to make sidebars render something
self.body.append('\n\\setlength{\\locallinewidth}{0.9\\admonitionwidth}\n')
self.body.append('\\begin{center}\\begin{sffamily}\n')
self.body.append('\\fbox{\\colorbox[gray]{0.80}{\\parbox{\\admonitionwidth}{\n')
def depart_sidebar(self, node):
self.body.append('}}}\n') # end parbox colorbox fbox
self.body.append('\\end{sffamily}\n\\end{center}\n');
self.body.append('\n\\setlength{\\locallinewidth}{\\linewidth}\n')
attribution_formats = {'dash': ('---', ''),
'parentheses': ('(', ')'),
'parens': ('(', ')'),
'none': ('', '')}
def visit_attribution(self, node):
prefix, suffix = self.attribution_formats[self.settings.attribution]
self.body.append('\n\\begin{flushright}\n')
self.body.append(prefix)
self.context.append(suffix)
def depart_attribution(self, node):
self.body.append(self.context.pop() + '\n')
self.body.append('\\end{flushright}\n')
def visit_status(self, node):
self.visit_docinfo_item(node, 'status')
def depart_status(self, node):
self.depart_docinfo_item(node)
def visit_strong(self, node):
self.body.append('\\textbf{')
self.literal_block_stack.append('\\textbf{')
def depart_strong(self, node):
self.body.append('}')
self.literal_block_stack.pop()
def visit_substitution_definition(self, node):
raise nodes.SkipNode
def visit_substitution_reference(self, node):
self.unimplemented_visit(node)
def visit_subtitle(self, node):
if isinstance(node.parent, nodes.sidebar):
self.body.append('~\\\\\n\\textbf{')
self.context.append('}\n\\smallskip\n')
else:
self.title = self.title + \
'\\\\\n\\large{%s}\n' % self.encode(node.astext())
raise nodes.SkipNode
def depart_subtitle(self, node):
if isinstance(node.parent, nodes.sidebar):
self.body.append(self.context.pop())
def visit_system_message(self, node):
if node['level'] < self.document.reporter['writer'].report_level:
raise nodes.SkipNode
def depart_system_message(self, node):
self.body.append('\n')
def visit_table(self, node):
if self.active_table.is_open():
print 'nested tables are not supported'
raise AssertionError
self.active_table.open()
self.body.append('\n' + self.active_table.get_opening())
def depart_table(self, node):
self.body.append(self.active_table.get_closing() + '\n')
self.active_table.close()
def visit_target(self, node):
# BUG: why not (refuri or refid or refname) means not footnote ?
if not (node.has_key('refuri') or node.has_key('refid')
or node.has_key('refname')):
self.body.append('\\hypertarget{%s}{' % node['id'])
self.context.append('}')
else:
self.context.append('')
def depart_target(self, node):
self.body.append(self.context.pop())
def visit_tbody(self, node):
# BUG write preamble if not yet done (colspecs not [])
# for tables without heads.
if not self.active_table.get('preamble written'):
self.visit_thead(None)
# self.depart_thead(None)
def depart_tbody(self, node):
pass
def visit_term(self, node):
self.body.append('\\item[')
def depart_term(self, node):
# definition list term.
self.body.append(']\n')
def visit_tgroup(self, node):
#self.body.append(self.starttag(node, 'colgroup'))
#self.context.append('</colgroup>\n')
pass
def depart_tgroup(self, node):
pass
def visit_thead(self, node):
self.body.append('{%s}\n' % self.active_table.get_colspecs())
if self.active_table.caption:
self.body.append('\\caption{%s}\\\\\n' % self.active_table.caption)
self.active_table.set('preamble written',1)
# TODO longtable supports firsthead and lastfoot too.
self.body.extend(self.active_table.visit_thead())
def depart_thead(self, node):
# the table header written should be on every page
# => \endhead
self.body.extend(self.active_table.depart_thead())
# and the firsthead => \endfirsthead
# BUG i want a "continued from previous page" on every not
# firsthead, but then we need the header twice.
#
# there is a \endfoot and \endlastfoot too.
# but we need the number of columns to
# self.body.append('\\multicolumn{%d}{c}{"..."}\n' % number_of_columns)
# self.body.append('\\hline\n\\endfoot\n')
# self.body.append('\\hline\n')
# self.body.append('\\endlastfoot\n')
def visit_tip(self, node):
self.visit_admonition(node, 'tip')
def depart_tip(self, node):
self.depart_admonition()
def bookmark(self, node):
"""Append latex href and pdfbookmarks for titles.
"""
if node.parent.hasattr('id'):
self.body.append('\\hypertarget{%s}{}\n' % node.parent['id'])
if not self.use_latex_toc:
# BUG level depends on style. pdflatex allows level 0 to 3
# ToC would be the only on level 0 so i choose to decrement the rest.
# "Table of contents" bookmark to see the ToC. To avoid this
# we set all zeroes to one.
l = self.section_level
if l>0:
l = l-1
# pdftex does not like "_" subscripts in titles
text = self.encode(node.astext())
self.body.append('\\pdfbookmark[%d]{%s}{%s}\n' % \
(l,text,node.parent['id']))
def visit_title(self, node):
"""Only 3 section levels are supported by LaTeX article (AFAIR)."""
if isinstance(node.parent, nodes.topic):
# section titles before the table of contents.
self.bookmark(node)
# BUG: latex chokes on center environment with "perhaps a missing item".
# so we use hfill.
self.body.append('\\subsection*{~\\hfill ')
# the closing brace for subsection.
self.context.append('\\hfill ~}\n')
# TODO: for admonition titles before the first section
# either specify every possible node or ... ?
elif isinstance(node.parent, nodes.sidebar) \
or isinstance(node.parent, nodes.admonition):
self.body.append('\\textbf{\\large ')
self.context.append('}\n\\smallskip\n')
elif isinstance(node.parent, nodes.table):
# caption must be written after column spec
self.active_table.caption = node.astext()
raise nodes.SkipNode
elif self.section_level == 0:
# document title
self.title = self.encode(node.astext())
if not self.pdfinfo == None:
self.pdfinfo.append( 'pdftitle={%s}' % self.encode(node.astext()) )
raise nodes.SkipNode
else:
self.body.append('\n\n')
self.body.append('%' + '_' * 75)
self.body.append('\n\n')
self.bookmark(node)
if self.use_latex_toc:
section_star = ""
else:
section_star = "*"
section_name = self.d_class.section(self.section_level)
self.body.append('\\%s%s{' % (section_name, section_star))
self.context.append('}\n')
def depart_title(self, node):
self.body.append(self.context.pop())
def visit_topic(self, node):
self.topic_class = node.get('class')
if self.use_latex_toc:
self.body.append('\\tableofcontents\n\n\\bigskip\n')
self.topic_class = ''
raise nodes.SkipNode
def depart_topic(self, node):
self.topic_class = ''
self.body.append('\n')
def visit_rubric(self, node):
self.body.append('\\rubric{')
self.context.append('}\n')
def depart_rubric(self, node):
self.body.append(self.context.pop())
def visit_transition(self, node):
self.body.append('\n\n')
self.body.append('%' + '_' * 75)
self.body.append('\n\\hspace*{\\fill}\\hrulefill\\hspace*{\\fill}')
self.body.append('\n\n')
def depart_transition(self, node):
pass
def visit_version(self, node):
self.visit_docinfo_item(node, 'version')
def depart_version(self, node):
self.depart_docinfo_item(node)
def visit_warning(self, node):
self.visit_admonition(node, 'warning')
def depart_warning(self, node):
self.depart_admonition()
def unimplemented_visit(self, node):
raise NotImplementedError('visiting unimplemented node type: %s'
% node.__class__.__name__)
# def unknown_visit(self, node):
# def default_visit(self, node):
# vim: set ts=4 et ai :
# Author: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:57 $
# Copyright: This module has been placed in the public domain.
"""
PEP HTML Writer.
"""
__docformat__ = 'reStructuredText'
import sys
import docutils
from docutils import frontend, nodes, utils
from docutils.writers import html4css1
class Writer(html4css1.Writer):
settings_spec = html4css1.Writer.settings_spec + (
'PEP/HTML-Specific Options',
"""The HTML --footnote-references option's default is set to """
'"brackets".',
(('Specify a template file. Default is "pep-html-template".',
['--template'],
{'default': 'pep-html-template', 'metavar': '<file>'}),
('Python\'s home URL. Default is ".." (parent directory).',
['--python-home'],
{'default': '..', 'metavar': '<URL>'}),
('Home URL prefix for PEPs. Default is "." (current directory).',
['--pep-home'],
{'default': '.', 'metavar': '<URL>'}),
# Workaround for SourceForge's broken Python
# (``import random`` causes a segfault).
(frontend.SUPPRESS_HELP,
['--no-random'],
{'action': 'store_true', 'validator': frontend.validate_boolean}),))
settings_default_overrides = {'footnote_references': 'brackets'}
relative_path_settings = (html4css1.Writer.relative_path_settings
+ ('template',))
config_section = 'pep_html writer'
config_section_dependencies = ('writers', 'html4css1 writer')
def __init__(self):
html4css1.Writer.__init__(self)
self.translator_class = HTMLTranslator
def translate(self):
html4css1.Writer.translate(self)
settings = self.document.settings
template = open(settings.template).read()
# Substitutions dict for template:
subs = {}
subs['encoding'] = settings.output_encoding
subs['version'] = docutils.__version__
subs['stylesheet'] = ''.join(self.stylesheet)
pyhome = settings.python_home
subs['pyhome'] = pyhome
subs['pephome'] = settings.pep_home
if pyhome == '..':
subs['pepindex'] = '.'
else:
subs['pepindex'] = pyhome + '/peps/'
index = self.document.first_child_matching_class(nodes.field_list)
header = self.document[index]
pepnum = header[0][1].astext()
subs['pep'] = pepnum
if settings.no_random:
subs['banner'] = 0
else:
import random
subs['banner'] = random.randrange(64)
try:
subs['pepnum'] = '%04i' % int(pepnum)
except:
subs['pepnum'] = pepnum
subs['title'] = header[1][1].astext()
subs['body'] = ''.join(
self.body_pre_docinfo + self.docinfo + self.body)
subs['body_suffix'] = ''.join(self.body_suffix)
self.output = template % subs
class HTMLTranslator(html4css1.HTMLTranslator):
def depart_field_list(self, node):
html4css1.HTMLTranslator.depart_field_list(self, node)
if node.get('class') == 'rfc2822':
self.body.append('<hr />\n')
# Authors: David Goodger
# Contact: goodger@users.sourceforge.net
# Revision: $Revision: 1.2.10.3.8.1 $
# Date: $Date: 2004/05/12 19:57:57 $
# Copyright: This module has been placed in the public domain.
"""
Simple internal document tree Writer, writes indented pseudo-XML.
"""
__docformat__ = 'reStructuredText'
from docutils import writers
class Writer(writers.Writer):
supported = ('pprint', 'pformat', 'pseudoxml')
"""Formats this writer supports."""
config_section = 'pseudoxml writer'
config_section_dependencies = ('writers',)
output = None
"""Final translated form of `document`."""
def translate(self):
self.output = self.document.pformat()
def supports(self, format):
"""This writer supports all format-specific elements."""
return 1
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment