[Zope-Checkins] CVS: Zope/lib/python/docutils - __init__.py:1.2.10.1 core.py:1.2.10.1 frontend.py:1.2.10.1 io.py:1.2.10.1 nodes.py:1.2.10.1 statemachine.py:1.2.10.1 utils.py:1.2.10.1
Chris McDonough
chrism@zope.com
Mon, 21 Jul 2003 12:39:02 -0400
Update of /cvs-repository/Zope/lib/python/docutils
In directory cvs.zope.org:/tmp/cvs-serv17213/lib/python/docutils
Modified Files:
Tag: Zope-2_7-branch
__init__.py core.py frontend.py io.py nodes.py statemachine.py
utils.py
Log Message:
Merge changes from HEAD since the release of Zope 2.7a1 into the Zope-2_7-branch in preparation for release of Zope 2.7b1.
=== Zope/lib/python/docutils/__init__.py 1.2 => 1.2.10.1 ===
--- Zope/lib/python/docutils/__init__.py:1.2 Sat Feb 1 04:26:00 2003
+++ Zope/lib/python/docutils/__init__.py Mon Jul 21 12:37:52 2003
@@ -23,12 +23,6 @@
- nodes.py: Docutils document tree (doctree) node class library.
-- optik.py: Option parsing and command-line help; from Greg Ward's
- http://optik.sf.net/ project, included for convenience.
-
-- roman.py: Conversion to and from Roman numerals. Courtesy of Mark
- Pilgrim (http://diveintopython.org/).
-
- statemachine.py: A finite state machine specialized for
regular-expression-based text filters.
@@ -55,12 +49,12 @@
__docformat__ = 'reStructuredText'
-__version__ = '0.2.8'
+__version__ = '0.3.0'
"""``major.minor.micro`` version number. The micro number is bumped any time
there's a change in the API incompatible with one of the front ends. The
minor number is bumped whenever there is a project release. The major number
-will be bumped when the project is complete, and perhaps if there is a major
-change in the design."""
+will be bumped when the project is feature-complete, and perhaps if there is a
+major change in the design."""
class ApplicationError(StandardError): pass
@@ -85,7 +79,11 @@
and/or description may be `None`; no group title implies no group, just a
list of single options. Runtime settings names are derived implicitly
from long option names ("--a-setting" becomes ``settings.a_setting``) or
- explicitly from the "destination" keyword argument."""
+ explicitly from the "dest" keyword argument."""
+
+ settings_defaults = None
+ """A dictionary of defaults for internal or inaccessible (by command-line
+ or config file) settings. Override in subclasses."""
settings_default_overrides = None
"""A dictionary of auxiliary defaults, to override defaults for settings
=== Zope/lib/python/docutils/core.py 1.2 => 1.2.10.1 ===
--- Zope/lib/python/docutils/core.py:1.2 Sat Feb 1 04:26:00 2003
+++ Zope/lib/python/docutils/core.py Mon Jul 21 12:37:52 2003
@@ -15,9 +15,9 @@
__docformat__ = 'reStructuredText'
import sys
-from docutils import Component
-from docutils import frontend, io, readers, parsers, writers
-from docutils.frontend import OptionParser, ConfigParser
+from docutils import Component, __version__
+from docutils import frontend, io, utils, readers, parsers, writers
+from docutils.frontend import OptionParser
class Publisher:
@@ -87,14 +87,8 @@
#@@@ Add self.source & self.destination to components in future?
option_parser = OptionParser(
components=(settings_spec, self.parser, self.reader, self.writer),
+ defaults=defaults, read_config_files=1,
usage=usage, description=description)
- config = ConfigParser()
- config.read_standard_files()
- config_settings = config.get_section('options')
- frontend.make_paths_absolute(config_settings,
- option_parser.relative_path_settings)
- defaults.update(config_settings)
- option_parser.set_defaults(**defaults)
return option_parser
def get_settings(self, usage=None, description=None,
@@ -148,7 +142,8 @@
self.settings._destination = destination_path
self.destination = self.destination_class(
destination=destination, destination_path=destination_path,
- encoding=self.settings.output_encoding)
+ encoding=self.settings.output_encoding,
+ error_handler=self.settings.output_encoding_error_handler)
def apply_transforms(self, document):
document.transformer.populate_from_components(
@@ -157,7 +152,8 @@
document.transformer.apply_transforms()
def publish(self, argv=None, usage=None, description=None,
- settings_spec=None, settings_overrides=None):
+ settings_spec=None, settings_overrides=None,
+ enable_exit=None):
"""
Process command line options and arguments (if `self.settings` not
already set), run `self.reader` and then `self.writer`. Return
@@ -169,25 +165,52 @@
elif settings_overrides:
self.settings._update(settings_overrides, 'loose')
self.set_io()
- document = self.reader.read(self.source, self.parser, self.settings)
- self.apply_transforms(document)
- output = self.writer.write(document, self.destination)
+ exit = None
+ document = None
+ try:
+ document = self.reader.read(self.source, self.parser,
+ self.settings)
+ self.apply_transforms(document)
+ output = self.writer.write(document, self.destination)
+ except utils.SystemMessage, error:
+ if self.settings.traceback:
+ raise
+ print >>sys.stderr, ('Exiting due to level-%s (%s) system message.'
+ % (error.level,
+ utils.Reporter.levels[error.level]))
+ exit = 1
+ except Exception, error:
+ if self.settings.traceback:
+ raise
+ print >>sys.stderr, error
+ print >>sys.stderr, ("""\
+Exiting due to error. Use "--traceback" to diagnose.
+Please report errors to <docutils-users@lists.sf.net>.
+Include "--traceback" output, Docutils version (%s),
+Python version (%s), your OS type & version, and the
+command line used.""" % (__version__, sys.version.split()[0]))
+ exit = 1
if self.settings.dump_settings:
from pprint import pformat
print >>sys.stderr, '\n::: Runtime settings:'
print >>sys.stderr, pformat(self.settings.__dict__)
- if self.settings.dump_internals:
+ if self.settings.dump_internals and document:
from pprint import pformat
print >>sys.stderr, '\n::: Document internals:'
print >>sys.stderr, pformat(document.__dict__)
- if self.settings.dump_transforms:
+ if self.settings.dump_transforms and document:
from pprint import pformat
print >>sys.stderr, '\n::: Transforms applied:'
print >>sys.stderr, pformat(document.transformer.applied)
- if self.settings.dump_pseudo_xml:
+ if self.settings.dump_pseudo_xml and document:
print >>sys.stderr, '\n::: Pseudo-XML:'
print >>sys.stderr, document.pformat().encode(
'raw_unicode_escape')
+ if enable_exit and document and (document.reporter.max_level
+ >= self.settings.exit_level):
+ sys.exit(document.reporter.max_level + 10)
+ elif exit:
+ sys.exit(1)
return output
@@ -199,7 +222,7 @@
parser=None, parser_name='restructuredtext',
writer=None, writer_name='pseudoxml',
settings=None, settings_spec=None,
- settings_overrides=None, argv=None,
+ settings_overrides=None, enable_exit=1, argv=None,
usage=default_usage, description=default_description):
"""
Set up & run a `Publisher`. For command-line front ends.
@@ -220,6 +243,7 @@
subclass. Used only if no `settings` specified.
- `settings_overrides`: A dictionary containing program-specific overrides
of component settings.
+ - `enable_exit`: Boolean; enable exit status at end of processing?
- `argv`: Command-line argument list to use instead of ``sys.argv[1:]``.
- `usage`: Usage string, output if there's a problem parsing the command
line.
@@ -228,14 +252,16 @@
"""
pub = Publisher(reader, parser, writer, settings=settings)
pub.set_components(reader_name, parser_name, writer_name)
- pub.publish(argv, usage, description, settings_spec, settings_overrides)
+ pub.publish(argv, usage, description, settings_spec, settings_overrides,
+ enable_exit=enable_exit)
def publish_file(source=None, source_path=None,
destination=None, destination_path=None,
reader=None, reader_name='standalone',
parser=None, parser_name='restructuredtext',
writer=None, writer_name='pseudoxml',
- settings=None, settings_spec=None, settings_overrides=None):
+ settings=None, settings_spec=None, settings_overrides=None,
+ enable_exit=None):
"""
Set up & run a `Publisher`. For programmatic use with file-like I/O.
@@ -263,6 +289,7 @@
subclass. Used only if no `settings` specified.
- `settings_overrides`: A dictionary containing program-specific overrides
of component settings.
+ - `enable_exit`: Boolean; enable exit status at end of processing?
"""
pub = Publisher(reader, parser, writer, settings=settings)
pub.set_components(reader_name, parser_name, writer_name)
@@ -272,21 +299,27 @@
settings._update(settings_overrides, 'loose')
pub.set_source(source, source_path)
pub.set_destination(destination, destination_path)
- pub.publish()
+ pub.publish(enable_exit=enable_exit)
def publish_string(source, source_path=None, destination_path=None,
reader=None, reader_name='standalone',
parser=None, parser_name='restructuredtext',
writer=None, writer_name='pseudoxml',
settings=None, settings_spec=None,
- settings_overrides=None):
+ settings_overrides=None, enable_exit=None):
"""
Set up & run a `Publisher`, and return the string output.
For programmatic use with string I/O.
For encoded string output, be sure to set the "output_encoding" setting to
the desired encoding. Set it to "unicode" for unencoded Unicode string
- output.
+ output. Here's how::
+
+ publish_string(..., settings_overrides={'output_encoding': 'unicode'})
+
+ Similarly for Unicode string input (`source`)::
+
+ publish_string(..., settings_overrides={'input_encoding': 'unicode'})
Parameters:
@@ -312,6 +345,7 @@
subclass. Used only if no `settings` specified.
- `settings_overrides`: A dictionary containing program-specific overrides
of component settings.
+ - `enable_exit`: Boolean; enable exit status at end of processing?
"""
pub = Publisher(reader, parser, writer, settings=settings,
source_class=io.StringInput,
@@ -323,4 +357,4 @@
settings._update(settings_overrides, 'loose')
pub.set_source(source, source_path)
pub.set_destination(destination_path=destination_path)
- return pub.publish()
+ return pub.publish(enable_exit=enable_exit)
=== Zope/lib/python/docutils/frontend.py 1.2 => 1.2.10.1 ===
--- Zope/lib/python/docutils/frontend.py:1.2 Sat Feb 1 04:26:00 2003
+++ Zope/lib/python/docutils/frontend.py Mon Jul 21 12:37:52 2003
@@ -19,10 +19,13 @@
import os
import os.path
+import sys
+import types
import ConfigParser as CP
+import codecs
import docutils
-from docutils import optik
-from docutils.optik import Values
+import optik as optparse
+from optik import Values, SUPPRESS_HELP
def store_multiple(option, opt, value, parser, *args, **kwargs):
@@ -42,12 +45,85 @@
Read a configuration file during option processing. (Option callback.)
"""
config_parser = ConfigParser()
- config_parser.read(value)
+ config_parser.read(value, parser)
settings = config_parser.get_section('options')
make_paths_absolute(settings, parser.relative_path_settings,
os.path.dirname(value))
parser.values.__dict__.update(settings)
+def set_encoding(option, opt, value, parser):
+ """
+ Validate & set the encoding specified. (Option callback.)
+ """
+ try:
+ value = validate_encoding(option.dest, value)
+ except LookupError, error:
+ raise (optparse.OptionValueError('option "%s": %s' % (opt, error)),
+ None, sys.exc_info()[2])
+ setattr(parser.values, option.dest, value)
+
+def validate_encoding(name, value):
+ try:
+ codecs.lookup(value)
+ except LookupError:
+ raise (LookupError('unknown encoding: "%s"' % value),
+ None, sys.exc_info()[2])
+ return value
+
+def set_encoding_error_handler(option, opt, value, parser):
+ """
+ Validate & set the encoding error handler specified. (Option callback.)
+ """
+ try:
+ value = validate_encoding_error_handler(option.dest, value)
+ except LookupError, error:
+ raise (optparse.OptionValueError('option "%s": %s' % (opt, error)),
+ None, sys.exc_info()[2])
+ setattr(parser.values, option.dest, value)
+
+def validate_encoding_error_handler(name, value):
+ try:
+ codecs.lookup_error(value)
+ except AttributeError: # prior to Python 2.3
+ if value not in ('strict', 'ignore', 'replace'):
+ raise (LookupError(
+ 'unknown encoding error handler: "%s" (choices: '
+ '"strict", "ignore", or "replace")' % value),
+ None, sys.exc_info()[2])
+ except LookupError:
+ raise (LookupError(
+ 'unknown encoding error handler: "%s" (choices: '
+ '"strict", "ignore", "replace", "backslashreplace", '
+ '"xmlcharrefreplace", and possibly others; see documentation for '
+ 'the Python ``codecs`` module)' % value),
+ None, sys.exc_info()[2])
+ return value
+
+def set_encoding_and_error_handler(option, opt, value, parser):
+ """
+ Validate & set the encoding and error handler specified. (Option callback.)
+ """
+ try:
+ value = validate_encoding_and_error_handler(option.dest, value)
+ except LookupError, error:
+ raise (optparse.OptionValueError('option "%s": %s' % (opt, error)),
+ None, sys.exc_info()[2])
+ if ':' in value:
+ encoding, handler = value.split(':')
+ setattr(parser.values, option.dest + '_error_handler', handler)
+ else:
+ encoding = value
+ setattr(parser.values, option.dest, encoding)
+
+def validate_encoding_and_error_handler(name, value):
+ if ':' in value:
+ encoding, handler = value.split(':')
+ validate_encoding_error_handler(name + '_error_handler', handler)
+ else:
+ encoding = value
+ validate_encoding(name, encoding)
+ return value
+
def make_paths_absolute(pathdict, keys, base_path=None):
"""
Interpret filesystem path settings relative to the `base_path` given.
@@ -63,7 +139,7 @@
os.path.abspath(os.path.join(base_path, pathdict[key])))
-class OptionParser(optik.OptionParser, docutils.SettingsSpec):
+class OptionParser(optparse.OptionParser, docutils.SettingsSpec):
"""
Parser for command-line and library use. The `settings_spec`
@@ -81,6 +157,11 @@
thresholds = {'info': 1, 'warning': 2, 'error': 3, 'severe': 4, 'none': 5}
"""Lookup table for --report and --halt threshold values."""
+ if hasattr(codecs, 'backslashreplace_errors'):
+ default_error_encoding_error_handler = 'backslashreplace'
+ else:
+ default_error_encoding_error_handler = 'replace'
+
settings_spec = (
'General Docutils Options',
None,
@@ -147,17 +228,54 @@
('Same as "--halt=info": halt processing at the slightest problem.',
['--strict'], {'action': 'store_const', 'const': 'info',
'dest': 'halt_level'}),
- ('Report debug-level system messages.',
+ ('Enable a non-zero exit status for normal exit if non-halting '
+ 'system messages (at or above <level>) were generated. Levels as '
+ 'in --report. Default is 5 (disabled). Exit status is the maximum '
+ 'system message level plus 10 (11 for INFO, etc.).',
+ ['--exit'], {'choices': threshold_choices, 'dest': 'exit_level',
+ 'default': 5, 'metavar': '<level>'}),
+ ('Report debug-level system messages and generate diagnostic output.',
['--debug'], {'action': 'store_true'}),
- ('Do not report debug-level system messages.',
+ ('Do not report debug-level system messages or generate diagnostic '
+ 'output.',
['--no-debug'], {'action': 'store_false', 'dest': 'debug'}),
('Send the output of system messages (warnings) to <file>.',
['--warnings'], {'dest': 'warning_stream', 'metavar': '<file>'}),
+ ('Enable Python tracebacks when an error occurs.',
+ ['--traceback'], {'action': 'store_true', 'default': None}),
+ ('Disable Python tracebacks when errors occur; report just the error '
+ 'instead. This is the default.',
+ ['--no-traceback'], {'dest': 'traceback', 'action': 'store_false'}),
('Specify the encoding of input text. Default is locale-dependent.',
- ['--input-encoding', '-i'], {'metavar': '<name>'}),
- ('Specify the encoding for output. Default is UTF-8.',
+ ['--input-encoding', '-i'],
+ {'action': 'callback', 'callback': set_encoding,
+ 'metavar': '<name>', 'type': 'string', 'dest': 'input_encoding'}),
+ ('Specify the text encoding for output. Default is UTF-8. '
+ 'Optionally also specify the encoding error handler for unencodable '
+ 'characters (see "--error-encoding"); default is "strict".',
['--output-encoding', '-o'],
- {'metavar': '<name>', 'default': 'utf-8'}),
+ {'action': 'callback', 'callback': set_encoding_and_error_handler,
+ 'metavar': '<name[:handler]>', 'type': 'string',
+ 'dest': 'output_encoding', 'default': 'utf-8'}),
+ (SUPPRESS_HELP, # usually handled by --output-encoding
+ ['--output_encoding_error_handler'],
+ {'action': 'callback', 'callback': set_encoding_error_handler,
+ 'type': 'string', 'dest': 'output_encoding_error_handler',
+ 'default': 'strict'}),
+ ('Specify the text encoding for error output. Default is ASCII. '
+ 'Optionally also specify the encoding error handler for unencodable '
+ 'characters, after a colon (":"). Acceptable values are the same '
+ 'as for the "error" parameter of Python\'s ``encode`` string '
+ 'method. Default is "%s".' % default_error_encoding_error_handler,
+ ['--error-encoding', '-e'],
+ {'action': 'callback', 'callback': set_encoding_and_error_handler,
+ 'metavar': '<name[:handler]>', 'type': 'string',
+ 'dest': 'error_encoding', 'default': 'ascii'}),
+ (SUPPRESS_HELP, # usually handled by --error-encoding
+ ['--error_encoding_error_handler'],
+ {'action': 'callback', 'callback': set_encoding_error_handler,
+ 'type': 'string', 'dest': 'error_encoding_error_handler',
+ 'default': default_error_encoding_error_handler}),
('Specify the language of input text (ISO 639 2-letter identifier).'
' Default is "en" (English).',
['--language', '-l'], {'dest': 'language_code', 'default': 'en',
@@ -170,53 +288,60 @@
('Show this help message and exit.',
['--help', '-h'], {'action': 'help'}),
# Hidden options, for development use only:
- (optik.SUPPRESS_HELP,
- ['--dump-settings'],
- {'action': 'store_true'}),
- (optik.SUPPRESS_HELP,
- ['--dump-internals'],
- {'action': 'store_true'}),
- (optik.SUPPRESS_HELP,
- ['--dump-transforms'],
- {'action': 'store_true'}),
- (optik.SUPPRESS_HELP,
- ['--dump-pseudo-xml'],
- {'action': 'store_true'}),
- (optik.SUPPRESS_HELP,
- ['--expose-internal-attribute'],
+ (SUPPRESS_HELP, ['--dump-settings'], {'action': 'store_true'}),
+ (SUPPRESS_HELP, ['--dump-internals'], {'action': 'store_true'}),
+ (SUPPRESS_HELP, ['--dump-transforms'], {'action': 'store_true'}),
+ (SUPPRESS_HELP, ['--dump-pseudo-xml'], {'action': 'store_true'}),
+ (SUPPRESS_HELP, ['--expose-internal-attribute'],
{'action': 'append', 'dest': 'expose_internals'}),))
"""Runtime settings and command-line options common to all Docutils front
ends. Setting specs specific to individual Docutils components are also
used (see `populate_from_components()`)."""
+ settings_defaults = {'_disable_config': None}
+ """Defaults for settings that don't have command-line option equivalents."""
+
relative_path_settings = ('warning_stream',)
version_template = '%%prog (Docutils %s)' % docutils.__version__
"""Default version message."""
- def __init__(self, components=(), *args, **kwargs):
+ def __init__(self, components=(), defaults=None, read_config_files=None,
+ *args, **kwargs):
"""
`components` is a list of Docutils components each containing a
``.settings_spec`` attribute. `defaults` is a mapping of setting
default overrides.
"""
- optik.OptionParser.__init__(
- self, help=None,
- format=optik.Titled(),
- # Needed when Optik is updated (replaces above 2 lines):
- #self, add_help=None,
- #formatter=optik.TitledHelpFormatter(width=78),
+ optparse.OptionParser.__init__(
+ self, add_help_option=None,
+ formatter=optparse.TitledHelpFormatter(width=78),
*args, **kwargs)
if not self.version:
self.version = self.version_template
- # Internal settings with no defaults from settings specifications;
- # initialize manually:
- self.set_defaults(_source=None, _destination=None)
# Make an instance copy (it will be modified):
self.relative_path_settings = list(self.relative_path_settings)
- self.populate_from_components(tuple(components) + (self,))
+ self.populate_from_components((self,) + tuple(components))
+ defaults = defaults or {}
+ if read_config_files and not self.defaults['_disable_config']:
+ # @@@ Extract this code into a method, which can be called from
+ # the read_config_file callback also.
+ config = ConfigParser()
+ config.read_standard_files(self)
+ config_settings = config.get_section('options')
+ make_paths_absolute(config_settings, self.relative_path_settings)
+ defaults.update(config_settings)
+ # Internal settings with no defaults from settings specifications;
+ # initialize manually:
+ self.set_defaults(_source=None, _destination=None, **defaults)
def populate_from_components(self, components):
+ """
+ For each component, first populate from the `SettingsSpec.settings_spec`
+ structure, then from the `SettingsSpec.settings_defaults` dictionary.
+ After all components have been processed, check for and populate from
+ each component's `SettingsSpec.settings_default_overrides` dictionary.
+ """
for component in components:
if component is None:
continue
@@ -227,13 +352,15 @@
while i < len(settings_spec):
title, description, option_spec = settings_spec[i:i+3]
if title:
- group = optik.OptionGroup(self, title, description)
+ group = optparse.OptionGroup(self, title, description)
self.add_option_group(group)
else:
group = self # single options
for (help_text, option_strings, kwargs) in option_spec:
group.add_option(help=help_text, *option_strings,
**kwargs)
+ if component.settings_defaults:
+ self.defaults.update(component.settings_defaults)
i += 3
for component in components:
if component and component.settings_default_overrides:
@@ -244,6 +371,8 @@
values.report_level = self.check_threshold(values.report_level)
if hasattr(values, 'halt_level'):
values.halt_level = self.check_threshold(values.halt_level)
+ if hasattr(values, 'exit_level'):
+ values.exit_level = self.check_threshold(values.exit_level)
values._source, values._destination = self.check_args(args)
make_paths_absolute(values.__dict__, self.relative_path_settings,
os.getcwd())
@@ -262,8 +391,12 @@
source = destination = None
if args:
source = args.pop(0)
+ if source == '-': # means stdin
+ source = None
if args:
destination = args.pop(0)
+ if destination == '-': # means stdout
+ destination = None
if args:
self.error('Maximum 2 arguments allowed.')
if source and source == destination:
@@ -281,8 +414,44 @@
"""Docutils configuration files, using ConfigParser syntax (section
'options'). Later files override earlier ones."""
- def read_standard_files(self):
- self.read(self.standard_config_files)
+ validation = {
+ 'options':
+ {'input_encoding': validate_encoding,
+ 'output_encoding': validate_encoding,
+ 'output_encoding_error_handler': validate_encoding_error_handler,
+ 'error_encoding': validate_encoding,
+ 'error_encoding_error_handler': validate_encoding_error_handler}}
+ """{section: {option: validation function}} mapping, used by
+ `validate_options`. Validation functions take two parameters: name and
+ value. They return a (possibly modified) value, or raise an exception."""
+
+ def read_standard_files(self, option_parser):
+ self.read(self.standard_config_files, option_parser)
+
+ def read(self, filenames, option_parser):
+ if type(filenames) in types.StringTypes:
+ filenames = [filenames]
+ for filename in filenames:
+ CP.ConfigParser.read(self, filename)
+ self.validate_options(filename, option_parser)
+
+ def validate_options(self, filename, option_parser):
+ for section in self.validation.keys():
+ if not self.has_section(section):
+ continue
+ for option in self.validation[section].keys():
+ if self.has_option(section, option):
+ value = self.get(section, option)
+ validator = self.validation[section][option]
+ try:
+ new_value = validator(option, value)
+ except Exception, error:
+ raise (ValueError(
+ 'Error in config file "%s", section "[%s]":\n'
+ ' %s: %s\n %s = %s'
+ % (filename, section, error.__class__.__name__,
+ error, option, value)), None, sys.exc_info()[2])
+ self.set(section, option, new_value)
def optionxform(self, optionstr):
"""
=== Zope/lib/python/docutils/io.py 1.2 => 1.2.10.1 ===
--- Zope/lib/python/docutils/io.py:1.2 Sat Feb 1 04:26:00 2003
+++ Zope/lib/python/docutils/io.py Mon Jul 21 12:37:52 2003
@@ -13,6 +13,7 @@
import sys
import locale
+from types import UnicodeType
from docutils import TransformSpec
@@ -26,20 +27,9 @@
default_source_path = None
- def __init__(self, settings=None, source=None, source_path=None,
- encoding=None):
+ def __init__(self, source=None, source_path=None, encoding=None):
self.encoding = encoding
- """The character encoding for the input source."""
-
- if settings:
- if not encoding:
- self.encoding = settings.input_encoding
- import warnings, traceback
- warnings.warn(
- 'Setting input encoding via a "settings" struct is '
- 'deprecated; send encoding directly instead.\n%s'
- % ''.join(traceback.format_list(traceback.extract_stack()
- [-3:-1])))
+ """Text encoding for the input source."""
self.source = source
"""The source of input data."""
@@ -67,7 +57,8 @@
locale.setlocale(locale.LC_ALL, '')
"""
- if self.encoding and self.encoding.lower() == 'unicode':
+ if (self.encoding and self.encoding.lower() == 'unicode'
+ or isinstance(data, UnicodeType)):
return unicode(data)
encodings = [self.encoding, 'utf-8']
try:
@@ -87,8 +78,7 @@
if not enc:
continue
try:
- decoded = unicode(data, enc)
- return decoded
+ return unicode(data, enc)
except (UnicodeError, LookupError):
pass
raise UnicodeError(
@@ -106,20 +96,13 @@
default_destination_path = None
- def __init__(self, settings=None, destination=None, destination_path=None,
- encoding=None):
+ def __init__(self, destination=None, destination_path=None,
+ encoding=None, error_handler='strict'):
self.encoding = encoding
- """The character encoding for the output destination."""
+ """Text encoding for the output destination."""
- if settings:
- if not encoding:
- self.encoding = settings.output_encoding
- import warnings, traceback
- warnings.warn(
- 'Setting output encoding via a "settings" struct is '
- 'deprecated; send encoding directly instead.\n%s'
- % ''.join(traceback.format_list(traceback.extract_stack()
- [-3:-1])))
+ self.error_handler = error_handler or 'strict'
+ """Text encoding error handler."""
self.destination = destination
"""The destination for output data."""
@@ -141,7 +124,7 @@
if self.encoding and self.encoding.lower() == 'unicode':
return data
else:
- return data.encode(self.encoding or '')
+ return data.encode(self.encoding, self.error_handler)
class FileInput(Input):
@@ -150,8 +133,8 @@
Input for single, simple file-like objects.
"""
- def __init__(self, settings=None, source=None, source_path=None,
- encoding=None, autoclose=1):
+ def __init__(self, source=None, source_path=None,
+ encoding=None, autoclose=1, handle_io_errors=1):
"""
:Parameters:
- `source`: either a file-like object (which is read directly), or
@@ -160,11 +143,22 @@
- `autoclose`: close automatically after read (boolean); always
false if `sys.stdin` is the source.
"""
- Input.__init__(self, settings, source, source_path, encoding)
+ Input.__init__(self, source, source_path, encoding)
self.autoclose = autoclose
+ self.handle_io_errors = handle_io_errors
if source is None:
if source_path:
- self.source = open(source_path)
+ try:
+ self.source = open(source_path)
+ except IOError, error:
+ if not handle_io_errors:
+ raise
+ print >>sys.stderr, '%s: %s' % (error.__class__.__name__,
+ error)
+ print >>sys.stderr, (
+ 'Unable to open source file for reading (%s). Exiting.'
+ % source_path)
+ sys.exit(1)
else:
self.source = sys.stdin
self.autoclose = None
@@ -191,8 +185,9 @@
Output for single, simple file-like objects.
"""
- def __init__(self, settings=None, destination=None, destination_path=None,
- encoding=None, autoclose=1):
+ def __init__(self, destination=None, destination_path=None,
+ encoding=None, error_handler='strict', autoclose=1,
+ handle_io_errors=1):
"""
:Parameters:
- `destination`: either a file-like object (which is written
@@ -203,10 +198,11 @@
- `autoclose`: close automatically after write (boolean); always
false if `sys.stdout` is the destination.
"""
- Output.__init__(self, settings, destination, destination_path,
- encoding)
+ Output.__init__(self, destination, destination_path,
+ encoding, error_handler)
self.opened = 1
self.autoclose = autoclose
+ self.handle_io_errors = handle_io_errors
if destination is None:
if destination_path:
self.opened = None
@@ -220,7 +216,16 @@
pass
def open(self):
- self.destination = open(self.destination_path, 'w')
+ try:
+ self.destination = open(self.destination_path, 'w')
+ except IOError, error:
+ if not self.handle_io_errors:
+ raise
+ print >>sys.stderr, '%s: %s' % (error.__class__.__name__,
+ error)
+ print >>sys.stderr, ('Unable to open destination file for writing '
+ '(%s). Exiting.' % source_path)
+ sys.exit(1)
self.opened = 1
def write(self, data):
=== Zope/lib/python/docutils/nodes.py 1.2 => 1.2.10.1 ===
--- Zope/lib/python/docutils/nodes.py:1.2 Sat Feb 1 04:26:00 2003
+++ Zope/lib/python/docutils/nodes.py Mon Jul 21 12:37:52 2003
@@ -53,7 +53,14 @@
"""The line number (1-based) of the beginning of this Node in `source`."""
def __nonzero__(self):
- """Node instances are always true."""
+ """
+ Node instances are always true, even if they're empty. A node is more
+ than a simple container. Its boolean "truth" does not depend on
+ having one or more subnodes in the doctree.
+
+ Use `len()` to check node length. Use `None` to represent a boolean
+ false value.
+ """
return 1
def asdom(self, dom=xml.dom.minidom):
@@ -175,6 +182,9 @@
data = repr(self.data[:64] + ' ...')
return '<%s: %s>' % (self.tagname, data)
+ def __len__(self):
+ return len(self.data)
+
def shortrepr(self):
data = repr(self.data)
if len(data) > 20:
@@ -261,9 +271,9 @@
def _dom_node(self, domroot):
element = domroot.createElement(self.tagname)
for attribute, value in self.attributes.items():
- if type(value) is ListType:
- value = ' '.join(value)
- element.setAttribute(attribute, str(value))
+ if isinstance(value, ListType):
+ value = ' '.join(['%s' % v for v in value])
+ element.setAttribute(attribute, '%s' % value)
for child in self.children:
element.appendChild(child._dom_node(domroot))
return element
@@ -289,10 +299,13 @@
return '<%s...>' % self.tagname
def __str__(self):
+ return unicode(self).encode('raw_unicode_escape')
+
+ def __unicode__(self):
if self.children:
- return '%s%s%s' % (self.starttag(),
- ''.join([str(c) for c in self.children]),
- self.endtag())
+ return u'%s%s%s' % (self.starttag(),
+ ''.join([str(c) for c in self.children]),
+ self.endtag())
else:
return self.emptytag()
@@ -302,19 +315,19 @@
if value is None: # boolean attribute
parts.append(name)
elif isinstance(value, ListType):
- values = [str(v) for v in value]
+ values = ['%s' % v for v in value]
parts.append('%s="%s"' % (name, ' '.join(values)))
else:
- parts.append('%s="%s"' % (name, str(value)))
+ parts.append('%s="%s"' % (name, value))
return '<%s>' % ' '.join(parts)
def endtag(self):
return '</%s>' % self.tagname
def emptytag(self):
- return '<%s/>' % ' '.join([self.tagname] +
- ['%s="%s"' % (n, v)
- for n, v in self.attlist()])
+ return u'<%s/>' % ' '.join([self.tagname] +
+ ['%s="%s"' % (n, v)
+ for n, v in self.attlist()])
def __len__(self):
return len(self.children)
@@ -619,6 +632,10 @@
self.substitution_defs = {}
"""Mapping of substitution names to substitution_definition nodes."""
+ self.substitution_names = {}
+ """Mapping of case-normalized substitution names to case-sensitive
+ names."""
+
self.refnames = {}
"""Mapping of names to lists of referencing nodes."""
@@ -864,8 +881,8 @@
self.citation_refs.setdefault(ref['refname'], []).append(ref)
self.note_refname(ref)
- def note_substitution_def(self, subdef, msgnode=None):
- name = subdef['name']
+ def note_substitution_def(self, subdef, def_name, msgnode=None):
+ name = subdef['name'] = whitespace_normalize_name(def_name)
if self.substitution_defs.has_key(name):
msg = self.reporter.error(
'Duplicate substitution definition name: "%s".' % name,
@@ -874,12 +891,14 @@
msgnode += msg
oldnode = self.substitution_defs[name]
dupname(oldnode)
- # keep only the last definition
+ # keep only the last definition:
self.substitution_defs[name] = subdef
+ # case-insensitive mapping:
+ self.substitution_names[fully_normalize_name(name)] = name
- def note_substitution_ref(self, subref):
- self.substitution_refs.setdefault(
- subref['refname'], []).append(subref)
+ def note_substitution_ref(self, subref, refname):
+ name = subref['refname'] = whitespace_normalize_name(refname)
+ self.substitution_refs.setdefault(name, []).append(subref)
def note_pending(self, pending, priority=None):
self.transformer.add_pending(pending, priority)
@@ -908,6 +927,7 @@
class title(Titular, PreBibliographic, TextElement): pass
class subtitle(Titular, PreBibliographic, TextElement): pass
+class rubric(Titular, TextElement): pass
# ========================
@@ -947,13 +967,30 @@
"""
Topics are terminal, "leaf" mini-sections, like block quotes with titles,
- or textual figures. A topic is just like a section, except that it has no
+ or textual figures. A topic is just like a section, except that it has no
subsections, and it doesn't have to conform to section placement rules.
Topics are allowed wherever body elements (list, table, etc.) are allowed,
- but only at the top level of a section or document. Topics cannot nest
- inside topics or body elements; you can't have a topic inside a table,
- list, block quote, etc.
+ but only at the top level of a section or document. Topics cannot nest
+ inside topics, sidebars, or body elements; you can't have a topic inside a
+ table, list, block quote, etc.
+ """
+
+
+class sidebar(Structural, Element):
+
+ """
+ Sidebars are like miniature, parallel documents that occur inside other
+ documents, providing related or reference material. A sidebar is
+ typically offset by a border and "floats" to the side of the page; the
+ document's main text may flow around it. Sidebars can also be likened to
+ super-footnotes; their content is outside of the flow of the document's
+ main text.
+
+ Sidebars are allowed wherever body elements (list, table, etc.) are
+ allowed, but only at the top level of a section or document. Sidebars
+ cannot nest inside sidebars, topics, or body elements; you can't have a
+ sidebar inside a table, list, block quote, etc.
"""
@@ -1009,6 +1046,7 @@
class doctest_block(General, FixedTextElement): pass
class line_block(General, FixedTextElement): pass
class block_quote(General, Element): pass
+class attribution(Part, TextElement): pass
class attention(Admonition, Element): pass
class caution(Admonition, Element): pass
class danger(Admonition, Element): pass
@@ -1018,6 +1056,7 @@
class tip(Admonition, Element): pass
class hint(Admonition, Element): pass
class warning(Admonition, Element): pass
+class admonition(Admonition, Element): pass
class comment(Special, Invisible, PreBibliographic, FixedTextElement): pass
class substitution_definition(Special, Invisible, TextElement): pass
class target(Special, Invisible, Inline, TextElement, Targetable): pass
@@ -1050,8 +1089,8 @@
def astext(self):
line = self.get('line', '')
- return '%s:%s: (%s/%s) %s' % (self['source'], line, self['type'],
- self['level'], Element.astext(self))
+ return u'%s:%s: (%s/%s) %s' % (self['source'], line, self['type'],
+ self['level'], Element.astext(self))
class pending(Special, Invisible, PreBibliographic, Element):
@@ -1106,7 +1145,7 @@
internals.append('%7s%s:' % ('', key))
internals.extend(['%9s%s' % ('', line)
for line in value.pformat().splitlines()])
- elif value and type(value) == ListType \
+ elif value and isinstance(value, ListType) \
and isinstance(value[0], Node):
internals.append('%7s%s:' % ('', key))
for v in value:
@@ -1146,6 +1185,8 @@
class title_reference(Inline, TextElement): pass
class abbreviation(Inline, TextElement): pass
class acronym(Inline, TextElement): pass
+class superscript(Inline, TextElement): pass
+class subscript(Inline, TextElement): pass
class image(General, Inline, TextElement):
@@ -1154,6 +1195,7 @@
return self.get('alt', '')
+class inline(Inline, TextElement): pass
class problematic(Inline, TextElement): pass
class generated(Inline, TextElement): pass
@@ -1164,7 +1206,8 @@
node_class_names = """
Text
- abbreviation acronym address attention author authors
+ abbreviation acronym address admonition attention attribution author
+ authors
block_quote bullet_list
caption caution citation citation_reference classifier colspec comment
contact copyright
@@ -1175,15 +1218,15 @@
footnote footnote_reference
generated
header hint
- image important
+ image important inline
label legend line_block list_item literal literal_block
note
option option_argument option_group option_list option_list_item
option_string organization
paragraph pending problematic
- raw reference revision row
- section status strong substitution_definition substitution_reference
- subtitle system_message
+ raw reference revision row rubric
+ section sidebar status strong subscript substitution_definition
+ substitution_reference subtitle superscript system_message
table target tbody term tgroup thead tip title title_reference topic
transition
version
@@ -1248,11 +1291,14 @@
subclasses), subclass `NodeVisitor` instead.
"""
- # Save typing with dynamic definitions.
- for name in node_class_names:
- exec """def visit_%s(self, node): pass\n""" % name
- exec """def depart_%s(self, node): pass\n""" % name
- del name
+def _nop(self, node):
+ pass
+
+# Save typing with dynamic assignments:
+for _name in node_class_names:
+ setattr(SparseNodeVisitor, "visit_" + _name, _nop)
+ setattr(SparseNodeVisitor, "depart_" + _name, _nop)
+del _name, _nop
class GenericNodeVisitor(NodeVisitor):
@@ -1281,13 +1327,17 @@
"""Override for generic, uniform traversals."""
raise NotImplementedError
- # Save typing with dynamic definitions.
- for name in node_class_names:
- exec """def visit_%s(self, node):
- self.default_visit(node)\n""" % name
- exec """def depart_%s(self, node):
- self.default_departure(node)\n""" % name
- del name
+def _call_default_visit(self, node):
+ self.default_visit(node)
+
+def _call_default_departure(self, node):
+ self.default_departure(node)
+
+# Save typing with dynamic assignments:
+for _name in node_class_names:
+ setattr(GenericNodeVisitor, "visit_" + _name, _call_default_visit)
+ setattr(GenericNodeVisitor, "depart_" + _name, _call_default_departure)
+del _name, _call_default_visit, _call_default_departure
class TreeCopyVisitor(GenericNodeVisitor):
@@ -1385,9 +1435,9 @@
Convert `string` into an identifier and return it.
Docutils identifiers will conform to the regular expression
- ``[a-z][-a-z0-9]*``. For CSS compatibility, identifiers (the "class" and
- "id" attributes) should have no underscores, colons, or periods. Hyphens
- may be used.
+ ``[a-z](-?[a-z0-9]+)*``. For CSS compatibility, identifiers (the "class"
+ and "id" attributes) should have no underscores, colons, or periods.
+ Hyphens may be used.
- The `HTML 4.01 spec`_ defines identifiers based on SGML tokens:
@@ -1410,7 +1460,7 @@
these characters. They should be replaced with hyphens ("-"). Combined
with HTML's requirements (the first character must be a letter; no
"unicode", "latin1", or "escape" characters), this results in the
- ``[a-z][-a-z0-9]*`` pattern.
+ ``[a-z](-?[a-z0-9]+)*`` pattern.
.. _HTML 4.01 spec: http://www.w3.org/TR/html401
.. _CSS1 spec: http://www.w3.org/TR/REC-CSS1
@@ -1425,3 +1475,11 @@
def dupname(node):
node['dupname'] = node['name']
del node['name']
+
+def fully_normalize_name(name):
+ """Return a case- and whitespace-normalized name."""
+ return ' '.join(name.lower().split())
+
+def whitespace_normalize_name(name):
+ """Return a whitespace-normalized name."""
+ return ' '.join(name.split())
=== Zope/lib/python/docutils/statemachine.py 1.2 => 1.2.10.1 ===
--- Zope/lib/python/docutils/statemachine.py:1.2 Sat Feb 1 04:26:00 2003
+++ Zope/lib/python/docutils/statemachine.py Mon Jul 21 12:37:52 2003
@@ -266,7 +266,8 @@
transitions = None
state = self.get_state(next_state)
except:
- self.error()
+ if self.debug:
+ self.error()
raise
self.observers = []
return results
@@ -1294,11 +1295,11 @@
"""A `ViewList` with string-specific methods."""
- def strip_indent(self, length, start=0, end=sys.maxint):
+ def trim_left(self, length, start=0, end=sys.maxint):
"""
- Strip `length` characters off the beginning of each item, in-place,
+ Trim `length` characters off the beginning of each item, in-place,
from index `start` to `end`. No whitespace-checking is done on the
- stripped text. Does not affect slice parent.
+ trimmed text. Does not affect slice parent.
"""
self.data[start:end] = [line[length:]
for line in self.data[start:end]]
@@ -1381,8 +1382,19 @@
if first_indent is not None and block:
block.data[0] = block.data[0][first_indent:]
if indent and strip_indent:
- block.strip_indent(indent, start=(first_indent is not None))
+ block.trim_left(indent, start=(first_indent is not None))
return block, indent or 0, blank_finish
+
+ def get_2D_block(self, top, left, bottom, right, strip_indent=1):
+ block = self[top:bottom]
+ indent = right
+ for i in range(len(block.data)):
+ block.data[i] = line = block.data[i][left:right].rstrip()
+ if line:
+ indent = min(indent, len(line) - len(line.lstrip()))
+ if strip_indent and 0 < indent < right:
+ block.data = [line[indent:] for line in block.data]
+ return block
class StateMachineError(Exception): pass
=== Zope/lib/python/docutils/utils.py 1.2 => 1.2.10.1 ===
--- Zope/lib/python/docutils/utils.py:1.2 Sat Feb 1 04:26:00 2003
+++ Zope/lib/python/docutils/utils.py Mon Jul 21 12:37:52 2003
@@ -20,8 +20,9 @@
class SystemMessage(ApplicationError):
- def __init__(self, system_message):
+ def __init__(self, system_message, level):
Exception.__init__(self, system_message.astext())
+ self.level = level
class Reporter:
@@ -75,7 +76,7 @@
"""List of names for system message levels, indexed by level."""
def __init__(self, source, report_level, halt_level, stream=None,
- debug=0):
+ debug=0, encoding='ascii', error_handler='replace'):
"""
Initialize the `ConditionSet` forthe `Reporter`'s default category.
@@ -90,6 +91,8 @@
- `stream`: Where warning output is sent. Can be file-like (has a
``.write`` method), a string (file name, opened for writing), or
`None` (implies `sys.stderr`; default).
+ - `encoding`: The encoding for stderr output.
+ - `error_handler`: The error handler for stderr output encoding.
"""
self.source = source
"""The path to or description of the source data."""
@@ -99,6 +102,12 @@
elif type(stream) in (StringType, UnicodeType):
raise NotImplementedError('This should open a file for writing.')
+ self.encoding = encoding
+ """The character encoding for the stderr output."""
+
+ self.error_handler = error_handler
+ """The character encoding error handler."""
+
self.categories = {'': ConditionSet(debug, report_level, halt_level,
stream)}
"""Mapping of category names to conditions. Default category is ''."""
@@ -107,6 +116,9 @@
"""List of bound methods or functions to call with each system_message
created."""
+ self.max_level = -1
+ """The highest level system message generated so far."""
+
def set_conditions(self, category, report_level, halt_level,
stream=None, debug=0):
if stream is None:
@@ -164,14 +176,16 @@
*children, **attributes)
debug, report_level, halt_level, stream = self[category].astuple()
if level >= report_level or debug and level == 0:
+ msgtext = msg.astext().encode(self.encoding, self.error_handler)
if category:
- print >>stream, msg.astext(), '[%s]' % category
+ print >>stream, msgtext, '[%s]' % category
else:
- print >>stream, msg.astext()
+ print >>stream, msgtext
if level >= halt_level:
- raise SystemMessage(msg)
+ raise SystemMessage(msg, level)
if level > 0 or debug:
self.notify_observers(msg)
+ self.max_level = max(level, self.max_level)
return msg
def debug(self, *args, **kwargs):
@@ -368,10 +382,6 @@
attlist.append((attname.lower(), data))
return attlist
-def normalize_name(name):
- """Return a case- and whitespace-normalized name."""
- return ' '.join(name.lower().split())
-
def new_document(source, settings=None):
"""
Return a new empty document object.
@@ -385,7 +395,9 @@
if settings is None:
settings = frontend.OptionParser().get_default_values()
reporter = Reporter(source, settings.report_level, settings.halt_level,
- settings.warning_stream, settings.debug)
+ stream=settings.warning_stream, debug=settings.debug,
+ encoding=settings.error_encoding,
+ error_handler=settings.error_encoding_error_handler)
document = nodes.document(settings, reporter, source=source)
document.note_source(source, -1)
return document
@@ -401,7 +413,7 @@
def relative_path(source, target):
"""
- Build and return a path to `target`, relative to `source`.
+ Build and return a path to `target`, relative to `source` (both files).
If there is no common prefix, return the absolute path to `target`.
"""
@@ -426,7 +438,7 @@
def get_source_line(node):
"""
Return the "source" and "line" attributes from the `node` given or from
- it's closest ancestor.
+ its closest ancestor.
"""
while node:
if node.source or node.line: