diff --git a/CHANGES b/CHANGES index 84ef3b5f0..8fa478b69 100644 --- a/CHANGES +++ b/CHANGES @@ -43,6 +43,8 @@ Features added * PR#255: When generating latex references, also insert latex target/anchor for the ids defined on the node. Thanks to Olivier Heurtier. * PR#229: Allow registration of other translators. Thanks to Russell Sim. +* Add app.set_translator() API to register or override a Docutils translator + class like :confval:`html_translator_class`. Bugs fixed ---------- diff --git a/doc/config.rst b/doc/config.rst index 25135ef9a..5647cfdfc 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -706,6 +706,8 @@ that use Sphinx's HTMLWriter class. used to translate document trees to HTML. Default is ``None`` (use the builtin translator). + .. seealso:: :meth:`~sphinx.application.Sphinx.set_translator` + .. confval:: html_show_copyright If true, "(C) Copyright ..." is shown in the HTML footer. Default is diff --git a/doc/extdev/appapi.rst b/doc/extdev/appapi.rst index 5f1be739c..8df819439 100644 --- a/doc/extdev/appapi.rst +++ b/doc/extdev/appapi.rst @@ -82,13 +82,17 @@ package. Register an event called *name*. This is needed to be able to emit it. -.. method:: Sphinx.add_translator(name, translator_class) +.. method:: Sphinx.set_translator(name, translator_class) - Register a Docutils translator class. This is used to register a - custom output translator. This allows extensions to define custom - nodes for the translator (see :meth:`add_node`). If the name - clashes with an existing translator an - :exc:`sphinx.errors.ExtensionError` will be raised. + Register or override a Docutils translator class. This is used to register + a custom output translator or to replace a builtin translator. + This allows extensions to use custom translator and define custom + nodes for the translator (see :meth:`add_node`). + + This is a API version of :confval:`html_translator_class` for all other + builders. Note that if :confval:`html_translator_class` is specified and + this API is called for html related builders, API overriding takes + precedence. .. versionadded:: 1.3 diff --git a/sphinx/application.py b/sphinx/application.py index 99cf3d170..4a35bb181 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -99,8 +99,7 @@ class Sphinx(object): self.warningiserror = warningiserror self._events = events.copy() - self._translators = dict.fromkeys([ - 'html', 'latex', 'text', 'man', 'texinfo']) + self._translators = {} # say hello to the world self.info(bold('Running Sphinx v%s' % sphinx.__version__)) @@ -451,10 +450,8 @@ class Sphinx(object): raise ExtensionError('Event %r already present' % name) self._events[name] = '' - def add_translator(self, name, translator_class): - if name in self._translators: - raise ExtensionError('A Translator by the name ' - '%s is already registered.' % name) + def set_translator(self, name, translator_class): + self.info(bold('A Translator for the %s builder is changed.' % name)) self._translators[name] = translator_class def add_node(self, node, **kwds): diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index 44c76fafb..d8faf04a5 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -65,6 +65,9 @@ class Builder(object): # images that need to be copied over (source -> dest) self.images = {} + # load default translator class + self.translator_class = app._translators.get(self.name) + self.init() # helper methods diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index a9e6a3680..926627a35 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -152,7 +152,9 @@ class StandaloneHTMLBuilder(Builder): self.config.trim_doctest_flags) def init_translator_class(self): - if self.config.html_translator_class: + if self.translator_class is not None: + pass + elif self.config.html_translator_class: self.translator_class = self.app.import_object( self.config.html_translator_class, 'html_translator_class setting') diff --git a/sphinx/builders/websupport.py b/sphinx/builders/websupport.py index 6cf981023..7b0e6f724 100644 --- a/sphinx/builders/websupport.py +++ b/sphinx/builders/websupport.py @@ -46,7 +46,8 @@ class WebSupportBuilder(PickleHTMLBuilder): self.storage = storage def init_translator_class(self): - self.translator_class = WebSupportTranslator + if self.translator_class is None: + self.translator_class = WebSupportTranslator def prepare_writing(self, docnames): PickleHTMLBuilder.prepare_writing(self, docnames) diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index c40f3443b..d77c2c541 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -89,9 +89,11 @@ class LaTeXWriter(writers.Writer): def __init__(self, builder): writers.Writer.__init__(self) self.builder = builder + self.translator_class = ( + self.builder.translator_class or LaTeXTranslator) def translate(self): - visitor = LaTeXTranslator(self.document, self.builder) + visitor = self.translator_class(self.document, self.builder) self.document.walkabout(visitor) self.output = visitor.astext() diff --git a/sphinx/writers/manpage.py b/sphinx/writers/manpage.py index 050f71d8b..8d49f807f 100644 --- a/sphinx/writers/manpage.py +++ b/sphinx/writers/manpage.py @@ -26,9 +26,11 @@ class ManualPageWriter(Writer): def __init__(self, builder): Writer.__init__(self) self.builder = builder + self.translator_class = ( + self.builder.translator_class or ManualPageTranslator) def translate(self): - visitor = ManualPageTranslator(self.builder, self.document) + visitor = self.translator_class(self.builder, self.document) self.visitor = visitor self.document.walkabout(visitor) self.output = visitor.astext() diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py index d8da901e4..a1051eb6a 100644 --- a/sphinx/writers/texinfo.py +++ b/sphinx/writers/texinfo.py @@ -120,9 +120,12 @@ class TexinfoWriter(writers.Writer): def __init__(self, builder): writers.Writer.__init__(self) self.builder = builder + self.translator_class = ( + self.builder.translator_class or TexinfoTranslator) def translate(self): - self.visitor = visitor = TexinfoTranslator(self.document, self.builder) + self.visitor = visitor = self.translator_class( + self.document, self.builder) self.document.walkabout(visitor) visitor.finish() for attr in self.visitor_attributes: diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py index 3f2edd145..b7d6c2869 100644 --- a/sphinx/writers/text.py +++ b/sphinx/writers/text.py @@ -140,9 +140,10 @@ class TextWriter(writers.Writer): def __init__(self, builder): writers.Writer.__init__(self) self.builder = builder + self.translator_class = self.builder.translator_class or TextTranslator def translate(self): - visitor = TextTranslator(self.document, self.builder) + visitor = self.translator_class(self.document, self.builder) self.document.walkabout(visitor) self.output = visitor.body diff --git a/sphinx/writers/xml.py b/sphinx/writers/xml.py index cfae484ef..179a9ab4f 100644 --- a/sphinx/writers/xml.py +++ b/sphinx/writers/xml.py @@ -18,6 +18,8 @@ class XMLWriter(BaseXMLWriter): def __init__(self, builder): BaseXMLWriter.__init__(self) self.builder = builder + if self.builder.translator_class: + self.translator_class = self.builder.translator_class def translate(self, *args, **kwargs): self.document.settings.newlines = \ diff --git a/tests/roots/test-api-set-translator/conf.py b/tests/roots/test-api-set-translator/conf.py new file mode 100644 index 000000000..5b172f0e1 --- /dev/null +++ b/tests/roots/test-api-set-translator/conf.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- + +import os +import sys + +from sphinx.writers.html import HTMLTranslator +from sphinx.writers.latex import LaTeXTranslator +from sphinx.writers.manpage import ManualPageTranslator +from sphinx.writers.texinfo import TexinfoTranslator +from sphinx.writers.text import TextTranslator +from sphinx.writers.websupport import WebSupportTranslator +from docutils.writers.docutils_xml import XMLTranslator + + +sys.path.insert(0, os.path.abspath('.')) + + +project = 'test' +master_doc = 'index' + + +class ConfHTMLTranslator(HTMLTranslator): + pass + + +class ConfDirHTMLTranslator(HTMLTranslator): + pass + + +class ConfSingleHTMLTranslator(HTMLTranslator): + pass + + +class ConfPickleTranslator(HTMLTranslator): + pass + + +class ConfJsonTranslator(HTMLTranslator): + pass + + +class ConfLaTeXTranslator(LaTeXTranslator): + pass + + +class ConfManualPageTranslator(ManualPageTranslator): + pass + + +class ConfTexinfoTranslator(TexinfoTranslator): + pass + + +class ConfTextTranslator(TextTranslator): + pass + + +class ConfWebSupportTranslator(WebSupportTranslator): + pass + + +class ConfXMLTranslator(XMLTranslator): + pass + + +class ConfPseudoXMLTranslator(XMLTranslator): + pass + + +def setup(app): + app.set_translator('html', ConfHTMLTranslator) + app.set_translator('dirhtml', ConfDirHTMLTranslator) + app.set_translator('singlehtml', ConfSingleHTMLTranslator) + app.set_translator('pickle', ConfPickleTranslator) + app.set_translator('json', ConfJsonTranslator) + app.set_translator('latex', ConfLaTeXTranslator) + app.set_translator('man', ConfManualPageTranslator) + app.set_translator('texinfo', ConfTexinfoTranslator) + app.set_translator('text', ConfTextTranslator) + app.set_translator('websupport', ConfWebSupportTranslator) + app.set_translator('xml', ConfXMLTranslator) + app.set_translator('pseudoxml', ConfPseudoXMLTranslator) diff --git a/tests/roots/test-api-set-translator/index.rst b/tests/roots/test-api-set-translator/index.rst new file mode 100644 index 000000000..101bd39af --- /dev/null +++ b/tests/roots/test-api-set-translator/index.rst @@ -0,0 +1,3 @@ +======================= +Test API set_translator +======================= \ No newline at end of file diff --git a/tests/roots/test-api-set-translator/nonext/conf.py b/tests/roots/test-api-set-translator/nonext/conf.py new file mode 100644 index 000000000..a07b3c27c --- /dev/null +++ b/tests/roots/test-api-set-translator/nonext/conf.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- + +import os +import sys + +sys.path.insert(0, os.path.dirname(os.path.abspath('.'))) + +project = 'test' +master_doc = 'index' diff --git a/tests/roots/test-api-set-translator/translator.py b/tests/roots/test-api-set-translator/translator.py new file mode 100644 index 000000000..d5c23d394 --- /dev/null +++ b/tests/roots/test-api-set-translator/translator.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +from sphinx.writers.html import HTMLTranslator + +class ExtHTMLTranslator(HTMLTranslator): + pass diff --git a/tests/test_api_translator.py b/tests/test_api_translator.py new file mode 100644 index 000000000..1606e645d --- /dev/null +++ b/tests/test_api_translator.py @@ -0,0 +1,185 @@ +# -*- coding: utf-8 -*- +""" + test_api_translator + ~~~~~~~~~~~~~~~~~~~ + + Test the Sphinx API for translator. + + :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from util import with_app, test_roots + + +@with_app( + buildername='html', + srcdir=(test_roots / 'test-api-set-translator'), + confdir=(test_roots / 'test-api-set-translator' / 'nonext'), +) +def test_html_translator(app): + # no set_translator(), no html_translator_class + translator_class = app.builder.translator_class + assert translator_class + assert translator_class.__name__ == 'SmartyPantsHTMLTranslator' + + +@with_app( + buildername='html', + srcdir=(test_roots / 'test-api-set-translator'), + confdir=(test_roots / 'test-api-set-translator' / 'nonext'), + confoverrides={ + 'html_translator_class': 'translator.ExtHTMLTranslator'}, +) +def test_html_with_html_translator_class(app): + # no set_translator(), but html_translator_class + translator_class = app.builder.translator_class + assert translator_class + assert translator_class.__name__ == 'ExtHTMLTranslator' + + +@with_app( + buildername='html', + srcdir=(test_roots / 'test-api-set-translator'), + confdir=(test_roots / 'test-api-set-translator' / 'nonext'), + confoverrides={'html_use_smartypants': False}, +) +def test_html_with_smartypants(app): + # no set_translator(), html_use_smartypants=False + translator_class = app.builder.translator_class + assert translator_class + assert translator_class.__name__ == 'HTMLTranslator' + + +@with_app( + buildername='html', + srcdir=(test_roots / 'test-api-set-translator'), +) +def test_html_with_set_translator_for_html_(app): + # use set_translator(), no html_translator_class + translator_class = app.builder.translator_class + assert translator_class + assert translator_class.__name__ == 'ConfHTMLTranslator' + + +@with_app( + buildername='html', + srcdir=(test_roots / 'test-api-set-translator'), + confoverrides={'html_translator_class': 'ext.ExtHTMLTranslator'}, +) +def test_html_with_set_translator_for_html_and_html_translator_class(app): + # use set_translator() and html_translator_class. + # set_translator() is given priority over html_translator_clas. + translator_class = app.builder.translator_class + assert translator_class + assert translator_class.__name__ == 'ConfHTMLTranslator' + + +@with_app( + buildername='dirhtml', + srcdir=(test_roots / 'test-api-set-translator'), +) +def test_dirhtml_set_translator_for_dirhtml(app): + translator_class = app.builder.translator_class + assert translator_class + assert translator_class.__name__ == 'ConfDirHTMLTranslator' + + +@with_app( + buildername='singlehtml', + srcdir=(test_roots / 'test-api-set-translator'), +) +def test_singlehtml_set_translator_for_singlehtml(app): + translator_class = app.builder.translator_class + assert translator_class + assert translator_class.__name__ == 'ConfSingleHTMLTranslator' + + +@with_app( + buildername='pickle', + srcdir=(test_roots / 'test-api-set-translator'), +) +def test_pickle_set_translator_for_pickle(app): + translator_class = app.builder.translator_class + assert translator_class + assert translator_class.__name__ == 'ConfPickleTranslator' + + +@with_app( + buildername='json', + srcdir=(test_roots / 'test-api-set-translator'), +) +def test_json_set_translator_for_json(app): + translator_class = app.builder.translator_class + assert translator_class + assert translator_class.__name__ == 'ConfJsonTranslator' + + +@with_app( + buildername='latex', + srcdir=(test_roots / 'test-api-set-translator'), +) +def test_html_with_set_translator_for_latex(app): + translator_class = app.builder.translator_class + assert translator_class + assert translator_class.__name__ == 'ConfLaTeXTranslator' + + +@with_app( + buildername='man', + srcdir=(test_roots / 'test-api-set-translator'), +) +def test_html_with_set_translator_for_man(app): + translator_class = app.builder.translator_class + assert translator_class + assert translator_class.__name__ == 'ConfManualPageTranslator' + + +@with_app( + buildername='texinfo', + srcdir=(test_roots / 'test-api-set-translator'), +) +def test_html_with_set_translator_for_texinfo(app): + translator_class = app.builder.translator_class + assert translator_class + assert translator_class.__name__ == 'ConfTexinfoTranslator' + + +@with_app( + buildername='text', + srcdir=(test_roots / 'test-api-set-translator'), +) +def test_html_with_set_translator_for_text(app): + translator_class = app.builder.translator_class + assert translator_class + assert translator_class.__name__ == 'ConfTextTranslator' + + +@with_app( + buildername='websupport', + srcdir=(test_roots / 'test-api-set-translator'), +) +def test_html_with_set_translator_for_websupport(app): + translator_class = app.builder.translator_class + assert translator_class + assert translator_class.__name__ == 'ConfWebSupportTranslator' + + +@with_app( + buildername='xml', + srcdir=(test_roots / 'test-api-set-translator'), +) +def test_html_with_set_translator_for_xml(app): + translator_class = app.builder.translator_class + assert translator_class + assert translator_class.__name__ == 'ConfXMLTranslator' + + +@with_app( + buildername='pseudoxml', + srcdir=(test_roots / 'test-api-set-translator'), +) +def test_html_with_set_translator_for_pseudoxml(app): + translator_class = app.builder.translator_class + assert translator_class + assert translator_class.__name__ == 'ConfPseudoXMLTranslator'