This commit is contained in:
Nozomu Kaneko 2013-02-06 13:28:26 +09:00
commit 1bd7aaed7f
13 changed files with 535 additions and 290 deletions

View File

@ -1,6 +1,8 @@
Release 1.2 (in development) Release 1.2 (in development)
============================ ============================
* Fix text builder did not respect wide/fullwidth charactors.
* #1062: sphinx.ext.autodoc use __init__ method signature for class signature. * #1062: sphinx.ext.autodoc use __init__ method signature for class signature.
* PR#111: Respect add_autodoc_attrgetter() even when inherited-members is set. * PR#111: Respect add_autodoc_attrgetter() even when inherited-members is set.

View File

@ -4,6 +4,7 @@ tag_date = true
[aliases] [aliases]
release = egg_info -RDb '' release = egg_info -RDb ''
upload = upload --sign --identity=36580288
[extract_messages] [extract_messages]
mapping_file = babel.cfg mapping_file = babel.cfg

View File

@ -26,28 +26,27 @@ from itertools import izip, groupby
from docutils import nodes from docutils import nodes
from docutils.io import FileInput, NullOutput from docutils.io import FileInput, NullOutput
from docutils.core import Publisher from docutils.core import Publisher
from docutils.utils import Reporter, relative_path, new_document, \ from docutils.utils import Reporter, relative_path, get_source_line
get_source_line
from docutils.readers import standalone from docutils.readers import standalone
from docutils.parsers.rst import roles, directives, Parser as RSTParser from docutils.parsers.rst import roles, directives
from docutils.parsers.rst.languages import en as english from docutils.parsers.rst.languages import en as english
from docutils.parsers.rst.directives.html import MetaBody from docutils.parsers.rst.directives.html import MetaBody
from docutils.writers import UnfilteredWriter from docutils.writers import UnfilteredWriter
from docutils.transforms import Transform
from docutils.transforms.parts import ContentsFilter
from sphinx import addnodes from sphinx import addnodes
from sphinx.util import url_re, get_matching_docs, docname_join, split_into, \ from sphinx.util import url_re, get_matching_docs, docname_join, split_into, \
split_index_msg, FilenameUniqDict FilenameUniqDict
from sphinx.util.nodes import clean_astext, make_refnode, extract_messages, \ from sphinx.util.nodes import clean_astext, make_refnode, WarningStream
traverse_translatable_index, WarningStream from sphinx.util.osutil import SEP, fs_encoding
from sphinx.util.osutil import SEP, ustrftime, find_catalog, fs_encoding
from sphinx.util.matching import compile_matchers from sphinx.util.matching import compile_matchers
from sphinx.util.pycompat import all, class_types from sphinx.util.pycompat import class_types
from sphinx.util.websupport import is_commentable from sphinx.util.websupport import is_commentable
from sphinx.errors import SphinxError, ExtensionError from sphinx.errors import SphinxError, ExtensionError
from sphinx.locale import _, init as init_locale from sphinx.locale import _
from sphinx.versioning import add_uids, merge_doctrees from sphinx.versioning import add_uids, merge_doctrees
from sphinx.transforms import DefaultSubstitutions, MoveModuleTargets, \
HandleCodeBlocks, SortIds, CitationReferences, Locale, \
RemoveTranslatableInline, SphinxContentsFilter
orig_role_function = roles.role orig_role_function = roles.role
@ -73,12 +72,6 @@ default_settings = {
ENV_VERSION = 42 + (sys.version_info[0] - 2) ENV_VERSION = 42 + (sys.version_info[0] - 2)
default_substitutions = set([
'version',
'release',
'today',
])
dummy_reporter = Reporter('', 4, 4) dummy_reporter = Reporter('', 4, 4)
versioning_conditions = { versioning_conditions = {
@ -93,261 +86,6 @@ class NoUri(Exception):
pass pass
class DefaultSubstitutions(Transform):
"""
Replace some substitutions if they aren't defined in the document.
"""
# run before the default Substitutions
default_priority = 210
def apply(self):
config = self.document.settings.env.config
# only handle those not otherwise defined in the document
to_handle = default_substitutions - set(self.document.substitution_defs)
for ref in self.document.traverse(nodes.substitution_reference):
refname = ref['refname']
if refname in to_handle:
text = config[refname]
if refname == 'today' and not text:
# special handling: can also specify a strftime format
text = ustrftime(config.today_fmt or _('%B %d, %Y'))
ref.replace_self(nodes.Text(text, text))
class MoveModuleTargets(Transform):
"""
Move module targets that are the first thing in a section to the section
title.
XXX Python specific
"""
default_priority = 210
def apply(self):
for node in self.document.traverse(nodes.target):
if not node['ids']:
continue
if (node.has_key('ismod') and
node.parent.__class__ is nodes.section and
# index 0 is the section title node
node.parent.index(node) == 1):
node.parent['ids'][0:0] = node['ids']
node.parent.remove(node)
class HandleCodeBlocks(Transform):
"""
Several code block related transformations.
"""
default_priority = 210
def apply(self):
# move doctest blocks out of blockquotes
for node in self.document.traverse(nodes.block_quote):
if all(isinstance(child, nodes.doctest_block) for child
in node.children):
node.replace_self(node.children)
# combine successive doctest blocks
#for node in self.document.traverse(nodes.doctest_block):
# if node not in node.parent.children:
# continue
# parindex = node.parent.index(node)
# while len(node.parent) > parindex+1 and \
# isinstance(node.parent[parindex+1], nodes.doctest_block):
# node[0] = nodes.Text(node[0] + '\n\n' +
# node.parent[parindex+1][0])
# del node.parent[parindex+1]
class SortIds(Transform):
"""
Sort secion IDs so that the "id[0-9]+" one comes last.
"""
default_priority = 261
def apply(self):
for node in self.document.traverse(nodes.section):
if len(node['ids']) > 1 and node['ids'][0].startswith('id'):
node['ids'] = node['ids'][1:] + [node['ids'][0]]
class CitationReferences(Transform):
"""
Replace citation references by pending_xref nodes before the default
docutils transform tries to resolve them.
"""
default_priority = 619
def apply(self):
for citnode in self.document.traverse(nodes.citation_reference):
cittext = citnode.astext()
refnode = addnodes.pending_xref(cittext, reftype='citation',
reftarget=cittext, refwarn=True,
ids=citnode["ids"])
refnode.line = citnode.line or citnode.parent.line
refnode += nodes.Text('[' + cittext + ']')
citnode.parent.replace(citnode, refnode)
class Locale(Transform):
"""
Replace translatable nodes with their translated doctree.
"""
default_priority = 0
def apply(self):
env = self.document.settings.env
settings, source = self.document.settings, self.document['source']
# XXX check if this is reliable
assert source.startswith(env.srcdir)
docname = path.splitext(relative_path(env.srcdir, source))[0]
textdomain = find_catalog(docname,
self.document.settings.gettext_compact)
# fetch translations
dirs = [path.join(env.srcdir, directory)
for directory in env.config.locale_dirs]
catalog, has_catalog = init_locale(dirs, env.config.language,
textdomain)
if not has_catalog:
return
parser = RSTParser()
for node, msg in extract_messages(self.document):
msgstr = catalog.gettext(msg)
# XXX add marker to untranslated parts
if not msgstr or msgstr == msg: # as-of-yet untranslated
continue
# Avoid "Literal block expected; none found." warnings.
# If msgstr ends with '::' then it cause warning message at
# parser.parse() processing.
# literal-block-warning is only appear in avobe case.
if msgstr.strip().endswith('::'):
msgstr += '\n\n dummy literal'
# dummy literal node will discard by 'patch = patch[0]'
patch = new_document(source, settings)
parser.parse(msgstr, patch)
patch = patch[0]
# XXX doctest and other block markup
if not isinstance(patch, nodes.paragraph):
continue # skip for now
# auto-numbered foot note reference should use original 'ids'.
def is_autonumber_footnote_ref(node):
return isinstance(node, nodes.footnote_reference) and \
node.get('auto') == 1
old_foot_refs = node.traverse(is_autonumber_footnote_ref)
new_foot_refs = patch.traverse(is_autonumber_footnote_ref)
if len(old_foot_refs) != len(new_foot_refs):
env.warn_node('inconsistent footnote references in '
'translated message', node)
for old, new in zip(old_foot_refs, new_foot_refs):
new['ids'] = old['ids']
self.document.autofootnote_refs.remove(old)
self.document.note_autofootnote_ref(new)
# reference should use original 'refname'.
# * reference target ".. _Python: ..." is not translatable.
# * section refname is not translatable.
# * inline reference "`Python <...>`_" has no 'refname'.
def is_refnamed_ref(node):
return isinstance(node, nodes.reference) and \
'refname' in node
old_refs = node.traverse(is_refnamed_ref)
new_refs = patch.traverse(is_refnamed_ref)
applied_refname_map = {}
if len(old_refs) != len(new_refs):
env.warn_node('inconsistent references in '
'translated message', node)
for new in new_refs:
if new['refname'] in applied_refname_map:
# 2nd appearance of the reference
new['refname'] = applied_refname_map[new['refname']]
elif old_refs:
# 1st appearance of the reference in old_refs
old = old_refs.pop(0)
refname = old['refname']
new['refname'] = refname
applied_refname_map[new['refname']] = refname
else:
# the reference is not found in old_refs
applied_refname_map[new['refname']] = new['refname']
self.document.note_refname(new)
# refnamed footnote and citation should use original 'ids'.
def is_refnamed_footnote_ref(node):
footnote_ref_classes = (nodes.footnote_reference,
nodes.citation_reference)
return isinstance(node, footnote_ref_classes) and \
'refname' in node
old_refs = node.traverse(is_refnamed_footnote_ref)
new_refs = patch.traverse(is_refnamed_footnote_ref)
refname_ids_map = {}
if len(old_refs) != len(new_refs):
env.warn_node('inconsistent references in '
'translated message', node)
for old in old_refs:
refname_ids_map[old["refname"]] = old["ids"]
for new in new_refs:
refname = new["refname"]
if refname in refname_ids_map:
new["ids"] = refname_ids_map[refname]
# Original pending_xref['reftarget'] contain not-translated
# target name, new pending_xref must use original one.
old_refs = node.traverse(addnodes.pending_xref)
new_refs = patch.traverse(addnodes.pending_xref)
if len(old_refs) != len(new_refs):
env.warn_node('inconsistent term references in '
'translated message', node)
for old, new in zip(old_refs, new_refs):
new['reftarget'] = old['reftarget']
# update leaves
for child in patch.children:
child.parent = node
node.children = patch.children
# Extract and translate messages for index entries.
for node, entries in traverse_translatable_index(self.document):
new_entries = []
for type, msg, tid, main in entries:
msg_parts = split_index_msg(type, msg)
msgstr_parts = []
for part in msg_parts:
msgstr = catalog.gettext(part)
if not msgstr:
msgstr = part
msgstr_parts.append(msgstr)
new_entries.append((type, ';'.join(msgstr_parts), tid, main))
node['raw_entries'] = entries
node['entries'] = new_entries
class RemoveTranslatableInline(Transform):
"""
Remove inline nodes used for translation as placeholders.
"""
default_priority = 999
def apply(self):
from sphinx.builders.gettext import MessageCatalogBuilder
env = self.document.settings.env
builder = env.app.builder
if isinstance(builder, MessageCatalogBuilder):
return
for inline in self.document.traverse(nodes.inline):
if 'translatable' in inline:
inline.parent.remove(inline)
inline.parent += inline.children
class SphinxStandaloneReader(standalone.Reader): class SphinxStandaloneReader(standalone.Reader):
""" """
Add our own transforms. Add our own transforms.
@ -367,20 +105,6 @@ class SphinxDummyWriter(UnfilteredWriter):
pass pass
class SphinxContentsFilter(ContentsFilter):
"""
Used with BuildEnvironment.add_toc_from() to discard cross-file links
within table-of-contents link nodes.
"""
def visit_pending_xref(self, node):
text = node.astext()
self.parent.append(nodes.literal(text, text))
raise nodes.SkipNode
def visit_image(self, node):
raise nodes.SkipNode
class BuildEnvironment: class BuildEnvironment:
""" """
The environment in which the ReST files are translated. The environment in which the ReST files are translated.

337
sphinx/transforms.py Normal file
View File

@ -0,0 +1,337 @@
# -*- coding: utf-8 -*-
"""
sphinx.transforms
~~~~~~~~~~~~~~~~~
Docutils transforms used by Sphinx when reading documents.
:copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
from os import path
from docutils import nodes
from docutils.utils import new_document, relative_path
from docutils.parsers.rst import Parser as RSTParser
from docutils.transforms import Transform
from docutils.transforms.parts import ContentsFilter
from sphinx import addnodes
from sphinx.locale import _, init as init_locale
from sphinx.util import split_index_msg
from sphinx.util.nodes import traverse_translatable_index, extract_messages
from sphinx.util.osutil import ustrftime, find_catalog
from sphinx.util.pycompat import all
default_substitutions = set([
'version',
'release',
'today',
])
class DefaultSubstitutions(Transform):
"""
Replace some substitutions if they aren't defined in the document.
"""
# run before the default Substitutions
default_priority = 210
def apply(self):
config = self.document.settings.env.config
# only handle those not otherwise defined in the document
to_handle = default_substitutions - set(self.document.substitution_defs)
for ref in self.document.traverse(nodes.substitution_reference):
refname = ref['refname']
if refname in to_handle:
text = config[refname]
if refname == 'today' and not text:
# special handling: can also specify a strftime format
text = ustrftime(config.today_fmt or _('%B %d, %Y'))
ref.replace_self(nodes.Text(text, text))
class MoveModuleTargets(Transform):
"""
Move module targets that are the first thing in a section to the section
title.
XXX Python specific
"""
default_priority = 210
def apply(self):
for node in self.document.traverse(nodes.target):
if not node['ids']:
continue
if (node.has_key('ismod') and
node.parent.__class__ is nodes.section and
# index 0 is the section title node
node.parent.index(node) == 1):
node.parent['ids'][0:0] = node['ids']
node.parent.remove(node)
class HandleCodeBlocks(Transform):
"""
Several code block related transformations.
"""
default_priority = 210
def apply(self):
# move doctest blocks out of blockquotes
for node in self.document.traverse(nodes.block_quote):
if all(isinstance(child, nodes.doctest_block) for child
in node.children):
node.replace_self(node.children)
# combine successive doctest blocks
#for node in self.document.traverse(nodes.doctest_block):
# if node not in node.parent.children:
# continue
# parindex = node.parent.index(node)
# while len(node.parent) > parindex+1 and \
# isinstance(node.parent[parindex+1], nodes.doctest_block):
# node[0] = nodes.Text(node[0] + '\n\n' +
# node.parent[parindex+1][0])
# del node.parent[parindex+1]
class SortIds(Transform):
"""
Sort secion IDs so that the "id[0-9]+" one comes last.
"""
default_priority = 261
def apply(self):
for node in self.document.traverse(nodes.section):
if len(node['ids']) > 1 and node['ids'][0].startswith('id'):
node['ids'] = node['ids'][1:] + [node['ids'][0]]
class CitationReferences(Transform):
"""
Replace citation references by pending_xref nodes before the default
docutils transform tries to resolve them.
"""
default_priority = 619
def apply(self):
for citnode in self.document.traverse(nodes.citation_reference):
cittext = citnode.astext()
refnode = addnodes.pending_xref(cittext, reftype='citation',
reftarget=cittext, refwarn=True,
ids=citnode["ids"])
refnode.line = citnode.line or citnode.parent.line
refnode += nodes.Text('[' + cittext + ']')
citnode.parent.replace(citnode, refnode)
class CustomLocaleReporter(object):
"""
Replacer for document.reporter.get_source_and_line method.
reST text lines for translation not have original source line number.
This class provide correct line number at reporting.
"""
def __init__(self, source, line):
self.source, self.line = source, line
try:
from docutils import __version__ as du_version
v = tuple([int(x) for x in du_version.split('.')[:2]])
except ImportError:
v = (99, 99)
self.du_version = v
def set_reporter(self, document):
if self.du_version < (0, 9):
document.reporter.locator = self.get_source_and_line
else:
document.reporter.get_source_and_line = self.get_source_and_line
def get_source_and_line(self, lineno=None):
return self.source, self.line
class Locale(Transform):
"""
Replace translatable nodes with their translated doctree.
"""
default_priority = 0
def apply(self):
env = self.document.settings.env
settings, source = self.document.settings, self.document['source']
# XXX check if this is reliable
assert source.startswith(env.srcdir)
docname = path.splitext(relative_path(env.srcdir, source))[0]
textdomain = find_catalog(docname,
self.document.settings.gettext_compact)
# fetch translations
dirs = [path.join(env.srcdir, directory)
for directory in env.config.locale_dirs]
catalog, has_catalog = init_locale(dirs, env.config.language,
textdomain)
if not has_catalog:
return
parser = RSTParser()
for node, msg in extract_messages(self.document):
msgstr = catalog.gettext(msg)
# XXX add marker to untranslated parts
if not msgstr or msgstr == msg: # as-of-yet untranslated
continue
# Avoid "Literal block expected; none found." warnings.
# If msgstr ends with '::' then it cause warning message at
# parser.parse() processing.
# literal-block-warning is only appear in avobe case.
if msgstr.strip().endswith('::'):
msgstr += '\n\n dummy literal'
# dummy literal node will discard by 'patch = patch[0]'
patch = new_document(source, settings)
CustomLocaleReporter(node.source, node.line).set_reporter(patch)
parser.parse(msgstr, patch)
patch = patch[0]
# XXX doctest and other block markup
if not isinstance(patch, nodes.paragraph):
continue # skip for now
# auto-numbered foot note reference should use original 'ids'.
def is_autonumber_footnote_ref(node):
return isinstance(node, nodes.footnote_reference) and \
node.get('auto') == 1
old_foot_refs = node.traverse(is_autonumber_footnote_ref)
new_foot_refs = patch.traverse(is_autonumber_footnote_ref)
if len(old_foot_refs) != len(new_foot_refs):
env.warn_node('inconsistent footnote references in '
'translated message', node)
for old, new in zip(old_foot_refs, new_foot_refs):
new['ids'] = old['ids']
for id in new['ids']:
self.document.ids[id] = new
self.document.autofootnote_refs.remove(old)
self.document.note_autofootnote_ref(new)
# reference should use original 'refname'.
# * reference target ".. _Python: ..." is not translatable.
# * section refname is not translatable.
# * inline reference "`Python <...>`_" has no 'refname'.
def is_refnamed_ref(node):
return isinstance(node, nodes.reference) and \
'refname' in node
old_refs = node.traverse(is_refnamed_ref)
new_refs = patch.traverse(is_refnamed_ref)
applied_refname_map = {}
if len(old_refs) != len(new_refs):
env.warn_node('inconsistent references in '
'translated message', node)
for new in new_refs:
if new['refname'] in applied_refname_map:
# 2nd appearance of the reference
new['refname'] = applied_refname_map[new['refname']]
elif old_refs:
# 1st appearance of the reference in old_refs
old = old_refs.pop(0)
refname = old['refname']
new['refname'] = refname
applied_refname_map[new['refname']] = refname
else:
# the reference is not found in old_refs
applied_refname_map[new['refname']] = new['refname']
self.document.note_refname(new)
# refnamed footnote and citation should use original 'ids'.
def is_refnamed_footnote_ref(node):
footnote_ref_classes = (nodes.footnote_reference,
nodes.citation_reference)
return isinstance(node, footnote_ref_classes) and \
'refname' in node
old_refs = node.traverse(is_refnamed_footnote_ref)
new_refs = patch.traverse(is_refnamed_footnote_ref)
refname_ids_map = {}
if len(old_refs) != len(new_refs):
env.warn_node('inconsistent references in '
'translated message', node)
for old in old_refs:
refname_ids_map[old["refname"]] = old["ids"]
for new in new_refs:
refname = new["refname"]
if refname in refname_ids_map:
new["ids"] = refname_ids_map[refname]
# Original pending_xref['reftarget'] contain not-translated
# target name, new pending_xref must use original one.
# This code restricts to change ref-targets in the translation.
old_refs = node.traverse(addnodes.pending_xref)
new_refs = patch.traverse(addnodes.pending_xref)
xref_reftarget_map = {}
if len(old_refs) != len(new_refs):
env.warn_node('inconsistent term references in '
'translated message', node)
for old in old_refs:
key = old["reftype"], old["refdomain"]
xref_reftarget_map[key] = old["reftarget"]
for new in new_refs:
key = new["reftype"], new["refdomain"]
if key in xref_reftarget_map:
new['reftarget'] = xref_reftarget_map[key]
# update leaves
for child in patch.children:
child.parent = node
node.children = patch.children
# Extract and translate messages for index entries.
for node, entries in traverse_translatable_index(self.document):
new_entries = []
for type, msg, tid, main in entries:
msg_parts = split_index_msg(type, msg)
msgstr_parts = []
for part in msg_parts:
msgstr = catalog.gettext(part)
if not msgstr:
msgstr = part
msgstr_parts.append(msgstr)
new_entries.append((type, ';'.join(msgstr_parts), tid, main))
node['raw_entries'] = entries
node['entries'] = new_entries
class RemoveTranslatableInline(Transform):
"""
Remove inline nodes used for translation as placeholders.
"""
default_priority = 999
def apply(self):
from sphinx.builders.gettext import MessageCatalogBuilder
env = self.document.settings.env
builder = env.app.builder
if isinstance(builder, MessageCatalogBuilder):
return
for inline in self.document.traverse(nodes.inline):
if 'translatable' in inline:
inline.parent.remove(inline)
inline.parent += inline.children
class SphinxContentsFilter(ContentsFilter):
"""
Used with BuildEnvironment.add_toc_from() to discard cross-file links
within table-of-contents link nodes.
"""
def visit_pending_xref(self, node):
text = node.astext()
self.parent.append(nodes.literal(text, text))
raise nodes.SkipNode
def visit_image(self, node):
raise nodes.SkipNode

View File

@ -13,6 +13,7 @@ import re
import textwrap import textwrap
from docutils import nodes, writers from docutils import nodes, writers
from docutils.utils import column_width
from sphinx import addnodes from sphinx import addnodes
from sphinx.locale import admonitionlabels, versionlabels, _ from sphinx.locale import admonitionlabels, versionlabels, _
@ -165,7 +166,8 @@ class TextTranslator(nodes.NodeVisitor):
char = '^' char = '^'
text = ''.join(x[1] for x in self.states.pop() if x[0] == -1) text = ''.join(x[1] for x in self.states.pop() if x[0] == -1)
self.stateindent.pop() self.stateindent.pop()
self.states[-1].append((0, ['', text, '%s' % (char * len(text)), ''])) self.states[-1].append(
(0, ['', text, '%s' % (char * column_width(text)), '']))
def visit_subtitle(self, node): def visit_subtitle(self, node):
pass pass
@ -391,7 +393,7 @@ class TextTranslator(nodes.NodeVisitor):
for i, cell in enumerate(line): for i, cell in enumerate(line):
par = my_wrap(cell, width=colwidths[i]) par = my_wrap(cell, width=colwidths[i])
if par: if par:
maxwidth = max(map(len, par)) maxwidth = max(map(column_width, par))
else: else:
maxwidth = 0 maxwidth = 0
realwidths[i] = max(realwidths[i], maxwidth) realwidths[i] = max(realwidths[i], maxwidth)
@ -411,7 +413,9 @@ class TextTranslator(nodes.NodeVisitor):
out = ['|'] out = ['|']
for i, cell in enumerate(line): for i, cell in enumerate(line):
if cell: if cell:
out.append(' ' + cell.ljust(realwidths[i]+1)) adjust_len = len(cell) - column_width(cell)
out.append(' ' + cell.ljust(
realwidths[i] + 1 + adjust_len))
else: else:
out.append(' ' * (realwidths[i] + 2)) out.append(' ' * (realwidths[i] + 2))
out.append('|') out.append('|')

View File

@ -1,9 +1,13 @@
CONTENTS
========
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
:numbered: :numbered:
subdir/contents subdir/contents
bom bom
warnings
footnote footnote
external_links external_links
refs_inconsistency refs_inconsistency
@ -12,6 +16,7 @@
definition_terms definition_terms
figure_caption figure_caption
index_entries index_entries
role_xref
glossary_terms glossary_terms
glossary_terms_inconsistency glossary_terms_inconsistency
docfields docfields

View File

@ -0,0 +1,23 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) 2012, foof
# This file is distributed under the same license as the foo package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: sphinx 1.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-02-04 14:00\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "i18n role xref"
msgstr "I18N ROCK'N ROLE XREF"
msgid "link to :term:`Some term`, :ref:`i18n-role-xref`, :doc:`contents`."
msgstr "LINK TO :ref:`i18n-role-xref`, :doc:`contents`, :term:`SOME NEW TERM`."

View File

@ -0,0 +1,9 @@
:tocdepth: 2
.. _i18n-role-xref:
i18n role xref
==============
link to :term:`Some term`, :ref:`i18n-role-xref`, :doc:`contents`.

View File

@ -0,0 +1,23 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) 2010, Georg Brandl & Team
# This file is distributed under the same license as the Sphinx <Tests> package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Sphinx <Tests> 0.6\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-02-04 13:06\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "i18n with reST warnings"
msgstr "I18N WITH REST WARNINGS"
msgid "line of ``literal`` markup."
msgstr "LINE OF ``BROKEN LITERAL MARKUP."

View File

@ -0,0 +1,5 @@
i18n with reST warnings
========================
line of ``literal`` markup.

65
tests/test_build_text.py Normal file
View File

@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
"""
test_build_text
~~~~~~~~~~~~~~~
Test the build process with Text builder with the test root.
:copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
from textwrap import dedent
from docutils.utils import column_width
from util import *
def with_text_app(*args, **kw):
default_kw = {
'buildername': 'text',
'srcdir': '(empty)',
'confoverrides': {
'project': 'text',
'master_doc': 'contents',
},
}
default_kw.update(kw)
return with_app(*args, **default_kw)
@with_text_app()
def test_multibyte_title_line(app):
title = u'\u65e5\u672c\u8a9e'
underline = u'=' * column_width(title)
content = u'\n'.join((title, underline, u''))
(app.srcdir / 'contents.rst').write_text(content, encoding='utf-8')
app.builder.build_all()
result = (app.outdir / 'contents.txt').text(encoding='utf-8')
expect_underline = underline.replace('=', '*')
result_underline = result.splitlines()[2].strip()
assert expect_underline == result_underline
@with_text_app()
def test_multibyte_table(app):
text = u'\u65e5\u672c\u8a9e'
contents = (u"\n.. list-table::"
"\n"
"\n - - spam"
"\n - egg"
"\n"
"\n - - %(text)s"
"\n - %(text)s"
"\n" % locals())
(app.srcdir / 'contents.rst').write_text(contents, encoding='utf-8')
app.builder.build_all()
result = (app.outdir / 'contents.txt').text(encoding='utf-8')
lines = [line.strip() for line in result.splitlines() if line.strip()]
line_widths = [column_width(line) for line in lines]
assert len(set(line_widths)) == 1 # same widths

View File

@ -89,6 +89,23 @@ def test_subdir(app):
assert result.startswith(u"\nsubdir contents\n***************\n") assert result.startswith(u"\nsubdir contents\n***************\n")
@with_intl_app(buildername='text', warning=warnfile)
def test_i18n_warnings_in_translation(app):
app.builddir.rmtree(True)
app.builder.build(['warnings'])
result = (app.outdir / 'warnings.txt').text(encoding='utf-8')
expect = (u"\nI18N WITH REST WARNINGS"
u"\n***********************\n"
u"\nLINE OF >>``<<BROKEN LITERAL MARKUP.\n")
assert result == expect
warnings = warnfile.getvalue().replace(os.sep, '/')
warning_expr = u'.*/warnings.txt:4: ' \
u'WARNING: Inline literal start-string without end-string.\n'
assert re.search(warning_expr, warnings)
@with_intl_app(buildername='html', cleanenv=True) @with_intl_app(buildername='html', cleanenv=True)
def test_i18n_footnote_break_refid(app): def test_i18n_footnote_break_refid(app):
"""test for #955 cant-build-html-with-footnotes-when-using""" """test for #955 cant-build-html-with-footnotes-when-using"""
@ -97,9 +114,10 @@ def test_i18n_footnote_break_refid(app):
# expect no error by build # expect no error by build
@with_intl_app(buildername='text', cleanenv=True) @with_intl_app(buildername='text', warning=warnfile)
def test_i18n_footnote_regression(app): def test_i18n_footnote_regression(app):
"""regression test for fix #955""" """regression test for fix #955"""
app.builddir.rmtree(True)
app.builder.build(['footnote']) app.builder.build(['footnote'])
result = (app.outdir / 'footnote.txt').text(encoding='utf-8') result = (app.outdir / 'footnote.txt').text(encoding='utf-8')
expect = (u"\nI18N WITH FOOTNOTE" expect = (u"\nI18N WITH FOOTNOTE"
@ -110,6 +128,10 @@ def test_i18n_footnote_regression(app):
u"\n[100] THIS IS A NUMBERED FOOTNOTE.\n") u"\n[100] THIS IS A NUMBERED FOOTNOTE.\n")
assert result == expect assert result == expect
warnings = warnfile.getvalue().replace(os.sep, '/')
warning_expr = u'.*/footnote.txt:\\d*: SEVERE: Duplicate ID: ".*".\n'
assert not re.search(warning_expr, warnings)
@with_intl_app(buildername='html', cleanenv=True) @with_intl_app(buildername='html', cleanenv=True)
def test_i18n_footnote_backlink(app): def test_i18n_footnote_backlink(app):
@ -271,6 +293,24 @@ def test_i18n_glossary_terms(app):
assert 'term not in glossary' not in warnings assert 'term not in glossary' not in warnings
@with_intl_app(buildername='text', warning=warnfile)
def test_i18n_role_xref(app):
# regression test for #1090
app.builddir.rmtree(True) #for warnings acceleration
app.builder.build(['role_xref'])
result = (app.outdir / 'role_xref.txt').text(encoding='utf-8')
expect = (u"\nI18N ROCK'N ROLE XREF"
u"\n*********************\n"
u"\nLINK TO *I18N ROCK'N ROLE XREF*, *CONTENTS*, *SOME NEW TERM*.\n")
warnings = warnfile.getvalue().replace(os.sep, '/')
assert 'term not in glossary' not in warnings
assert 'undefined label' not in warnings
assert 'unknown document' not in warnings
assert result == expect
@with_intl_app(buildername='text', warning=warnfile) @with_intl_app(buildername='text', warning=warnfile)
def test_i18n_glossary_terms_inconsistency(app): def test_i18n_glossary_terms_inconsistency(app):
# regression test for #1090 # regression test for #1090

View File

@ -142,6 +142,13 @@ class TestApp(application.Sphinx):
temproot = tempdir / 'root' temproot = tempdir / 'root'
test_root.copytree(temproot) test_root.copytree(temproot)
srcdir = temproot srcdir = temproot
elif srcdir == '(empty)':
tempdir = path(tempfile.mkdtemp())
self.cleanup_trees.append(tempdir)
temproot = tempdir / 'root'
temproot.makedirs()
(temproot / 'conf.py').write_text('')
srcdir = temproot
else: else:
srcdir = path(srcdir) srcdir = path(srcdir)
self.builddir = srcdir.joinpath('_build') self.builddir = srcdir.joinpath('_build')