diff options
| author | Georg Brandl <georg@python.org> | 2013-02-04 15:52:26 +0100 |
|---|---|---|
| committer | Georg Brandl <georg@python.org> | 2013-02-04 15:52:26 +0100 |
| commit | bd942df4f41830a9b7de1f21f2c187fd971de85d (patch) | |
| tree | f30937d8f58420c51141330b9dfb35c8ee2f38c3 /sphinx | |
| parent | edde8efbfe2f5b45ff767b395d846fe610c0ba95 (diff) | |
| download | sphinx-bd942df4f41830a9b7de1f21f2c187fd971de85d.tar.gz | |
Move custom transforms from environment to their own module, to make sphinx.environment at least a little lighter.
Diffstat (limited to 'sphinx')
| -rw-r--r-- | sphinx/environment.py | 295 | ||||
| -rw-r--r-- | sphinx/transforms.py | 300 |
2 files changed, 309 insertions, 286 deletions
diff --git a/sphinx/environment.py b/sphinx/environment.py index f0f51fdd..32e8eb5b 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -26,28 +26,26 @@ from itertools import izip, groupby from docutils import nodes from docutils.io import FileInput, NullOutput from docutils.core import Publisher -from docutils.utils import Reporter, relative_path, new_document, \ - get_source_line +from docutils.utils import Reporter, relative_path, get_source_line 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.directives.html import MetaBody from docutils.writers import UnfilteredWriter -from docutils.transforms import Transform -from docutils.transforms.parts import ContentsFilter from sphinx import addnodes from sphinx.util import url_re, get_matching_docs, docname_join, split_into, \ - split_index_msg, FilenameUniqDict -from sphinx.util.nodes import clean_astext, make_refnode, extract_messages, \ - traverse_translatable_index, WarningStream -from sphinx.util.osutil import SEP, ustrftime, find_catalog, fs_encoding + FilenameUniqDict +from sphinx.util.nodes import clean_astext, make_refnode, WarningStream +from sphinx.util.osutil import SEP, fs_encoding 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.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.transforms import DefaultSubstitutions, MoveModuleTargets, \ + HandleCodeBlocks, SortIds, CitationReferences, Locale, SphinxContentsFilter orig_role_function = roles.role @@ -73,12 +71,6 @@ default_settings = { ENV_VERSION = 42 + (sys.version_info[0] - 2) -default_substitutions = set([ - 'version', - 'release', - 'today', -]) - dummy_reporter = Reporter('', 4, 4) versioning_conditions = { @@ -93,261 +85,6 @@ class NoUri(Exception): 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 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 - - 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) - patch.reporter.get_source_and_line = CustomLocaleReporter( - node.source, node.line).get_source_and_line - 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. - 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 SphinxStandaloneReader(standalone.Reader): """ Add our own transforms. @@ -366,20 +103,6 @@ class SphinxDummyWriter(UnfilteredWriter): 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: """ The environment in which the ReST files are translated. diff --git a/sphinx/transforms.py b/sphinx/transforms.py new file mode 100644 index 00000000..43ec97cb --- /dev/null +++ b/sphinx/transforms.py @@ -0,0 +1,300 @@ +# -*- 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 + + 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) + patch.reporter.get_source_and_line = CustomLocaleReporter( + node.source, node.line).get_source_and_line + 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. + 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 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 |
