Add SphinxTranslator as an abstract class

This commit is contained in:
Takeshi KOMIYA 2018-11-28 01:53:00 +09:00
parent e888e92ac4
commit 3863256cb6
9 changed files with 134 additions and 99 deletions

View File

@ -33,8 +33,9 @@ from sphinx.util.osutil import ensuredir, ENOENT, EPIPE, EINVAL
if False: if False:
# For type annotation # For type annotation
from docutils.parsers.rst import Directive # NOQA from docutils.parsers.rst import Directive # NOQA
from typing import Any, Dict, List, Tuple, Union # NOQA from typing import Any, Dict, List, Tuple # NOQA
from sphinx.application import Sphinx # NOQA from sphinx.application import Sphinx # NOQA
from sphinx.util.docutils import SphinxTranslator # NOQA
from sphinx.util.typing import unicode # NOQA from sphinx.util.typing import unicode # NOQA
from sphinx.writers.html import HTMLTranslator # NOQA from sphinx.writers.html import HTMLTranslator # NOQA
from sphinx.writers.latex import LaTeXTranslator # NOQA from sphinx.writers.latex import LaTeXTranslator # NOQA
@ -219,7 +220,7 @@ class GraphvizSimple(SphinxDirective):
def render_dot(self, code, options, format, prefix='graphviz'): def render_dot(self, code, options, format, prefix='graphviz'):
# type: (Union[HTMLTranslator, LaTeXTranslator, TexinfoTranslator], unicode, Dict, unicode, unicode) -> Tuple[unicode, unicode] # NOQA # type: (SphinxTranslator, unicode, Dict, unicode, unicode) -> Tuple[unicode, unicode] # NOQA
"""Render graphviz code into a PNG or PDF output file.""" """Render graphviz code into a PNG or PDF output file."""
graphviz_dot = options.get('graphviz_dot', self.builder.config.graphviz_dot) graphviz_dot = options.get('graphviz_dot', self.builder.config.graphviz_dot)
hashkey = (code + str(options) + str(graphviz_dot) + hashkey = (code + str(options) + str(graphviz_dot) +

View File

@ -39,6 +39,7 @@ if False:
from types import ModuleType # NOQA from types import ModuleType # NOQA
from typing import Any, Callable, Generator, List, Set, Tuple, Type # NOQA from typing import Any, Callable, Generator, List, Set, Tuple, Type # NOQA
from docutils.statemachine import State, ViewList # NOQA from docutils.statemachine import State, ViewList # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.config import Config # NOQA from sphinx.config import Config # NOQA
from sphinx.environment import BuildEnvironment # NOQA from sphinx.environment import BuildEnvironment # NOQA
from sphinx.io import SphinxFileInput # NOQA from sphinx.io import SphinxFileInput # NOQA
@ -383,6 +384,32 @@ class SphinxDirective(Directive):
return self.env.config return self.env.config
class SphinxTranslator(nodes.NodeVisitor):
"""A base class for Sphinx translators.
This class provides helper methods for Sphinx translators.
.. note:: The subclasses of this class might not work with docutils.
This class is strongly coupled with Sphinx.
"""
def __init__(self, builder, document):
# type: (Builder, nodes.document) -> None
super(SphinxTranslator, self).__init__(document)
self.builder = builder
self.config = builder.config
def get_settings(self):
# type: () -> Any
"""Get settings object with type safe.
.. note:: It is hard to check types for settings object because it's attributes
are added dynamically. This method avoids the type errors through
imitating it's type as Any.
"""
return self.document.settings
# cache a vanilla instance of nodes.document # cache a vanilla instance of nodes.document
# Used in new_document() function # Used in new_document() function
__document_cache__ = None # type: nodes.document __document_cache__ = None # type: nodes.document

View File

@ -23,6 +23,7 @@ from sphinx import addnodes
from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.locale import admonitionlabels, _, __ from sphinx.locale import admonitionlabels, _, __
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.docutils import SphinxTranslator
from sphinx.util.images import get_image_size from sphinx.util.images import get_image_size
if False: if False:
@ -67,25 +68,26 @@ class HTMLWriter(Writer):
self.clean_meta = ''.join(self.visitor.meta[2:]) self.clean_meta = ''.join(self.visitor.meta[2:])
class HTMLTranslator(BaseTranslator): class HTMLTranslator(SphinxTranslator, BaseTranslator):
""" """
Our custom HTML translator. Our custom HTML translator.
""" """
def __init__(self, builder, *args, **kwds): builder = None # type: StandaloneHTMLBuilder
# type: (StandaloneHTMLBuilder, Any, Any) -> None
super(HTMLTranslator, self).__init__(*args, **kwds) def __init__(self, builder, document):
self.highlighter = builder.highlighter # type: (StandaloneHTMLBuilder, nodes.document) -> None
self.builder = builder super(HTMLTranslator, self).__init__(builder, document)
self.docnames = [builder.current_docname] # for singlehtml builder self.highlighter = self.builder.highlighter
self.manpages_url = builder.config.manpages_url self.docnames = [self.builder.current_docname] # for singlehtml builder
self.manpages_url = self.config.manpages_url
self.protect_literal_text = 0 self.protect_literal_text = 0
self.permalink_text = builder.config.html_add_permalinks self.permalink_text = self.config.html_add_permalinks
# support backwards-compatible setting to a bool # support backwards-compatible setting to a bool
if not isinstance(self.permalink_text, str): if not isinstance(self.permalink_text, str):
self.permalink_text = self.permalink_text and u'\u00B6' or '' self.permalink_text = self.permalink_text and u'\u00B6' or ''
self.permalink_text = self.encode(self.permalink_text) self.permalink_text = self.encode(self.permalink_text)
self.secnumber_suffix = builder.config.html_secnumber_suffix self.secnumber_suffix = self.config.html_secnumber_suffix
self.param_separator = '' self.param_separator = ''
self.optional_param_level = 0 self.optional_param_level = 0
self._table_row_index = 0 self._table_row_index = 0
@ -250,8 +252,8 @@ class HTMLTranslator(BaseTranslator):
atts['class'] += ' external' atts['class'] += ' external'
if 'refuri' in node: if 'refuri' in node:
atts['href'] = node['refuri'] or '#' atts['href'] = node['refuri'] or '#'
if self.settings.cloak_email_addresses and \ if (self.get_settings().cloak_email_addresses and
atts['href'].startswith('mailto:'): atts['href'].startswith('mailto:')):
atts['href'] = self.cloak_mailto(atts['href']) atts['href'] = self.cloak_mailto(atts['href'])
self.in_mailto = True self.in_mailto = True
else: else:
@ -708,7 +710,7 @@ class HTMLTranslator(BaseTranslator):
# protect runs of multiple spaces; the last one can wrap # protect runs of multiple spaces; the last one can wrap
self.body.append(' ' * (len(token) - 1) + ' ') self.body.append(' ' * (len(token) - 1) + ' ')
else: else:
if self.in_mailto and self.settings.cloak_email_addresses: if self.in_mailto and self.get_settings().cloak_email_addresses:
encoded = self.cloak_email(encoded) encoded = self.cloak_email(encoded)
self.body.append(encoded) self.body.append(encoded)

View File

@ -22,6 +22,7 @@ from sphinx import addnodes
from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.locale import admonitionlabels, _, __ from sphinx.locale import admonitionlabels, _, __
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.docutils import SphinxTranslator
from sphinx.util.images import get_image_size from sphinx.util.images import get_image_size
if False: if False:
@ -37,25 +38,26 @@ logger = logging.getLogger(__name__)
# http://www.arnebrodowski.de/blog/write-your-own-restructuredtext-writer.html # http://www.arnebrodowski.de/blog/write-your-own-restructuredtext-writer.html
class HTML5Translator(BaseTranslator): class HTML5Translator(SphinxTranslator, BaseTranslator):
""" """
Our custom HTML translator. Our custom HTML translator.
""" """
def __init__(self, builder, *args, **kwds): builder = None # type: StandaloneHTMLBuilder
# type: (StandaloneHTMLBuilder, Any, Any) -> None
super(HTML5Translator, self).__init__(*args, **kwds) def __init__(self, builder, document):
self.highlighter = builder.highlighter # type: (StandaloneHTMLBuilder, nodes.document) -> None
self.builder = builder super(HTML5Translator, self).__init__(builder, document)
self.docnames = [builder.current_docname] # for singlehtml builder self.highlighter = self.builder.highlighter
self.manpages_url = builder.config.manpages_url self.docnames = [self.builder.current_docname] # for singlehtml builder
self.manpages_url = self.config.manpages_url
self.protect_literal_text = 0 self.protect_literal_text = 0
self.permalink_text = builder.config.html_add_permalinks self.permalink_text = self.config.html_add_permalinks
# support backwards-compatible setting to a bool # support backwards-compatible setting to a bool
if not isinstance(self.permalink_text, str): if not isinstance(self.permalink_text, str):
self.permalink_text = self.permalink_text and u'\u00B6' or '' self.permalink_text = self.permalink_text and u'\u00B6' or ''
self.permalink_text = self.encode(self.permalink_text) self.permalink_text = self.encode(self.permalink_text)
self.secnumber_suffix = builder.config.html_secnumber_suffix self.secnumber_suffix = self.config.html_secnumber_suffix
self.param_separator = '' self.param_separator = ''
self.optional_param_level = 0 self.optional_param_level = 0
self._table_row_index = 0 self._table_row_index = 0
@ -219,8 +221,8 @@ class HTML5Translator(BaseTranslator):
atts['class'] += ' external' atts['class'] += ' external'
if 'refuri' in node: if 'refuri' in node:
atts['href'] = node['refuri'] or '#' atts['href'] = node['refuri'] or '#'
if self.settings.cloak_email_addresses and \ if (self.get_settings().cloak_email_addresses and
atts['href'].startswith('mailto:'): atts['href'].startswith('mailto:')):
atts['href'] = self.cloak_mailto(atts['href']) atts['href'] = self.cloak_mailto(atts['href'])
self.in_mailto = True self.in_mailto = True
else: else:
@ -649,7 +651,7 @@ class HTML5Translator(BaseTranslator):
# protect runs of multiple spaces; the last one can wrap # protect runs of multiple spaces; the last one can wrap
self.body.append(' ' * (len(token) - 1) + ' ') self.body.append(' ' * (len(token) - 1) + ' ')
else: else:
if self.in_mailto and self.settings.cloak_email_addresses: if self.in_mailto and self.get_settings().cloak_email_addresses:
encoded = self.cloak_email(encoded) encoded = self.cloak_email(encoded)
self.body.append(encoded) self.body.append(encoded)
@ -788,7 +790,7 @@ class HTML5Translator(BaseTranslator):
self._table_row_index = 0 self._table_row_index = 0
classes = [cls.strip(u' \t\n') classes = [cls.strip(u' \t\n')
for cls in self.settings.table_style.split(',')] for cls in self.get_settings().table_style.split(',')]
classes.insert(0, "docutils") # compat classes.insert(0, "docutils") # compat
if 'align' in node: if 'align' in node:
classes.append('align-%s' % node['align']) classes.append('align-%s' % node['align'])

View File

@ -29,6 +29,7 @@ from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warnin
from sphinx.errors import SphinxError from sphinx.errors import SphinxError
from sphinx.locale import admonitionlabels, _, __ from sphinx.locale import admonitionlabels, _, __
from sphinx.util import split_into, logging from sphinx.util import split_into, logging
from sphinx.util.docutils import SphinxTranslator
from sphinx.util.i18n import format_date from sphinx.util.i18n import format_date
from sphinx.util.nodes import clean_astext from sphinx.util.nodes import clean_astext
from sphinx.util.template import LaTeXRenderer from sphinx.util.template import LaTeXRenderer
@ -497,7 +498,8 @@ def rstdim_to_latexdim(width_str):
return res return res
class LaTeXTranslator(nodes.NodeVisitor): class LaTeXTranslator(SphinxTranslator):
builder = None # type: LaTeXBuilder
secnumdepth = 2 # legacy sphinxhowto.cls uses this, whereas article.cls secnumdepth = 2 # legacy sphinxhowto.cls uses this, whereas article.cls
# default is originally 3. For book/report, 2 is already LaTeX default. # default is originally 3. For book/report, 2 is already LaTeX default.
@ -508,8 +510,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
def __init__(self, document, builder): def __init__(self, document, builder):
# type: (nodes.document, LaTeXBuilder) -> None # type: (nodes.document, LaTeXBuilder) -> None
super(LaTeXTranslator, self).__init__(document) super(LaTeXTranslator, self).__init__(builder, document)
self.builder = builder
self.body = [] # type: List[unicode] self.body = [] # type: List[unicode]
# flags # flags
@ -529,42 +530,42 @@ class LaTeXTranslator(nodes.NodeVisitor):
self.first_param = 0 self.first_param = 0
# sort out some elements # sort out some elements
self.elements = builder.context.copy() self.elements = self.builder.context.copy()
# but some have other interface in config file # but some have other interface in config file
self.elements['wrapperclass'] = self.format_docclass(document.settings.docclass) self.elements['wrapperclass'] = self.format_docclass(self.get_settings().docclass)
# we assume LaTeX class provides \chapter command except in case # we assume LaTeX class provides \chapter command except in case
# of non-Japanese 'howto' case # of non-Japanese 'howto' case
self.sectionnames = LATEXSECTIONNAMES[:] self.sectionnames = LATEXSECTIONNAMES[:]
if document.settings.docclass == 'howto': if self.get_settings().docclass == 'howto':
docclass = builder.config.latex_docclass.get('howto', 'article') docclass = self.config.latex_docclass.get('howto', 'article')
if docclass[0] == 'j': # Japanese class... if docclass[0] == 'j': # Japanese class...
pass pass
else: else:
self.sectionnames.remove('chapter') self.sectionnames.remove('chapter')
else: else:
docclass = builder.config.latex_docclass.get('manual', 'report') docclass = self.config.latex_docclass.get('manual', 'report')
self.elements['docclass'] = docclass self.elements['docclass'] = docclass
# determine top section level # determine top section level
self.top_sectionlevel = 1 self.top_sectionlevel = 1
if builder.config.latex_toplevel_sectioning: if self.config.latex_toplevel_sectioning:
try: try:
self.top_sectionlevel = \ self.top_sectionlevel = \
self.sectionnames.index(builder.config.latex_toplevel_sectioning) self.sectionnames.index(self.config.latex_toplevel_sectioning)
except ValueError: except ValueError:
logger.warning(__('unknown %r toplevel_sectioning for class %r') % logger.warning(__('unknown %r toplevel_sectioning for class %r') %
(builder.config.latex_toplevel_sectioning, docclass)) (self.config.latex_toplevel_sectioning, docclass))
if builder.config.today: if self.config.today:
self.elements['date'] = builder.config.today self.elements['date'] = self.config.today
else: else:
self.elements['date'] = format_date(builder.config.today_fmt or _('%b %d, %Y'), self.elements['date'] = format_date(self.config.today_fmt or _('%b %d, %Y'),
language=builder.config.language) language=self.config.language)
if builder.config.numfig: if self.config.numfig:
self.numfig_secnum_depth = builder.config.numfig_secnum_depth self.numfig_secnum_depth = self.config.numfig_secnum_depth
if self.numfig_secnum_depth > 0: # default is 1 if self.numfig_secnum_depth > 0: # default is 1
# numfig_secnum_depth as passed to sphinx.sty indices same names as in # numfig_secnum_depth as passed to sphinx.sty indices same names as in
# LATEXSECTIONNAMES but with -1 for part, 0 for chapter, 1 for section... # LATEXSECTIONNAMES but with -1 for part, 0 for chapter, 1 for section...
@ -582,31 +583,31 @@ class LaTeXTranslator(nodes.NodeVisitor):
else: else:
self.elements['sphinxpkgoptions'] += ',nonumfigreset' self.elements['sphinxpkgoptions'] += ',nonumfigreset'
try: try:
if builder.config.math_numfig: if self.config.math_numfig:
self.elements['sphinxpkgoptions'] += ',mathnumfig' self.elements['sphinxpkgoptions'] += ',mathnumfig'
except AttributeError: except AttributeError:
pass pass
if builder.config.latex_logo: if self.config.latex_logo:
# no need for \\noindent here, used in flushright # no need for \\noindent here, used in flushright
self.elements['logo'] = '\\sphinxincludegraphics{%s}\\par' % \ self.elements['logo'] = '\\sphinxincludegraphics{%s}\\par' % \
path.basename(builder.config.latex_logo) path.basename(self.config.latex_logo)
if (builder.config.language and builder.config.language != 'ja' and if (self.config.language and self.config.language != 'ja' and
'fncychap' not in builder.config.latex_elements): 'fncychap' not in self.config.latex_elements):
# use Sonny style if any language specified # use Sonny style if any language specified
self.elements['fncychap'] = ('\\usepackage[Sonny]{fncychap}\n' self.elements['fncychap'] = ('\\usepackage[Sonny]{fncychap}\n'
'\\ChNameVar{\\Large\\normalfont' '\\ChNameVar{\\Large\\normalfont'
'\\sffamily}\n\\ChTitleVar{\\Large' '\\sffamily}\n\\ChTitleVar{\\Large'
'\\normalfont\\sffamily}') '\\normalfont\\sffamily}')
self.babel = ExtBabel(builder.config.language, self.babel = ExtBabel(self.config.language,
not self.elements['babel']) not self.elements['babel'])
if builder.config.language and not self.babel.is_supported_language(): if self.config.language and not self.babel.is_supported_language():
# emit warning if specified language is invalid # emit warning if specified language is invalid
# (only emitting, nothing changed to processing) # (only emitting, nothing changed to processing)
logger.warning(__('no Babel option known for language %r'), logger.warning(__('no Babel option known for language %r'),
builder.config.language) self.config.language)
# set up multilingual module... # set up multilingual module...
if self.elements['latex_engine'] == 'pdflatex': if self.elements['latex_engine'] == 'pdflatex':
@ -628,12 +629,11 @@ class LaTeXTranslator(nodes.NodeVisitor):
self.elements['classoptions'] += ',' + self.babel.get_language() self.elements['classoptions'] += ',' + self.babel.get_language()
# this branch is not taken for xelatex/lualatex if default settings # this branch is not taken for xelatex/lualatex if default settings
self.elements['multilingual'] = self.elements['babel'] self.elements['multilingual'] = self.elements['babel']
if builder.config.language: if self.config.language:
self.elements['shorthandoff'] = SHORTHANDOFF self.elements['shorthandoff'] = SHORTHANDOFF
# Times fonts don't work with Cyrillic languages # Times fonts don't work with Cyrillic languages
if self.babel.uses_cyrillic() \ if self.babel.uses_cyrillic() and 'fontpkg' not in self.config.latex_elements:
and 'fontpkg' not in builder.config.latex_elements:
self.elements['fontpkg'] = '' self.elements['fontpkg'] = ''
elif self.elements['polyglossia']: elif self.elements['polyglossia']:
self.elements['classoptions'] += ',' + self.babel.get_language() self.elements['classoptions'] += ',' + self.babel.get_language()
@ -647,25 +647,25 @@ class LaTeXTranslator(nodes.NodeVisitor):
self.elements['multilingual'] = '%s\n%s' % (self.elements['polyglossia'], self.elements['multilingual'] = '%s\n%s' % (self.elements['polyglossia'],
mainlanguage) mainlanguage)
if getattr(builder, 'usepackages', None): if getattr(self.builder, 'usepackages', None):
def declare_package(packagename, options=None): def declare_package(packagename, options=None):
# type:(unicode, unicode) -> unicode # type:(unicode, unicode) -> unicode
if options: if options:
return '\\usepackage[%s]{%s}' % (options, packagename) return '\\usepackage[%s]{%s}' % (options, packagename)
else: else:
return '\\usepackage{%s}' % (packagename,) return '\\usepackage{%s}' % (packagename,)
usepackages = (declare_package(*p) for p in builder.usepackages) usepackages = (declare_package(*p) for p in self.builder.usepackages)
self.elements['usepackages'] += "\n".join(usepackages) self.elements['usepackages'] += "\n".join(usepackages)
minsecnumdepth = self.secnumdepth # 2 from legacy sphinx manual/howto minsecnumdepth = self.secnumdepth # 2 from legacy sphinx manual/howto
if document.get('tocdepth'): if self.document.get('tocdepth'):
# reduce tocdepth if `part` or `chapter` is used for top_sectionlevel # reduce tocdepth if `part` or `chapter` is used for top_sectionlevel
# tocdepth = -1: show only parts # tocdepth = -1: show only parts
# tocdepth = 0: show parts and chapters # tocdepth = 0: show parts and chapters
# tocdepth = 1: show parts, chapters and sections # tocdepth = 1: show parts, chapters and sections
# tocdepth = 2: show parts, chapters, sections and subsections # tocdepth = 2: show parts, chapters, sections and subsections
# ... # ...
tocdepth = document['tocdepth'] + self.top_sectionlevel - 2 tocdepth = self.document['tocdepth'] + self.top_sectionlevel - 2
if len(self.sectionnames) < len(LATEXSECTIONNAMES) and \ if len(self.sectionnames) < len(LATEXSECTIONNAMES) and \
self.top_sectionlevel > 0: self.top_sectionlevel > 0:
tocdepth += 1 # because top_sectionlevel is shifted by -1 tocdepth += 1 # because top_sectionlevel is shifted by -1
@ -676,16 +676,16 @@ class LaTeXTranslator(nodes.NodeVisitor):
self.elements['tocdepth'] = '\\setcounter{tocdepth}{%d}' % tocdepth self.elements['tocdepth'] = '\\setcounter{tocdepth}{%d}' % tocdepth
minsecnumdepth = max(minsecnumdepth, tocdepth) minsecnumdepth = max(minsecnumdepth, tocdepth)
if builder.config.numfig and (builder.config.numfig_secnum_depth > 0): if self.config.numfig and (self.config.numfig_secnum_depth > 0):
minsecnumdepth = max(minsecnumdepth, self.numfig_secnum_depth - 1) minsecnumdepth = max(minsecnumdepth, self.numfig_secnum_depth - 1)
if minsecnumdepth > self.secnumdepth: if minsecnumdepth > self.secnumdepth:
self.elements['secnumdepth'] = '\\setcounter{secnumdepth}{%d}' %\ self.elements['secnumdepth'] = '\\setcounter{secnumdepth}{%d}' %\
minsecnumdepth minsecnumdepth
if getattr(document.settings, 'contentsname', None): contentsname = self.get_settings().contentsname
self.elements['contentsname'] = \ self.elements['contentsname'] = self.babel_renewcommand('\\contentsname',
self.babel_renewcommand('\\contentsname', document.settings.contentsname) contentsname)
if self.elements['maxlistdepth']: if self.elements['maxlistdepth']:
self.elements['sphinxpkgoptions'] += (',maxlistdepth=%s' % self.elements['sphinxpkgoptions'] += (',maxlistdepth=%s' %
@ -699,9 +699,9 @@ class LaTeXTranslator(nodes.NodeVisitor):
if self.elements['extraclassoptions']: if self.elements['extraclassoptions']:
self.elements['classoptions'] += ',' + \ self.elements['classoptions'] += ',' + \
self.elements['extraclassoptions'] self.elements['extraclassoptions']
self.elements['numfig_format'] = self.generate_numfig_format(builder) self.elements['numfig_format'] = self.generate_numfig_format(self.builder)
self.highlighter = highlighting.PygmentsBridge('latex', builder.config.pygments_style) self.highlighter = highlighting.PygmentsBridge('latex', self.config.pygments_style)
self.context = [] # type: List[Any] self.context = [] # type: List[Any]
self.descstack = [] # type: List[unicode] self.descstack = [] # type: List[unicode]
self.table = None # type: Table self.table = None # type: Table

View File

@ -20,6 +20,7 @@ from docutils.writers.manpage import (
from sphinx import addnodes from sphinx import addnodes
from sphinx.locale import admonitionlabels, _ from sphinx.locale import admonitionlabels, _
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.docutils import SphinxTranslator
from sphinx.util.i18n import format_date from sphinx.util.i18n import format_date
from sphinx.util.nodes import NodeMatcher from sphinx.util.nodes import NodeMatcher
@ -78,17 +79,16 @@ class NestedInlineTransform:
node.parent.insert(pos + 1, newnode) node.parent.insert(pos + 1, newnode)
class ManualPageTranslator(BaseTranslator): class ManualPageTranslator(SphinxTranslator, BaseTranslator):
""" """
Custom translator. Custom translator.
""" """
_docinfo = {} # type: Dict[unicode, Any] _docinfo = {} # type: Dict[unicode, Any]
def __init__(self, builder, *args, **kwds): def __init__(self, builder, document):
# type: (Builder, Any, Any) -> None # type: (Builder, nodes.document) -> None
super(ManualPageTranslator, self).__init__(*args, **kwds) super(ManualPageTranslator, self).__init__(builder, document)
self.builder = builder
self.in_productionlist = 0 self.in_productionlist = 0
@ -96,23 +96,24 @@ class ManualPageTranslator(BaseTranslator):
self.section_level = -1 self.section_level = -1
# docinfo set by man_pages config value # docinfo set by man_pages config value
self._docinfo['title'] = self.document.settings.title settings = self.get_settings()
self._docinfo['subtitle'] = self.document.settings.subtitle self._docinfo['title'] = settings.title
if self.document.settings.authors: self._docinfo['subtitle'] = settings.subtitle
if settings.authors:
# don't set it if no author given # don't set it if no author given
self._docinfo['author'] = self.document.settings.authors self._docinfo['author'] = settings.authors
self._docinfo['manual_section'] = self.document.settings.section self._docinfo['manual_section'] = settings.section
# docinfo set by other config values # docinfo set by other config values
self._docinfo['title_upper'] = self._docinfo['title'].upper() self._docinfo['title_upper'] = self._docinfo['title'].upper()
if builder.config.today: if self.config.today:
self._docinfo['date'] = builder.config.today self._docinfo['date'] = self.config.today
else: else:
self._docinfo['date'] = format_date(builder.config.today_fmt or _('%b %d, %Y'), self._docinfo['date'] = format_date(self.config.today_fmt or _('%b %d, %Y'),
language=builder.config.language) language=self.config.language)
self._docinfo['copyright'] = builder.config.copyright self._docinfo['copyright'] = self.config.copyright
self._docinfo['version'] = builder.config.version self._docinfo['version'] = self.config.version
self._docinfo['manual_group'] = builder.config.project self._docinfo['manual_group'] = self.config.project
# Overwrite admonition label translations with our own # Overwrite admonition label translations with our own
for label, translation in admonitionlabels.items(): for label, translation in admonitionlabels.items():

View File

@ -20,6 +20,7 @@ from sphinx import addnodes, __display_version__
from sphinx.errors import ExtensionError from sphinx.errors import ExtensionError
from sphinx.locale import admonitionlabels, _, __ from sphinx.locale import admonitionlabels, _, __
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.docutils import SphinxTranslator
from sphinx.util.i18n import format_date from sphinx.util.i18n import format_date
from sphinx.writers.latex import collected_footnote from sphinx.writers.latex import collected_footnote
@ -144,8 +145,9 @@ class TexinfoWriter(writers.Writer):
setattr(self, attr, getattr(self.visitor, attr)) setattr(self, attr, getattr(self.visitor, attr))
class TexinfoTranslator(nodes.NodeVisitor): class TexinfoTranslator(SphinxTranslator):
builder = None # type: TexinfoBuilder
ignore_missing_images = False ignore_missing_images = False
default_elements = { default_elements = {
@ -165,8 +167,7 @@ class TexinfoTranslator(nodes.NodeVisitor):
def __init__(self, document, builder): def __init__(self, document, builder):
# type: (nodes.document, TexinfoBuilder) -> None # type: (nodes.document, TexinfoBuilder) -> None
super(TexinfoTranslator, self).__init__(document) super(TexinfoTranslator, self).__init__(builder, document)
self.builder = builder
self.init_settings() self.init_settings()
self.written_ids = set() # type: Set[unicode] self.written_ids = set() # type: Set[unicode]
@ -227,7 +228,7 @@ class TexinfoTranslator(nodes.NodeVisitor):
def init_settings(self): def init_settings(self):
# type: () -> None # type: () -> None
settings = self.settings = self.document.settings self.settings = settings = self.get_settings()
elements = self.elements = self.default_elements.copy() elements = self.elements = self.default_elements.copy()
elements.update({ elements.update({
# if empty, the title is set to the first section title # if empty, the title is set to the first section title
@ -243,11 +244,10 @@ class TexinfoTranslator(nodes.NodeVisitor):
language=self.builder.config.language)) language=self.builder.config.language))
}) })
# title # title
title = None # type: unicode title = settings.title # type: unicode
title = elements['title'] # type: ignore
if not title: if not title:
title = self.document.next_node(nodes.title) title_node = self.document.next_node(nodes.title)
title = (title and title.astext()) or '<untitled>' # type: ignore title = (title and title_node.astext()) or '<untitled>'
elements['title'] = self.escape_id(title) or '<untitled>' elements['title'] = self.escape_id(title) or '<untitled>'
# filename # filename
if not elements['filename']: if not elements['filename']:

View File

@ -20,6 +20,7 @@ from docutils.utils import column_width
from sphinx import addnodes from sphinx import addnodes
from sphinx.locale import admonitionlabels, _ from sphinx.locale import admonitionlabels, _
from sphinx.util.docutils import SphinxTranslator
if False: if False:
# For type annotation # For type annotation
@ -391,23 +392,23 @@ class TextWriter(writers.Writer):
self.output = cast(TextTranslator, visitor).body self.output = cast(TextTranslator, visitor).body
class TextTranslator(nodes.NodeVisitor): class TextTranslator(SphinxTranslator):
builder = None # type: TextBuilder
def __init__(self, document, builder): def __init__(self, document, builder):
# type: (nodes.document, TextBuilder) -> None # type: (nodes.document, TextBuilder) -> None
super(TextTranslator, self).__init__(document) super(TextTranslator, self).__init__(builder, document)
self.builder = builder
newlines = builder.config.text_newlines newlines = self.config.text_newlines
if newlines == 'windows': if newlines == 'windows':
self.nl = '\r\n' self.nl = '\r\n'
elif newlines == 'native': elif newlines == 'native':
self.nl = os.linesep self.nl = os.linesep
else: else:
self.nl = '\n' self.nl = '\n'
self.sectionchars = builder.config.text_sectionchars self.sectionchars = self.config.text_sectionchars
self.add_secnumbers = builder.config.text_add_secnumbers self.add_secnumbers = self.config.text_add_secnumbers
self.secnumber_suffix = builder.config.text_secnumber_suffix self.secnumber_suffix = self.config.text_secnumber_suffix
self.states = [[]] # type: List[List[Tuple[int, Union[unicode, List[unicode]]]]] self.states = [[]] # type: List[List[Tuple[int, Union[unicode, List[unicode]]]]]
self.stateindent = [0] self.stateindent = [0]
self.list_counter = [] # type: List[int] self.list_counter = [] # type: List[int]

View File

@ -35,6 +35,7 @@ def settings(app):
settings.smart_quotes = True settings.smart_quotes = True
settings.env = app.builder.env settings.env = app.builder.env
settings.env.temp_data['docname'] = 'dummy' settings.env.temp_data['docname'] = 'dummy'
settings.contentsname = 'dummy'
domain_context = sphinx_domains(settings.env) domain_context = sphinx_domains(settings.env)
domain_context.enable() domain_context.enable()
yield settings yield settings