summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormilde <milde@929543f6-e4f2-0310-98a6-ba3bd3dd1d04>2011-05-19 08:56:27 +0000
committermilde <milde@929543f6-e4f2-0310-98a6-ba3bd3dd1d04>2011-05-19 08:56:27 +0000
commitfd72f55d904bdea4e2fea83fb3b612555f8b8909 (patch)
treeb8b357be72ac2c45f9cb081dd2cc2084d7b914e6
parentd4fe2cf3a195f507e4473e87dd120e134e7d1698 (diff)
downloaddocutils-fd72f55d904bdea4e2fea83fb3b612555f8b8909.tar.gz
New sub-module and test suite for error reporting.
* Code to handle encoding/decoding errors when reporting exceptions. * Test and fix error reporting with problematic locale settings. (https://bugs.gentoo.org/show_bug.cgi?id=349101) git-svn-id: http://svn.code.sf.net/p/docutils/code/trunk@7037 929543f6-e4f2-0310-98a6-ba3bd3dd1d04
-rw-r--r--docutils/HISTORY.txt5
-rw-r--r--docutils/docutils/core.py10
-rw-r--r--docutils/docutils/error_reporting.py193
-rw-r--r--docutils/docutils/frontend.py13
-rw-r--r--docutils/docutils/io.py207
-rw-r--r--docutils/docutils/parsers/rst/directives/misc.py41
-rw-r--r--docutils/docutils/parsers/rst/directives/tables.py10
-rw-r--r--docutils/docutils/parsers/rst/states.py4
-rw-r--r--docutils/docutils/statemachine.py2
-rw-r--r--docutils/docutils/utils.py18
-rw-r--r--docutils/test/test_error_reporting.py339
-rwxr-xr-xdocutils/test/test_io.py153
-rwxr-xr-xdocutils/test/test_parsers/test_rst/test_directives/test_raw.py2
-rwxr-xr-xdocutils/tools/buildhtml.py18
14 files changed, 601 insertions, 414 deletions
diff --git a/docutils/HISTORY.txt b/docutils/HISTORY.txt
index befebec79..49ef25555 100644
--- a/docutils/HISTORY.txt
+++ b/docutils/HISTORY.txt
@@ -27,11 +27,10 @@ Changes Since 0.7
- Decode command line arguments with the locale's preferred encoding
(to allow, e.g., ``--title=Dornröschen``).
- Orphaned "python" reader and "newlatex2e" writer moved to the sandbox.
- - Work around Issue2517_ to allow unicode messages in `IOError`
- instances with Python < 2.6.
+ - New sub-module `error_reporting`: handle encoding/decoding errors
+ when reporting exceptions.
.. _BCP 47: http://www.rfc-editor.org/rfc/bcp/bcp47.txt
- .. _Issue2517: http://bugs.python.org/issue2517
* tools/buildhtml.py:
diff --git a/docutils/docutils/core.py b/docutils/docutils/core.py
index 91275f4a2..620c01ac4 100644
--- a/docutils/docutils/core.py
+++ b/docutils/docutils/core.py
@@ -20,7 +20,7 @@ from docutils import __version__, __version_details__, SettingsSpec
from docutils import frontend, io, utils, readers, writers
from docutils.frontend import OptionParser
from docutils.transforms import Transformer
-from docutils.io import ErrorOutput
+from docutils.error_reporting import ErrorOutput, ErrorString
import docutils.readers.doctree
class Publisher:
@@ -152,6 +152,8 @@ class Publisher:
option_parser = self.setup_option_parser(
usage, description, settings_spec, config_section, **defaults)
if argv is None:
+ # converting to Unicode (Python 3 does this automatically):
+ # TODO: make this failsafe and reversible
argv_encoding = (sys.stdin.encoding or frontend.locale_encoding
or 'ascii')
argv = [a.decode(argv_encoding) for a in sys.argv[1:]]
@@ -258,7 +260,7 @@ class Publisher:
elif isinstance(error, UnicodeEncodeError):
self.report_UnicodeError(error)
else:
- print >>self._stderr, '%s: %s' % (error.__class__.__name__, error)
+ print >>self._stderr, u'%s' % ErrorString(error)
print >>self._stderr, ("""\
Exiting due to error. Use "--traceback" to diagnose.
Please report errors to <docutils-users@lists.sf.net>.
@@ -275,7 +277,7 @@ command line used.""" % (__version__, __version_details__,
def report_UnicodeError(self, error):
data = error.object[error.start:error.end]
self._stderr.write(
- '%s: %s\n'
+ '%s\n'
'\n'
'The specified output encoding (%s) cannot\n'
'handle all of the output.\n'
@@ -295,7 +297,7 @@ command line used.""" % (__version__, __version_details__,
'Include "--traceback" output, Docutils version (%s),\n'
'Python version (%s), your OS type & version, and the\n'
'command line used.\n'
- % (error.__class__.__name__, error,
+ % (ErrorString(error),
self.settings.output_encoding,
data.encode('ascii', 'xmlcharrefreplace'),
data.encode('ascii', 'backslashreplace'),
diff --git a/docutils/docutils/error_reporting.py b/docutils/docutils/error_reporting.py
new file mode 100644
index 000000000..a00685aa6
--- /dev/null
+++ b/docutils/docutils/error_reporting.py
@@ -0,0 +1,193 @@
+#!/usr/bin/env python
+# -*- coding: utf8 -*-
+# :Copyright: © 2011 Günter Milde.
+# Released without warranties or conditions of any kind
+# under the terms of the Apache License, Version 2.0
+# http://www.apache.org/licenses/LICENSE-2.0
+# :Id: $Id$
+
+"""
+Error reporting should be safe from encoding/decoding errors.
+However, implicit conversions of strings and exceptions like
+
+>>> u'%s world: %s' % ('H\xe4llo', Exception(u'H\xe4llo')
+
+fail in some Python versions:
+
+* In Python <= 2.6, ``unicode(<exception instance>)`` uses
+ `__str__` and fails with non-ASCII chars in`unicode` arguments.
+ (work around http://bugs.python.org/issue2517):
+
+* In Python 2, unicode(<exception instance>) fails, with non-ASCII
+ chars in arguments. (Use case: in some locales, the errstr
+ argument of IOError contains non-ASCII chars.)
+
+* In Python 2, str(<exception instance>) fails, with non-ASCII chars
+ in `unicode` arguments.
+
+The `SafeString`, `ErrorString` and `ErrorOutput` classes handle
+common exceptions.
+"""
+
+import sys, codecs
+
+# Guess the locale's encoding.
+# If no valid guess can be made, locale_encoding is set to `None`:
+try:
+ import locale # module missing in Jython
+except ImportError:
+ locale_encoding = None
+else:
+ locale_encoding = locale.getlocale()[1] or locale.getdefaultlocale()[1]
+ # locale.getpreferredencoding([do_setlocale=True|False])
+ # has side-effects | might return a wrong guess.
+ # (cf. Update 1 in http://stackoverflow.com/questions/4082645/using-python-2-xs-locale-module-to-format-numbers-and-currency)
+ try:
+ codecs.lookup(locale_encoding or '') # None -> ''
+ except LookupError:
+ locale_encoding = None
+
+
+
+class SafeString(object):
+ """
+ A wrapper providing robust conversion to `str` and `unicode`.
+ """
+
+ def __init__(self, data, encoding=None, encoding_errors='backslashreplace',
+ decoding_errors='replace'):
+ self.data = data
+ self.encoding = (encoding or getattr(data, 'encoding', None) or
+ locale_encoding or 'ascii')
+ self.encoding_errors = encoding_errors
+ self.decoding_errors = decoding_errors
+
+
+ def __str__(self):
+ try:
+ return str(self.data)
+ except UnicodeEncodeError, err:
+ if isinstance(self.data, Exception):
+ args = [str(SafeString(arg, self.encoding,
+ self.encoding_errors))
+ for arg in self.data.args]
+ return ', '.join(args)
+ if isinstance(self.data, unicode):
+ return self.data.encode(self.encoding, self.encoding_errors)
+ raise
+
+ def __unicode__(self):
+ """
+ Return unicode representation of `self.data`.
+
+ Try ``unicode(self.data)``, catch `UnicodeError` and
+
+ * if `self.data` is an Exception instance, work around
+ http://bugs.python.org/issue2517 with an emulation of
+ Exception.__unicode__,
+
+ * else decode with `self.encoding` and `self.decoding_errors`.
+ """
+ try:
+ return unicode(self.data)
+ except UnicodeError, error: # catch ..Encode.. and ..Decode.. errors
+ if isinstance(self.data, EnvironmentError):
+ return u"[Errno %s] %s: '%s'" % (self.data.errno,
+ SafeString(self.data.strerror, self.encoding,
+ self.decoding_errors),
+ SafeString(self.data.filename, self.encoding,
+ self.decoding_errors))
+ if isinstance(self.data, Exception):
+ args = [unicode(SafeString(arg, self.encoding,
+ decoding_errors=self.decoding_errors))
+ for arg in self.data.args]
+ return u', '.join(args)
+ if isinstance(error, UnicodeDecodeError):
+ return unicode(self.data, self.encoding, self.decoding_errors)
+ raise
+
+class ErrorString(SafeString):
+ """
+ Safely report exception type and message.
+ """
+ def __str__(self):
+ return '%s: %s' % (self.data.__class__.__name__,
+ super(ErrorString, self).__str__())
+
+ def __unicode__(self):
+ return u'%s: %s' % (self.data.__class__.__name__,
+ super(ErrorString, self).__unicode__())
+
+
+class ErrorOutput(object):
+ """
+ Wrapper class for file-like error streams with
+ failsave de- and encoding of `str`, `bytes`, `unicode` and
+ `Exception` instances.
+ """
+
+ def __init__(self, stream=None, encoding=None,
+ encoding_errors='backslashreplace',
+ decoding_errors='replace'):
+ """
+ :Parameters:
+ - `stream`: a file-like object (which is written to),
+ a string (opended as a file),
+ `None` (bind to `sys.stderr`; default).
+ If evaluating to `False` (but not `None`),
+ write() requests are ignored.
+ - `encoding`: `stream` text encoding. Guessed if None.
+ - `encoding_errors`: how to treat encoding errors.
+ """
+ if stream is None:
+ stream = sys.stderr
+ elif not(stream):
+ stream = False
+ # if `stream` is a file name, open it
+ elif isinstance(stream, str):
+ stream = open(stream, 'w')
+ elif isinstance(stream, unicode):
+ stream = open(stream.encode(sys.getfilesystemencoding()), 'w')
+
+ self.stream = stream
+ """Where warning output is sent."""
+
+ self.encoding = (encoding or getattr(stream, 'encoding', None) or
+ locale_encoding or 'ascii')
+ """The output character encoding."""
+
+ self.encoding_errors = encoding_errors
+ """Encoding error handler."""
+
+ self.decoding_errors = decoding_errors
+ """Decoding error handler."""
+
+ def write(self, data):
+ """
+ Write `data` to self.stream. Ignore, if self.stream is False.
+
+ `data` can be a `string`, `unicode`, or `Exception` instance.
+ """
+ if self.stream is False:
+ return
+ if isinstance(data, Exception):
+ data = unicode(SafeString(data, self.encoding,
+ self.encoding_errors, self.decoding_errors))
+ try:
+ self.stream.write(data)
+ except UnicodeEncodeError:
+ self.stream.write(data.encode(self.encoding, self.encoding_errors))
+ except TypeError: # in Python 3, stderr expects unicode
+ self.stream.write(unicode(data, self.encoding, self.decoding_errors))
+
+ def close(self):
+ """
+ Close the error-output stream.
+
+ Ignored if the stream has no close() method.
+ """
+ try:
+ self.stream.close()
+ except AttributeError:
+ pass
+
diff --git a/docutils/docutils/frontend.py b/docutils/docutils/frontend.py
index 0f78f18be..a5c1a9126 100644
--- a/docutils/docutils/frontend.py
+++ b/docutils/docutils/frontend.py
@@ -38,7 +38,7 @@ from optparse import SUPPRESS_HELP
import docutils
import docutils.utils
import docutils.nodes
-from docutils.io import locale_encoding, ErrorOutput
+from docutils.error_reporting import locale_encoding, ErrorOutput
def store_multiple(option, opt, value, parser, *args, **kwargs):
@@ -276,8 +276,8 @@ class Option(optparse.Option):
new_value = self.validator(setting, value, parser)
except Exception, error:
raise (optparse.OptionValueError(
- 'Error in option "%s":\n %s: %s'
- % (opt, error.__class__.__name__, error)),
+ 'Error in option "%s":\n %s'
+ % (opt, ErrorString(error))),
None, sys.exc_info()[2])
setattr(values, setting, new_value)
if self.overrides:
@@ -753,9 +753,10 @@ Skipping "%s" configuration file.
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])
+ ' %s\n'
+ ' %s = %s'
+ % (filename, section, ErrorString(error),
+ setting, value)), None, sys.exc_info()[2])
self.set(section, setting, new_value)
if option.overrides:
self.set(section, option.overrides, None)
diff --git a/docutils/docutils/io.py b/docutils/docutils/io.py
index 796cb2288..e58f8e9e8 100644
--- a/docutils/docutils/io.py
+++ b/docutils/docutils/io.py
@@ -13,25 +13,8 @@ import sys
import re
import codecs
from docutils import TransformSpec
-from docutils._compat import b, bytes
-
-
-# Guess the locale's encoding.
-# If no valid guess can be made, locale_encoding is set to `None`:
-try:
- import locale # module missing in Jython
-except ImportError:
- locale_encoding = None
-else:
- locale_encoding = locale.getlocale()[1] or locale.getdefaultlocale()[1]
- # locale.getpreferredencoding([do_setlocale=True|False])
- # has side-effects | might return a wrong guess.
- # (cf. Update 1 in http://stackoverflow.com/questions/4082645/using-python-2-xs-locale-module-to-format-numbers-and-currency)
- try:
- codecs.lookup(locale_encoding or '') # None -> ''
- except LookupError:
- locale_encoding = None
-
+from docutils._compat import b
+from docutils.error_reporting import locale_encoding, ErrorString, ErrorOutput
class Input(TransformSpec):
@@ -101,30 +84,23 @@ class Input(TransformSpec):
# Apply heuristics only if no encoding is explicitly given and
# no BOM found. Start with UTF-8, because that only matches
# data that *IS* UTF-8:
- encodings = ['utf-8',
- locale_encoding,
- 'latin-1', # fallback encoding
- ]
- error = None
- error_details = ''
+ encodings = [enc for enc in ('utf-8',
+ locale_encoding, # can be None
+ 'latin-1') # fallback encoding
+ if enc]
for enc in encodings:
- if not enc:
- continue
try:
decoded = unicode(data, enc, self.error_handler)
self.successful_encoding = enc
# Return decoded, removing BOMs.
return decoded.replace(u'\ufeff', u'')
- except (UnicodeError, LookupError), tmperror:
- error = tmperror # working around Python 3 deleting the
- # error variable after the except clause
- if error is not None:
- error_details = '\n(%s: %s)' % (error.__class__.__name__, error)
+ except (UnicodeError, LookupError), err:
+ error = err # in Python 3, the <exception instance> is
+ # local to the except clause
raise UnicodeError(
'Unable to decode input data. Tried the following encodings: '
- '%s.%s'
- % (', '.join([repr(enc) for enc in encodings if enc]),
- error_details))
+ '%s.\n(%s)' % (', '.join([repr(enc) for enc in encodings]),
+ ErrorString(error)))
coding_slug = re.compile(b("coding[:=]\s*([-\w.]+)"))
"""Encoding declaration pattern."""
@@ -200,160 +176,6 @@ class Output(TransformSpec):
else:
return data.encode(self.encoding, self.error_handler)
-# Robust error reporting
-# -----------------------
-#
-# Implicit conversions of strings and exceptions like
-#
-# >>> u'%s world' % 'H\xe4llo'
-#
-# fail in some Python versions:
-#
-# * In Python <= 2.6, ``unicode(<exception instance>)`` uses
-# `__str__` and fails with non-ASCII chars in`unicode` arguments.
-# (work around http://bugs.python.org/issue2517):
-#
-# * In Python 2, unicode(<exception instance>) fails, with non-ASCII
-# chars in `str` arguments. (Use case: in some locales, the errstr
-# argument of IOError contains non-ASCII chars.)
-#
-# * In Python 2, str(<exception instance>) fails, with non-ASCII chars
-# in `unicode` arguments.
-#
-# However, when reporting an error we do not want to mask ist with
-# encoding/decoding errors. The `ErrString` and `ErrorOutput` classes
-# handle common exceptions:
-
-class ErrorString(object):
- """
- A wrapper providing robust (bytes and unicode) string conversion.
- """
-
- def __init__(self, data, encoding=None, encoding_errors='backslashreplace',
- decoding_errors='replace'):
- self.data = data
- self.encoding = (encoding or getattr(data, 'encoding', None) or
- locale_encoding or 'ascii')
- self.encoding_errors = encoding_errors
- self.decoding_errors = decoding_errors
-
-
- def __str__(self):
- try:
- return str(self.data)
- except UnicodeEncodeError, err:
- if isinstance(self.data, Exception):
- args = [str(ErrorString(arg, self.encoding,
- self.encoding_errors))
- for arg in self.data.args]
- return ', '.join(args)
- if isinstance(self.data, unicode):
- return self.data.encode(self.encoding, self.encoding_errors)
- raise
-
- def __unicode__(self):
- """
- Return unicode representation of `self.data`.
-
- Try ``unicode(self.data)``, catch `UnicodeError` and
-
- * if `self.data` is an Exception instance, work around
- http://bugs.python.org/issue2517 with an emulation of
- Exception.__unicode__,
-
- * else decode with `self.encoding` and `self.decoding_errors`.
- """
- try:
- return unicode(self.data)
- except UnicodeError, err: # can be ..EncodeError or ..DecodeError
- if isinstance(self.data, IOError):
- return u"[Errno %d] %s: '%s'" % (self.data.errno,
- ErrorString(self.data.strerror, self.encoding,
- self.decoding_errors),
- ErrorString(self.data.filename, self.encoding,
- self.decoding_errors))
- if isinstance(self.data, Exception):
- args = [unicode(ErrorString(arg, self.encoding,
- decoding_errors=self.decoding_errors))
- for arg in self.data.args]
- return u', '.join(args)
- if isinstance(err, UnicodeDecodeError):
- return unicode(self.data, self.encoding, self.decoding_errors)
- raise
-
-
-class ErrorOutput(object):
- """
- Wrapper class for file-like error streams with
- failsave de- and encoding of `str`, `bytes`, `unicode` and
- `Exception` instances.
- """
-
- def __init__(self, stream=None, encoding=None,
- encoding_errors='backslashreplace',
- decoding_errors='replace'):
- """
- :Parameters:
- - `stream`: a file-like object (which is written to),
- a string (opended as a file),
- `None` (bind to `sys.stderr`; default).
- If evaluating to `False` (but not `None`),
- write() requests are ignored.
- - `encoding`: `stream` text encoding. Guessed if None.
- - `encoding_errors`: how to treat encoding errors.
- """
- if stream is None:
- stream = sys.stderr
- elif not(stream):
- stream = False
- # if `stream` is a file name, open it
- elif isinstance(stream, bytes):
- stream = open(stream, 'w')
- elif isinstance(stream, unicode):
- stream = open(stream.encode(sys.getfilesystemencoding()), 'w')
-
- self.stream = stream
- """Where warning output is sent."""
-
- self.encoding = (encoding or getattr(stream, 'encoding', None) or
- locale_encoding or 'ascii')
- """The output character encoding."""
-
- self.encoding_errors = encoding_errors
- """Encoding error handler."""
-
- self.decoding_errors = decoding_errors
- """Decoding error handler."""
-
- def write(self, data):
- """
- Write `data` to self.stream. Ignore, if self.stream is False.
-
- `data` can be a `string`, `unicode`, or `Exception` instance.
- """
- if self.stream is False:
- return
- if isinstance(data, Exception):
- data = unicode(ErrorString(data, self.encoding,
- self.encoding_errors, self.decoding_errors))
- try:
- self.stream.write(data)
- except UnicodeEncodeError:
- self.stream.write(data.encode(self.encoding, self.encoding_errors))
- except TypeError: # in Python 3, stderr expects unicode
- self.stream.write(unicode(data, self.encoding, self.decoding_errors))
-
- def close(self):
- """
- Close the error-output stream.
-
- Ignored if the stream has no close() method.
- """
- try:
- self.stream.close()
- except AttributeError:
- pass
-
class FileInput(Input):
@@ -396,8 +218,7 @@ class FileInput(Input):
except IOError, error:
if not handle_io_errors:
raise
- print >>self._stderr, '%s: %s' % (
- error.__class__.__name__, error)
+ print >>self._stderr, ErrorString(error)
print >>self._stderr, (u'Unable to open source'
u" file for reading ('%s'). Exiting." % source_path)
sys.exit(1)
@@ -489,7 +310,7 @@ class FileOutput(Output):
except IOError, error:
if not self.handle_io_errors:
raise
- print >>self._stderr, '%s: %s' % (error.__class__.__name__, error)
+ print >>self._stderr, ErrorString(error)
print >>self._stderr, (u'Unable to open destination file'
u" for writing ('%s'). Exiting." % self.destination_path)
sys.exit(1)
@@ -528,7 +349,7 @@ class BinaryFileOutput(FileOutput):
except IOError, error:
if not self.handle_io_errors:
raise
- print >>self._stderr, '%s: %s' % (error.__class__.__name__, error)
+ print >>self._stderr, ErrorString(error)
print >>self._stderr, (u'Unable to open destination file'
u" for writing ('%s'). Exiting." % self.destination_path)
sys.exit(1)
diff --git a/docutils/docutils/parsers/rst/directives/misc.py b/docutils/docutils/parsers/rst/directives/misc.py
index 5cca85045..43f299bab 100644
--- a/docutils/docutils/parsers/rst/directives/misc.py
+++ b/docutils/docutils/parsers/rst/directives/misc.py
@@ -11,7 +11,7 @@ import os.path
import re
import time
from docutils import io, nodes, statemachine, utils
-from docutils.io import ErrorString
+from docutils.error_reporting import SafeString, ErrorString
from docutils.parsers.rst import Directive, convert_directive_function
from docutils.parsers.rst import directives, roles, states
from docutils.transforms import misc
@@ -67,8 +67,8 @@ class Include(Directive):
input_encoding_error_handler),
handle_io_errors=None)
except IOError, error:
- raise self.severe(u'Problems with "%s" directive path:\n%s: %s.' %
- (self.name, error.__class__.__name__, ErrorString(error)))
+ raise self.severe(u'Problems with "%s" directive path:\n%s.' %
+ (self.name, ErrorString(error)))
startline = self.options.get('start-line', None)
endline = self.options.get('end-line', None)
try:
@@ -78,9 +78,8 @@ class Include(Directive):
else:
rawtext = include_file.read()
except UnicodeError, error:
- raise self.severe(
- 'Problem with "%s" directive:\n%s: %s'
- % (self.name, error.__class__.__name__, error))
+ raise self.severe(u'Problem with "%s" directive:\n%s' %
+ (self.name, ErrorString(error)))
# start-after/end-before: no restrictions on newlines in match-text,
# and no restrictions on matching inside lines vs. line boundaries
after_text = self.options.get('start-after', None)
@@ -167,14 +166,13 @@ class Raw(Directive):
input_encoding_error_handler),
handle_io_errors=None)
except IOError, error:
- raise self.severe('Problems with "%s" directive path:\n%s.'
- % (self.name, error))
+ raise self.severe(u'Problems with "%s" directive path:\n%s.'
+ % (self.name, ErrorString(error)))
try:
text = raw_file.read()
except UnicodeError, error:
- raise self.severe(
- 'Problem with "%s" directive:\n%s: %s'
- % (self.name, error.__class__.__name__, error))
+ raise self.severe(u'Problem with "%s" directive:\n%s'
+ % (self.name, ErrorString(error)))
attributes['source'] = path
elif 'url' in self.options:
source = self.options['url']
@@ -185,9 +183,8 @@ class Raw(Directive):
try:
raw_text = urllib2.urlopen(source).read()
except (urllib2.URLError, IOError, OSError), error:
- raise self.severe(
- 'Problems with "%s" directive URL "%s":\n%s.'
- % (self.name, self.options['url'], error))
+ raise self.severe(u'Problems with "%s" directive URL "%s":\n%s.'
+ % (self.name, self.options['url'], ErrorString(error)))
raw_file = io.StringInput(
source=raw_text, source_path=source, encoding=encoding,
error_handler=(self.state.document.settings.\
@@ -195,9 +192,8 @@ class Raw(Directive):
try:
text = raw_file.read()
except UnicodeError, error:
- raise self.severe(
- 'Problem with "%s" directive:\n%s: %s'
- % (self.name, error.__class__.__name__, error))
+ raise self.severe(u'Problem with "%s" directive:\n%s'
+ % (self.name, ErrorString(error)))
attributes['source'] = source
else:
# This will always fail because there is no content.
@@ -275,10 +271,9 @@ class Unicode(Directive):
for code in codes:
try:
decoded = directives.unicode_code(code)
- except ValueError, err:
- raise self.error(
- 'Invalid character code: %s\n%s: %s'
- % (code, err.__class__.__name__, err))
+ except ValueError, error:
+ raise self.error(u'Invalid character code: %s\n%s'
+ % (code, ErrorString(error)))
element += nodes.Text(decoded)
return element.children
@@ -373,8 +368,8 @@ class Role(Directive):
options['class'] = directives.class_option(new_role_name)
except ValueError, detail:
error = self.state_machine.reporter.error(
- 'Invalid argument for "%s" directive:\n%s.'
- % (self.name, detail), nodes.literal_block(
+ u'Invalid argument for "%s" directive:\n%s.'
+ % (self.name, SafeString(detail)), nodes.literal_block(
self.block_text, self.block_text), line=self.lineno)
return messages + [error]
role = roles.CustomRole(new_role_name, base_role, options, content)
diff --git a/docutils/docutils/parsers/rst/directives/tables.py b/docutils/docutils/parsers/rst/directives/tables.py
index 8beca5142..748557620 100644
--- a/docutils/docutils/parsers/rst/directives/tables.py
+++ b/docutils/docutils/parsers/rst/directives/tables.py
@@ -14,6 +14,7 @@ import os.path
import csv
from docutils import io, nodes, statemachine, utils
+from docutils.error_reporting import SafeString
from docutils.utils import SystemMessagePropagation
from docutils.parsers.rst import Directive
from docutils.parsers.rst import directives
@@ -274,9 +275,10 @@ class CSVTable(Table):
csv_data = csv_file.read().splitlines()
except IOError, error:
severe = self.state_machine.reporter.severe(
- 'Problems with "%s" directive path:\n%s.'
- % (self.name, error), nodes.literal_block(
- self.block_text, self.block_text), line=self.lineno)
+ u'Problems with "%s" directive path:\n%s.'
+ % (self.name, SafeString(error)),
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno)
raise SystemMessagePropagation(severe)
elif 'url' in self.options:
# CSV data is from a URL.
@@ -290,7 +292,7 @@ class CSVTable(Table):
except (urllib2.URLError, IOError, OSError, ValueError), error:
severe = self.state_machine.reporter.severe(
'Problems with "%s" directive URL "%s":\n%s.'
- % (self.name, self.options['url'], error),
+ % (self.name, self.options['url'], SafeString(error)),
nodes.literal_block(self.block_text, self.block_text),
line=self.lineno)
raise SystemMessagePropagation(severe)
diff --git a/docutils/docutils/parsers/rst/states.py b/docutils/docutils/parsers/rst/states.py
index ef9b9a0a4..b76cd48c3 100644
--- a/docutils/docutils/parsers/rst/states.py
+++ b/docutils/docutils/parsers/rst/states.py
@@ -1443,8 +1443,8 @@ class Body(RSTState):
except MarkupError, error:
# This shouldn't happen; pattern won't match.
src, srcline = self.state_machine.get_source_and_line()
- msg = self.reporter.error('Invalid option list marker: %s' %
- str(error), source=src, line=srcline)
+ msg = self.reporter.error(u'Invalid option list marker: %s' %
+ error, source=src, line=srcline)
self.parent += msg
indented, indent, line_offset, blank_finish = \
self.state_machine.get_first_known_indented(match.end())
diff --git a/docutils/docutils/statemachine.py b/docutils/docutils/statemachine.py
index decaaf17f..6c0c6fc03 100644
--- a/docutils/docutils/statemachine.py
+++ b/docutils/docutils/statemachine.py
@@ -110,7 +110,7 @@ import sys
import re
import types
import unicodedata
-from docutils.io import ErrorOutput
+from docutils.error_reporting import ErrorOutput
class StateMachine:
diff --git a/docutils/docutils/utils.py b/docutils/docutils/utils.py
index 950318146..fd9b0ec2e 100644
--- a/docutils/docutils/utils.py
+++ b/docutils/docutils/utils.py
@@ -15,8 +15,7 @@ import warnings
import unicodedata
from docutils import ApplicationError, DataError
from docutils import nodes
-from docutils.io import ErrorOutput
-from docutils._compat import bytes
+from docutils.error_reporting import ErrorOutput, SafeString
class SystemMessage(ApplicationError):
@@ -154,19 +153,8 @@ class Reporter:
Raise an exception or generate a warning if appropriate.
"""
# `message` can be a `string`, `unicode`, or `Exception` instance.
- # Convert now to detect errors:
- try:
- message = unicode(message)
- except UnicodeError, err:
- # In Python < 2.6, # unicode(<exception instance>) uses __str__
- # and fails with non-ASCII chars in arguments
- if sys.version_info < (2,6):
- try:
- message = u', '.join(message.args)
- except AttributeError:
- raise err
- else:
- raise err
+ if isinstance(message, Exception):
+ message = SafeString(message)
attributes = kwargs.copy()
if 'base_node' in kwargs:
diff --git a/docutils/test/test_error_reporting.py b/docutils/test/test_error_reporting.py
new file mode 100644
index 000000000..61c832096
--- /dev/null
+++ b/docutils/test/test_error_reporting.py
@@ -0,0 +1,339 @@
+#! /usr/bin/env python
+# .. coding: utf8
+# $Id$
+# Author: Günter Milde <milde@users.sourceforge.net>
+# Copyright: This module has been placed in the public domain.
+
+"""
+Test `EnvironmentError` reporting.
+
+In some locales, the `errstr` argument of IOError and OSError contains
+non-ASCII chars.
+
+In Python 2, converting an exception instance to `str` or `unicode`
+might fail, with non-ASCII chars in arguments and the default encoding
+and errors ('ascii', 'strict').
+
+Therefore, Docutils must not use string interpolation with exception
+instances like, e.g., ::
+
+ try:
+ something
+ except IOError, error:
+ print 'Found %s' % error
+
+unless the minimal required Python version has this problem fixed.
+"""
+
+import unittest
+import sys, os
+import codecs
+try: # from standard library module `io`
+ from io import StringIO, BytesIO
+except ImportError: # new in Python 2.6
+ from StringIO import StringIO
+ BytesIO = StringIO
+
+import DocutilsTestSupport # must be imported before docutils
+from docutils import core, parsers, frontend, utils
+from docutils.error_reporting import SafeString, ErrorString, ErrorOutput
+from docutils._compat import b, bytes
+
+oldlocale = None
+if sys.version_info < (3,0): # problems solved in py3k
+ try:
+ import locale # module missing in Jython
+ oldlocale = locale.getlocale()
+ # Why does getlocale return the defaultlocale in Python 3.2 ????
+ # oldlocale = (None, None) # test suite runs without locale
+ except ImportError:
+ print ('cannot test error reporting with problematic locales,\n'
+ '`import locale` failed.')
+
+
+# locales confirmed to use non-ASCII chars in the IOError message
+# for a missing file (https://bugs.gentoo.org/show_bug.cgi?id=349101)
+# TODO: add more confirmed problematic locales
+problematic_locales = ['cs_CZ', 'cs_CZ.UTF8',
+ 'el_GR', 'el_GR.UTF-8',
+ # 'fr_FR.UTF-8', # only OSError
+ 'ja_JP.UTF-8',
+ 'ru_RU', 'ru_RU.KOI8-R',
+ 'ru_RU.UTF-8',
+ '', # default locale: might be non-problematic
+ ]
+
+if oldlocale is not None:
+ # find a supported problematic locale:
+ for testlocale in problematic_locales:
+ try:
+ locale.setlocale(locale.LC_ALL, testlocale)
+ except locale.Error:
+ testlocale = None
+ else:
+ break
+ locale.setlocale(locale.LC_ALL, oldlocale) # reset
+else:
+ testlocale = None
+
+class SafeStringTests(unittest.TestCase):
+ # the error message in EnvironmentError instances comes from the OS
+ # and in some locales (e.g. ru_RU), contains high bit chars.
+ # -> see the test in test_error_reporting.py
+
+ # test data:
+ bs = b('\xfc') # unicode(bs) fails, str(bs) in Python 3 return repr()
+ us = u'\xfc' # bytes(us) fails; str(us) fails in Python 2
+ be = Exception(bs) # unicode(be) fails
+ ue = Exception(us) # bytes(ue) fails, str(ue) fails in Python 2;
+ # unicode(ue) fails in Python < 2.6 (issue2517_)
+ # .. _issue2517: http://bugs.python.org/issue2517
+ # wrapped test data:
+ wbs = SafeString(bs)
+ wus = SafeString(us)
+ wbe = SafeString(be)
+ wue = SafeString(ue)
+
+ def test_7bit(self):
+ # wrapping (not required with 7-bit chars) must not change the
+ # result of conversions:
+ bs7 = b('foo')
+ us7 = u'foo'
+ be7 = Exception(bs7)
+ ue7 = Exception(us7)
+ self.assertEqual(str(42), str(SafeString(42)))
+ self.assertEqual(str(bs7), str(SafeString(bs7)))
+ self.assertEqual(str(us7), str(SafeString(us7)))
+ self.assertEqual(str(be7), str(SafeString(be7)))
+ self.assertEqual(str(ue7), str(SafeString(ue7)))
+ self.assertEqual(unicode(7), unicode(SafeString(7)))
+ self.assertEqual(unicode(bs7), unicode(SafeString(bs7)))
+ self.assertEqual(unicode(us7), unicode(SafeString(us7)))
+ self.assertEqual(unicode(be7), unicode(SafeString(be7)))
+ self.assertEqual(unicode(ue7), unicode(SafeString(ue7)))
+
+ def test_ustr(self):
+ """Test conversion to a unicode-string."""
+ # unicode(self.bs) fails
+ self.assertEqual(unicode, type(unicode(self.wbs)))
+ self.assertEqual(unicode(self.us), unicode(self.wus))
+ # unicode(self.be) fails
+ self.assertEqual(unicode, type(unicode(self.wbe)))
+ # unicode(ue) fails in Python < 2.6 (issue2517_)
+ self.assertEqual(unicode, type(unicode(self.wue)))
+ self.assertEqual(self.us, unicode(self.wue))
+
+ def test_str(self):
+ """Test conversion to a string (bytes in Python 2, unicode in Python 3)."""
+ self.assertEqual(str(self.bs), str(self.wbs))
+ self.assertEqual(str(self.be), str(self.be))
+ # str(us) fails in Python 2
+ self.assertEqual(str, type(str(self.wus)))
+ # str(ue) fails in Python 2
+ self.assertEqual(str, type(str(self.wue)))
+
+
+class ErrorStringTests(unittest.TestCase):
+ bs = b('\xfc') # unicode(bs) fails, str(bs) in Python 3 return repr()
+ us = u'\xfc' # bytes(us) fails; str(us) fails in Python 2
+
+ def test_str(self):
+ self.assertEqual('Exception: spam',
+ str(ErrorString(Exception('spam'))))
+ self.assertEqual('IndexError: '+str(self.bs),
+ str(ErrorString(IndexError(self.bs))))
+ self.assertEqual('ImportError: %s' % SafeString(self.us),
+ str(ErrorString(ImportError(self.us))))
+
+ def test_unicode(self):
+ self.assertEqual(u'Exception: spam',
+ unicode(ErrorString(Exception(u'spam'))))
+ self.assertEqual(u'IndexError: '+self.us,
+ unicode(ErrorString(IndexError(self.us))))
+ self.assertEqual(u'ImportError: %s' % SafeString(self.bs),
+ unicode(ErrorString(ImportError(self.bs))))
+
+
+# ErrorOutput tests
+# -----------------
+
+# Stub: Buffer with 'strict' auto-conversion of input to byte string:
+class BBuf(BytesIO, object):
+ def write(self, data):
+ if isinstance(data, unicode):
+ data.encode('ascii', 'strict')
+ super(BBuf, self).write(data)
+
+# Stub: Buffer expecting unicode string:
+class UBuf(StringIO, object):
+ def write(self, data):
+ # emulate Python 3 handling of stdout, stderr
+ if isinstance(data, bytes):
+ raise TypeError('must be unicode, not bytes')
+ super(UBuf, self).write(data)
+
+class ErrorOutputTests(unittest.TestCase):
+ def test_defaults(self):
+ e = ErrorOutput()
+ self.assertEquals(e.stream, sys.stderr)
+
+ def test_bbuf(self):
+ buf = BBuf() # buffer storing byte string
+ e = ErrorOutput(buf, encoding='ascii')
+ # write byte-string as-is
+ e.write(b('b\xfc'))
+ self.assertEquals(buf.getvalue(), b('b\xfc'))
+ # encode unicode data with backslashescape fallback replacement:
+ e.write(u' u\xfc')
+ self.assertEquals(buf.getvalue(), b('b\xfc u\\xfc'))
+ # handle Exceptions with Unicode string args
+ # unicode(Exception(u'e\xfc')) # fails in Python < 2.6
+ e.write(AttributeError(u' e\xfc'))
+ self.assertEquals(buf.getvalue(), b('b\xfc u\\xfc e\\xfc'))
+ # encode with `encoding` attribute
+ e.encoding = 'utf8'
+ e.write(u' u\xfc')
+ self.assertEquals(buf.getvalue(), b('b\xfc u\\xfc e\\xfc u\xc3\xbc'))
+
+ def test_ubuf(self):
+ buf = UBuf() # buffer only accepting unicode string
+ # decode of binary strings
+ e = ErrorOutput(buf, encoding='ascii')
+ e.write(b('b\xfc'))
+ self.assertEquals(buf.getvalue(), u'b\ufffd') # use REPLACEMENT CHARACTER
+ # write Unicode string and Exceptions with Unicode args
+ e.write(u' u\xfc')
+ self.assertEquals(buf.getvalue(), u'b\ufffd u\xfc')
+ e.write(AttributeError(u' e\xfc'))
+ self.assertEquals(buf.getvalue(), u'b\ufffd u\xfc e\xfc')
+ # decode with `encoding` attribute
+ e.encoding = 'latin1'
+ e.write(b(' b\xfc'))
+ self.assertEquals(buf.getvalue(), u'b\ufffd u\xfc e\xfc b\xfc')
+
+
+
+class SafeStringTests_locale(unittest.TestCase):
+ """
+ Test docutils.SafeString with 'problematic' locales.
+
+ The error message in `EnvironmentError` instances comes from the OS
+ and in some locales (e.g. ru_RU), contains high bit chars.
+ """
+ if testlocale:
+ locale.setlocale(locale.LC_ALL, testlocale)
+ # test data:
+ bs = b('\xfc')
+ us = u'\xfc'
+ try:
+ open(b('\xfc'))
+ except IOError, e: # in Python 3 the name for the exception instance
+ bioe = e # is local to the except clause
+ try:
+ open(u'\xfc')
+ except IOError, e:
+ uioe = e
+ except UnicodeEncodeError:
+ try:
+ open(u'\xfc'.encode(sys.getfilesystemencoding(), 'replace'))
+ except IOError:
+ uioe = e
+ try:
+ os.chdir(b('\xfc'))
+ except OSError, e:
+ bose = e
+ try:
+ os.chdir(u'\xfc')
+ except OSError, e:
+ uose = e
+ except UnicodeEncodeError:
+ try:
+ os.chdir(u'\xfc'.encode(sys.getfilesystemencoding(), 'replace'))
+ except OSError:
+ uose = e
+ # wrapped test data:
+ wbioe = SafeString(bioe)
+ wuioe = SafeString(uioe)
+ wbose = SafeString(bose)
+ wuose = SafeString(uose)
+ # reset locale
+ if testlocale:
+ locale.setlocale(locale.LC_ALL, oldlocale)
+
+ def test_ustr(self):
+ """Test conversion to a unicode-string."""
+ # unicode(bioe) fails with e.g. 'ru_RU.utf8' locale
+ self.assertEqual(unicode, type(unicode(self.wbioe)))
+ self.assertEqual(unicode, type(unicode(self.wuioe)))
+ self.assertEqual(unicode, type(unicode(self.wbose)))
+ self.assertEqual(unicode, type(unicode(self.wuose)))
+
+ def test_str(self):
+ """Test conversion to a string (bytes in Python 2, unicode in Python 3)."""
+ self.assertEqual(str(self.bioe), str(self.wbioe))
+ self.assertEqual(str(self.uioe), str(self.wuioe))
+ self.assertEqual(str(self.bose), str(self.wbose))
+ self.assertEqual(str(self.uose), str(self.wuose))
+
+
+
+class ErrorReportingTests(unittest.TestCase):
+ """
+ Test cases where error reporting can go wrong.
+
+ Do not test the exact output (as this varies with the locale), just
+ ensure that the correct exception is thrown.
+ """
+
+ # These tests fail with a 'problematic locale' and
+ # (revision < 7035) and Python-2.
+
+ parser = parsers.rst.Parser()
+ """Parser shared by all ParserTestCases."""
+
+ option_parser = frontend.OptionParser(components=(parsers.rst.Parser,))
+ settings = option_parser.get_default_values()
+ settings.report_level = 1
+ settings.halt_level = 1
+ settings.warning_stream = ''
+ document = utils.new_document('test data', settings)
+
+ def setUp(self):
+ if testlocale:
+ locale.setlocale(locale.LC_ALL, testlocale)
+
+ def tearDown(self):
+ if testlocale:
+ locale.setlocale(locale.LC_ALL, oldlocale)
+
+ def test_include(self):
+ source = ('.. include:: bogus.txt')
+ self.assertRaises(utils.SystemMessage,
+ self.parser.parse, source, self.document)
+
+ def test_raw_file(self):
+ source = ('.. raw:: html\n'
+ ' :file: bogus.html\n')
+ self.assertRaises(utils.SystemMessage,
+ self.parser.parse, source, self.document)
+
+ def test_raw_url(self):
+ source = ('.. raw:: html\n'
+ ' :url: http://bogus.html\n')
+ self.assertRaises(utils.SystemMessage,
+ self.parser.parse, source, self.document)
+
+ def test_csv_table(self):
+ source = ('.. csv-table:: external file\n'
+ ' :file: bogus.csv\n')
+ self.assertRaises(utils.SystemMessage,
+ self.parser.parse, source, self.document)
+
+ def test_csv_table_url(self):
+ source = ('.. csv-table:: external URL\n'
+ ' :url: ftp://bogus.csv\n')
+ self.assertRaises(utils.SystemMessage,
+ self.parser.parse, source, self.document)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/docutils/test/test_io.py b/docutils/test/test_io.py
index 3b83c1391..5cf44f439 100755
--- a/docutils/test/test_io.py
+++ b/docutils/test/test_io.py
@@ -8,18 +8,7 @@
Test module for io.py.
"""
-try:
- import locale
-except ImportError:
- locale = None
-
import unittest, sys
-try: # from standard library module `io`
- from io import StringIO, BytesIO
-except ImportError: # new in Python 2.6
- from StringIO import StringIO
- BytesIO = StringIO
-
import DocutilsTestSupport # must be imported before docutils
from docutils import io
from docutils._compat import b, bytes
@@ -74,147 +63,5 @@ print "hello world"
self.assertEquals(input.successful_encoding, 'utf-8')
-class ErrorStringTests(unittest.TestCase):
- # test data:
- bs = b('\xfc') # unicode(bs) fails, str(bs) in Python 3 return repr()
- us = u'\xfc' # bytes(us) fails; str(us) fails in Python 2
- be = Exception(bs) # unicode(be) fails
- ue = Exception(us) # bytes(ue) fails, str(ue) fails in Python 2;
- # unicode(ue) fails in Python < 2.6 (issue2517_)
- # the error message in IOError comes from the OS, and in some locales
- # (e.g. ru_RU), contains high bit chars:
- if locale:
- try:
- locale.setlocale(locale.LC_ALL, '')
- except locale.Error:
- print 'cannot test locale'
- try:
- open(b('\xfc'))
- except IOError, e: # in Python >= 3.2, the name for the exception instance
- bioe = e # is local to the except clause
- try:
- open(u'\xfc')
- except IOError, e:
- uioe = e
- except UnicodeEncodeError:
- try:
- open(u'\xfc'.encode(sys.getfilesystemencoding(), 'replace'))
- except IOError:
- uioe = e
- if locale:
- locale.setlocale(locale.LC_ALL, 'C') # reset
- # wrapped test data:
- wbs = io.ErrorString(bs)
- wus = io.ErrorString(us)
- wbe = io.ErrorString(be)
- wue = io.ErrorString(ue)
- wbioe = io.ErrorString(bioe)
- wuioe = io.ErrorString(uioe)
-
- def test_7bit(self):
- # wrapping (not required with 7-bit chars) must not change the
- # result of conversions:
- bs = b('foo')
- us = u'foo'
- be = Exception(bs)
- ue = Exception(us)
- self.assertEqual(str(7), str(io.ErrorString(7)))
- self.assertEqual(str(bs), str(io.ErrorString(bs)))
- self.assertEqual(str(us), str(io.ErrorString(us)))
- self.assertEqual(str(be), str(io.ErrorString(be)))
- self.assertEqual(str(ue), str(io.ErrorString(ue)))
- self.assertEqual(unicode(7), unicode(io.ErrorString(7)))
- self.assertEqual(unicode(bs), unicode(io.ErrorString(bs)))
- self.assertEqual(unicode(us), unicode(io.ErrorString(us)))
- self.assertEqual(unicode(be), unicode(io.ErrorString(be)))
- self.assertEqual(unicode(ue), unicode(io.ErrorString(ue)))
-
- def test_ustr(self):
- """Test conversion to a unicode-string."""
- # unicode(self.bs) fails
- self.assertEqual(unicode, type(unicode(self.wbs)))
- self.assertEqual(unicode(self.us), unicode(self.wus))
- # unicode(self.be) fails
- self.assertEqual(unicode, type(unicode(self.wbe)))
- # unicode(ue) fails in Python < 2.6 (issue2517_)
- self.assertEqual(unicode, type(unicode(self.wue)))
- self.assertEqual(self.us, unicode(self.wue))
- # unicode(bioe) fails with e.g. 'ru_RU.utf8' locale
- self.assertEqual(unicode, type(unicode(self.wbioe)))
- self.assertEqual(unicode, type(unicode(self.wuioe)))
-
- def test_str(self):
- """Test conversion to a string (bytes in Python 2, unicode in Python 3)."""
- self.assertEqual(str(self.bs), str(self.wbs))
- self.assertEqual(str(self.be), str(self.be))
- # str(us) fails in Python 2
- self.assertEqual(str, type(str(self.wus)))
- # str(ue) fails in Python 2
- self.assertEqual(str, type(str(self.wue)))
- self.assertEqual(str(self.bioe), str(self.wbioe))
- self.assertEqual(str(self.uioe), str(self.wuioe))
-
-
-# .. _issue2517: http://bugs.python.org/issue2517
-
-
-# ErrorOutput tests
-# -----------------
-
-# Stub: Buffer with 'strict' auto-conversion of input to byte string:
-class BBuf(BytesIO, object):
- def write(self, data):
- if isinstance(data, unicode):
- data.encode('ascii', 'strict')
- super(BBuf, self).write(data)
-
-# Stub: Buffer expecting unicode string:
-class UBuf(StringIO, object):
- def write(self, data):
- # emulate Python 3 handling of stdout, stderr
- if isinstance(data, bytes):
- raise TypeError('must be unicode, not bytes')
- super(UBuf, self).write(data)
-
-class ErrorOutputTests(unittest.TestCase):
- def test_defaults(self):
- e = io.ErrorOutput()
- self.assertEquals(e.stream, sys.stderr)
-
- def test_bbuf(self):
- buf = BBuf() # buffer storing byte string
- e = io.ErrorOutput(buf, encoding='ascii')
- # write byte-string as-is
- e.write(b('b\xfc'))
- self.assertEquals(buf.getvalue(), b('b\xfc'))
- # encode unicode data with backslashescape fallback replacement:
- e.write(u' u\xfc')
- self.assertEquals(buf.getvalue(), b('b\xfc u\\xfc'))
- # handle Exceptions with Unicode string args
- # unicode(Exception(u'e\xfc')) # fails in Python < 2.6
- e.write(AttributeError(u' e\xfc'))
- self.assertEquals(buf.getvalue(), b('b\xfc u\\xfc e\\xfc'))
- # encode with `encoding` attribute
- e.encoding = 'utf8'
- e.write(u' u\xfc')
- self.assertEquals(buf.getvalue(), b('b\xfc u\\xfc e\\xfc u\xc3\xbc'))
-
- def test_ubuf(self):
- buf = UBuf() # buffer only accepting unicode string
- # decode of binary strings
- e = io.ErrorOutput(buf, encoding='ascii')
- e.write(b('b\xfc'))
- self.assertEquals(buf.getvalue(), u'b\ufffd') # use REPLACEMENT CHARACTER
- # write Unicode string and Exceptions with Unicode args
- e.write(u' u\xfc')
- self.assertEquals(buf.getvalue(), u'b\ufffd u\xfc')
- e.write(AttributeError(u' e\xfc'))
- self.assertEquals(buf.getvalue(), u'b\ufffd u\xfc e\xfc')
- # decode with `encoding` attribute
- e.encoding = 'latin1'
- e.write(b(' b\xfc'))
- self.assertEquals(buf.getvalue(), u'b\ufffd u\xfc e\xfc b\xfc')
-
-
if __name__ == '__main__':
unittest.main()
diff --git a/docutils/test/test_parsers/test_rst/test_directives/test_raw.py b/docutils/test/test_parsers/test_rst/test_directives/test_raw.py
index fa7808112..680da5045 100755
--- a/docutils/test/test_parsers/test_rst/test_directives/test_raw.py
+++ b/docutils/test/test_parsers/test_rst/test_directives/test_raw.py
@@ -158,7 +158,7 @@ Raw input file is UTF-16-encoded, and is not valid ASCII.
<system_message level="4" line="1" source="test data" type="SEVERE">
<paragraph>
Problems with "raw" directive path:
- [Errno 2] No such file or directory: %s'non-existent.file'.
+ IOError: [Errno 2] No such file or directory: %s'non-existent.file'.
<literal_block xml:space="preserve">
.. raw:: html
:file: non-existent.file
diff --git a/docutils/tools/buildhtml.py b/docutils/tools/buildhtml.py
index 52d6d2458..807ef0109 100755
--- a/docutils/tools/buildhtml.py
+++ b/docutils/tools/buildhtml.py
@@ -28,7 +28,8 @@ import copy
from fnmatch import fnmatch
import docutils
from docutils import ApplicationError
-from docutils import core, frontend, utils, io
+from docutils import core, frontend, utils
+from docutils.error_reporting import ErrorOutput, ErrorString
from docutils.parsers import rst
from docutils.readers import standalone, pep
from docutils.writers import html4css1, pep_html
@@ -194,15 +195,15 @@ class Builder:
def visit(self, directory, names):
# BUG prune and ignore do not work
settings = self.get_settings('', directory)
- stderr = io.ErrorOutput(encoding=settings.error_encoding)
+ errout = ErrorOutput(encoding=settings.error_encoding)
if settings.prune and (os.path.abspath(directory) in settings.prune):
- stderr.write('/// ...Skipping directory (pruned): %s\n' %
- directory)
+ print >>errout, ('/// ...Skipping directory (pruned): %s' %
+ directory)
sys.stderr.flush()
names[:] = []
return
if not self.initial_settings.silent:
- stderr.write('/// Processing directory: %s\n' % directory)
+ print >>errout, '/// Processing directory: %s' % directory
sys.stderr.flush()
# settings.ignore grows many duplicate entries as we recurse
# if we add patterns in config files or on the command line.
@@ -224,14 +225,14 @@ class Builder:
else:
publisher = '.txt'
settings = self.get_settings(publisher, directory)
- stderr = io.ErrorOutput(encoding=settings.error_encoding)
+ errout = ErrorOutput(encoding=settings.error_encoding)
pub_struct = self.publishers[publisher]
if settings.prune and (directory in settings.prune):
return 1
settings._source = os.path.normpath(os.path.join(directory, name))
settings._destination = settings._source[:-4]+'.html'
if not self.initial_settings.silent:
- stderr.write(' ::: Processing: %s\n'% name)
+ print >>errout, ' ::: Processing: %s' % name
sys.stderr.flush()
try:
if not settings.dry_run:
@@ -242,8 +243,7 @@ class Builder:
writer_name=pub_struct.writer_name,
settings=settings)
except ApplicationError, error:
- stderr.write(' Error (%s): %s' %
- (error.__class__.__name__, error))
+ print >>errout, ' %s' % ErrorString(error)
if __name__ == "__main__":