Merge branch '2.0' into 6798_autosummary_emits_skip_member_event

This commit is contained in:
Takeshi KOMIYA
2019-11-21 00:45:57 +09:00
committed by GitHub
22 changed files with 246 additions and 70 deletions

13
CHANGES
View File

@@ -18,6 +18,8 @@ Deprecated
* ``sphinx.builders.gettext.POHEADER`` * ``sphinx.builders.gettext.POHEADER``
* ``sphinx.io.SphinxStandaloneReader.app`` * ``sphinx.io.SphinxStandaloneReader.app``
* ``sphinx.io.SphinxStandaloneReader.env`` * ``sphinx.io.SphinxStandaloneReader.env``
* ``sphinx.util.texescape.tex_escape_map``
* ``sphinx.util.texescape.tex_hl_escape_map_new``
Features added Features added
-------------- --------------
@@ -31,9 +33,12 @@ Features added
* #1331: Add new config variable: :confval:`user_agent` * #1331: Add new config variable: :confval:`user_agent`
* #6000: LaTeX: have backslash also be an inline literal word wrap break * #6000: LaTeX: have backslash also be an inline literal word wrap break
character character
* #4186: LaTeX: Support upLaTeX as a new :confval:`latex_engine` (experimental)
* #6812: Improve a warning message when extensions are not parallel safe * #6812: Improve a warning message when extensions are not parallel safe
* #6818: Improve Intersphinx performance for multiple remote inventories. * #6818: Improve Intersphinx performance for multiple remote inventories.
* #2546: apidoc: .so file support
* #6798: autosummary: emit ``autodoc-skip-member`` event on generating stub file * #6798: autosummary: emit ``autodoc-skip-member`` event on generating stub file
* #6483: i18n: make explicit titles in toctree translatable
Bugs fixed Bugs fixed
---------- ----------
@@ -44,8 +49,10 @@ Bugs fixed
.. _latex3/latex2e#173: https://github.com/latex3/latex2e/issues/173 .. _latex3/latex2e#173: https://github.com/latex3/latex2e/issues/173
* #6618: LaTeX: Avoid section names at the end of a page * #6618: LaTeX: Avoid section names at the end of a page
* #6738: LaTeX: Do not replace unicode characters by LaTeX macros on unicode * #6738: LaTeX: Do not replace unicode characters by LaTeX macros on unicode
supported LaTeX engines supported LaTeX engines: ¶, §, €, ∞, ±, →, ‣, , superscript and subscript
digits go through "as is" (as default OpenType font supports them)
* #6704: linkcheck: Be defensive and handle newly defined HTTP error code * #6704: linkcheck: Be defensive and handle newly defined HTTP error code
* #6806: linkcheck: Failure on parsing content
* #6655: image URLs containing ``data:`` causes gettext builder crashed * #6655: image URLs containing ``data:`` causes gettext builder crashed
* #6584: i18n: Error when compiling message catalogs on Hindi * #6584: i18n: Error when compiling message catalogs on Hindi
* #6718: i18n: KeyError is raised if section title and table title are same * #6718: i18n: KeyError is raised if section title and table title are same
@@ -60,6 +67,10 @@ Bugs fixed
code-block long enough not to fit on one page code-block long enough not to fit on one page
* #6809: LaTeX: code-block in a danger type admonition can easily spill over * #6809: LaTeX: code-block in a danger type admonition can easily spill over
bottom of page bottom of page
* #6793: texinfo: Code examples broken following "sidebar"
* #6813: An orphan warning is emitted for included document on Windows. Thanks
to @drillan
* #6850: Fix smartypants module calls re.sub() with wrong options
Testing Testing
-------- --------

View File

@@ -41,6 +41,16 @@ The following is a list of deprecated interfaces.
- 4.0 - 4.0
- ``sphinx.io.SphinxStandaloneReader.setup()`` - ``sphinx.io.SphinxStandaloneReader.setup()``
* - ``sphinx.util.texescape.tex_escape_map``
- 2.3
- 4.0
- ``sphinx.util.texescape.escape()``
* - ``sphinx.util.texescape.tex_hl_escape_map_new``
- 2.3
- 4.0
- ``sphinx.util.texescape.hlescape()``
* - ``sphinx.domains.math.MathDomain.add_equation()`` * - ``sphinx.domains.math.MathDomain.add_equation()``
- 2.2 - 2.2
- 4.0 - 4.0

View File

@@ -1836,6 +1836,7 @@ These options influence LaTeX output.
* ``'xelatex'`` -- XeLaTeX * ``'xelatex'`` -- XeLaTeX
* ``'lualatex'`` -- LuaLaTeX * ``'lualatex'`` -- LuaLaTeX
* ``'platex'`` -- pLaTeX (default if :confval:`language` is ``'ja'``) * ``'platex'`` -- pLaTeX (default if :confval:`language` is ``'ja'``)
* ``'uplatex'`` -- upLaTeX (experimental)
``'pdflatex'``\ 's support for Unicode characters is limited. ``'pdflatex'``\ 's support for Unicode characters is limited.
@@ -1861,6 +1862,10 @@ These options influence LaTeX output.
Use ``xelatex`` by default for Greek documents. Use ``xelatex`` by default for Greek documents.
.. versionchanged:: 2.3
Add ``uplatex`` support.
Contrarily to :ref:`MathJaX math rendering in HTML output <math-support>`, Contrarily to :ref:`MathJaX math rendering in HTML output <math-support>`,
LaTeX requires some extra configuration to support Unicode literals in LaTeX requires some extra configuration to support Unicode literals in
:rst:dir:`math`: the only comprehensive solution (as far as we know) is to :rst:dir:`math`: the only comprehensive solution (as far as we know) is to

View File

@@ -63,20 +63,38 @@ class toctree(nodes.General, nodes.Element, translatable):
def preserve_original_messages(self): def preserve_original_messages(self):
# type: () -> None # type: () -> None
# toctree entries
rawentries = self.setdefault('rawentries', [])
for title, docname in self['entries']:
if title:
rawentries.append(title)
# :caption: option
if self.get('caption'): if self.get('caption'):
self['rawcaption'] = self['caption'] self['rawcaption'] = self['caption']
def apply_translated_message(self, original_message, translated_message): def apply_translated_message(self, original_message, translated_message):
# type: (str, str) -> None # type: (str, str) -> None
# toctree entries
for i, (title, docname) in enumerate(self['entries']):
if title == original_message:
self['entries'][i] = (translated_message, docname)
# :caption: option
if self.get('rawcaption') == original_message: if self.get('rawcaption') == original_message:
self['caption'] = translated_message self['caption'] = translated_message
def extract_original_messages(self): def extract_original_messages(self):
# type: () -> List[str] # type: () -> List[str]
messages = [] # type: List[str]
# toctree entries
messages.extend(self.get('rawentries', []))
# :caption: option
if 'rawcaption' in self: if 'rawcaption' in self:
return [self['rawcaption']] messages.append(self['rawcaption'])
else: return messages
return []
# domain-specific object descriptions (class, function etc.) # domain-specific object descriptions (class, function etc.)

View File

@@ -427,8 +427,12 @@ def default_latex_engine(config: Config) -> str:
def default_latex_docclass(config: Config) -> Dict[str, str]: def default_latex_docclass(config: Config) -> Dict[str, str]:
""" Better default latex_docclass settings for specific languages. """ """ Better default latex_docclass settings for specific languages. """
if config.language == 'ja': if config.language == 'ja':
return {'manual': 'jsbook', if config.latex_engine == 'uplatex':
'howto': 'jreport'} return {'manual': 'ujbook',
'howto': 'ujreport'}
else:
return {'manual': 'jsbook',
'howto': 'jreport'}
else: else:
return {} return {}
@@ -440,10 +444,12 @@ def default_latex_use_xindy(config: Config) -> bool:
def default_latex_documents(config: Config) -> List[Tuple[str, str, str, str, str]]: def default_latex_documents(config: Config) -> List[Tuple[str, str, str, str, str]]:
""" Better default latex_documents settings. """ """ Better default latex_documents settings. """
project = texescape.escape(config.project, config.latex_engine)
author = texescape.escape(config.author, config.latex_engine)
return [(config.master_doc, return [(config.master_doc,
make_filename_from_project(config.project) + '.tex', make_filename_from_project(config.project) + '.tex',
texescape.escape_abbr(texescape.escape(config.project)), texescape.escape_abbr(project),
texescape.escape_abbr(texescape.escape(config.author)), texescape.escape_abbr(author),
'manual')] 'manual')]
@@ -454,7 +460,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.connect('config-inited', validate_config_values) app.connect('config-inited', validate_config_values)
app.add_config_value('latex_engine', default_latex_engine, None, app.add_config_value('latex_engine', default_latex_engine, None,
ENUM('pdflatex', 'xelatex', 'lualatex', 'platex')) ENUM('pdflatex', 'xelatex', 'lualatex', 'platex', 'uplatex'))
app.add_config_value('latex_documents', default_latex_documents, None) app.add_config_value('latex_documents', default_latex_documents, None)
app.add_config_value('latex_logo', None, None, [str]) app.add_config_value('latex_logo', None, None, [str])
app.add_config_value('latex_appendices', [], None) app.add_config_value('latex_appendices', [], None)

View File

@@ -59,6 +59,9 @@ def check_anchor(response: requests.requests.Response, anchor: str) -> bool:
# Read file in chunks. If we find a matching anchor, we break # Read file in chunks. If we find a matching anchor, we break
# the loop early in hopes not to have to download the whole thing. # the loop early in hopes not to have to download the whole thing.
for chunk in response.iter_content(chunk_size=4096, decode_unicode=True): for chunk in response.iter_content(chunk_size=4096, decode_unicode=True):
if isinstance(chunk, bytes): # requests failed to decode
chunk = chunk.decode() # manually try to decode it
parser.feed(chunk) parser.feed(chunk)
if parser.found: if parser.found:
break break

View File

@@ -21,6 +21,7 @@ import os
import sys import sys
import warnings import warnings
from fnmatch import fnmatch from fnmatch import fnmatch
from importlib.machinery import EXTENSION_SUFFIXES
from os import path from os import path
from typing import Any, List, Tuple from typing import Any, List, Tuple
@@ -45,7 +46,7 @@ else:
] ]
INITPY = '__init__.py' INITPY = '__init__.py'
PY_SUFFIXES = {'.py', '.pyx'} PY_SUFFIXES = ('.py', '.pyx') + tuple(EXTENSION_SUFFIXES)
template_dir = path.join(package_dir, 'templates', 'apidoc') template_dir = path.join(package_dir, 'templates', 'apidoc')
@@ -232,7 +233,7 @@ def recurse_tree(rootpath: str, excludes: List[str], opts: Any,
for root, subs, files in os.walk(rootpath, followlinks=followlinks): for root, subs, files in os.walk(rootpath, followlinks=followlinks):
# document only Python module files (that aren't excluded) # document only Python module files (that aren't excluded)
py_files = sorted(f for f in files py_files = sorted(f for f in files
if path.splitext(f)[1] in PY_SUFFIXES and if f.endswith(PY_SUFFIXES) and
not is_excluded(path.join(root, f), excludes)) not is_excluded(path.join(root, f), excludes))
is_pkg = INITPY in py_files is_pkg = INITPY in py_files
is_namespace = INITPY not in py_files and implicit_namespaces is_namespace = INITPY not in py_files and implicit_namespaces
@@ -270,7 +271,7 @@ def recurse_tree(rootpath: str, excludes: List[str], opts: Any,
assert root == rootpath and root_package is None assert root == rootpath and root_package is None
for py_file in py_files: for py_file in py_files:
if not is_skipped_module(path.join(rootpath, py_file), opts, excludes): if not is_skipped_module(path.join(rootpath, py_file), opts, excludes):
module = path.splitext(py_file)[0] module = py_file.split('.')[0]
create_module_file(root_package, module, opts, user_template_dir) create_module_file(root_package, module, opts, user_template_dir)
toplevels.append(module) toplevels.append(module)

View File

@@ -27,10 +27,9 @@ from sphinx.domains import Domain
from sphinx.environment import BuildEnvironment from sphinx.environment import BuildEnvironment
from sphinx.errors import NoUri from sphinx.errors import NoUri
from sphinx.locale import _, __ from sphinx.locale import _, __
from sphinx.util import logging from sphinx.util import logging, texescape
from sphinx.util.docutils import SphinxDirective from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import make_refnode from sphinx.util.nodes import make_refnode
from sphinx.util.texescape import tex_escape_map
from sphinx.writers.html import HTMLTranslator from sphinx.writers.html import HTMLTranslator
from sphinx.writers.latex import LaTeXTranslator from sphinx.writers.latex import LaTeXTranslator
@@ -301,8 +300,10 @@ def latex_visit_todo_node(self: LaTeXTranslator, node: todo_node) -> None:
if self.config.todo_include_todos: if self.config.todo_include_todos:
self.body.append('\n\\begin{sphinxadmonition}{note}{') self.body.append('\n\\begin{sphinxadmonition}{note}{')
self.body.append(self.hypertarget_to(node)) self.body.append(self.hypertarget_to(node))
title_node = cast(nodes.title, node[0]) title_node = cast(nodes.title, node[0])
self.body.append('%s:}' % title_node.astext().translate(tex_escape_map)) title = texescape.escape(title_node.astext(), self.config.latex_engine)
self.body.append('%s:}' % title)
node.pop(0) node.pop(0)
else: else:
raise nodes.SkipNode raise nodes.SkipNode

View File

@@ -27,8 +27,7 @@ from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.ext import doctest from sphinx.ext import doctest
from sphinx.locale import __ from sphinx.locale import __
from sphinx.pygments_styles import SphinxStyle, NoneStyle from sphinx.pygments_styles import SphinxStyle, NoneStyle
from sphinx.util import logging from sphinx.util import logging, texescape
from sphinx.util.texescape import tex_hl_escape_map_new
if False: if False:
# For type annotation # For type annotation
@@ -68,9 +67,11 @@ class PygmentsBridge:
html_formatter = HtmlFormatter html_formatter = HtmlFormatter
latex_formatter = LatexFormatter latex_formatter = LatexFormatter
def __init__(self, dest='html', stylename='sphinx', trim_doctest_flags=None): def __init__(self, dest='html', stylename='sphinx', trim_doctest_flags=None,
# type: (str, str, bool) -> None latex_engine=None):
# type: (str, str, bool, str) -> None
self.dest = dest self.dest = dest
self.latex_engine = latex_engine
style = self.get_style(stylename) style = self.get_style(stylename)
self.formatter_args = {'style': style} # type: Dict[str, Any] self.formatter_args = {'style': style} # type: Dict[str, Any]
@@ -112,7 +113,7 @@ class PygmentsBridge:
# first, escape highlighting characters like Pygments does # first, escape highlighting characters like Pygments does
source = source.translate(escape_hl_chars) source = source.translate(escape_hl_chars)
# then, escape all characters nonrepresentable in LaTeX # then, escape all characters nonrepresentable in LaTeX
source = source.translate(tex_hl_escape_map_new) source = texescape.escape(source, self.latex_engine)
return '\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n' + \ return '\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n' + \
source + '\\end{Verbatim}\n' source + '\\end{Verbatim}\n'
@@ -192,7 +193,7 @@ class PygmentsBridge:
if self.dest == 'html': if self.dest == 'html':
return hlsource return hlsource
else: else:
return hlsource.translate(tex_hl_escape_map_new) return texescape.hlescape(hlsource, self.latex_engine)
def get_stylesheet(self): def get_stylesheet(self):
# type: () -> str # type: () -> str

View File

@@ -13,6 +13,7 @@ import os
from sphinx.locale import __ from sphinx.locale import __
from sphinx.util import get_matching_files from sphinx.util import get_matching_files
from sphinx.util import logging from sphinx.util import logging
from sphinx.util import path_stabilize
from sphinx.util.matching import compile_matchers from sphinx.util.matching import compile_matchers
from sphinx.util.osutil import SEP, relpath from sphinx.util.osutil import SEP, relpath
@@ -71,6 +72,7 @@ class Project:
filename = relpath(filename, self.srcdir) filename = relpath(filename, self.srcdir)
for suffix in self.source_suffix: for suffix in self.source_suffix:
if filename.endswith(suffix): if filename.endswith(suffix):
filename = path_stabilize(filename)
return filename[:-len(suffix)] return filename[:-len(suffix)]
# the file does not have docname # the file does not have docname

View File

@@ -37,7 +37,7 @@ XINDYOPTS += -M LatinRules.xdy
# format: pdf or dvi (used only by archive targets) # format: pdf or dvi (used only by archive targets)
FMT = pdf FMT = pdf
{% if latex_engine == 'platex' -%} {% if latex_engine in ('platex', 'uplatex') -%}
# latexmkrc is read then overridden by latexmkjarc # latexmkrc is read then overridden by latexmkjarc
LATEX = latexmk -r latexmkjarc -dvi LATEX = latexmk -r latexmkjarc -dvi
PDFLATEX = latexmk -r latexmkjarc -pdfdvi -dvi- -ps- PDFLATEX = latexmk -r latexmkjarc -pdfdvi -dvi- -ps-
@@ -49,7 +49,7 @@ PDFLATEX = latexmk -pdf -dvi- -ps-
%.png %.gif %.jpg %.jpeg: FORCE_MAKE %.png %.gif %.jpg %.jpeg: FORCE_MAKE
extractbb '$@' extractbb '$@'
{% if latex_engine == 'platex' -%} {% if latex_engine in ('platex', 'uplatex') -%}
%.dvi: %.tex $(ALLIMGS) FORCE_MAKE %.dvi: %.tex $(ALLIMGS) FORCE_MAKE
for f in *.pdf; do extractbb "$$f"; done for f in *.pdf; do extractbb "$$f"; done
$(LATEX) $(LATEXMKOPTS) '$<' $(LATEX) $(LATEXMKOPTS) '$<'
@@ -62,7 +62,7 @@ PDFLATEX = latexmk -pdf -dvi- -ps-
%.ps: %.dvi %.ps: %.dvi
dvips '$<' dvips '$<'
{% if latex_engine == 'platex' -%} {% if latex_engine in ('platex', 'uplatex') -%}
%.pdf: %.tex $(ALLIMGS) FORCE_MAKE %.pdf: %.tex $(ALLIMGS) FORCE_MAKE
for f in *.pdf; do extractbb "$$f"; done for f in *.pdf; do extractbb "$$f"; done
{%- else -%} {%- else -%}

View File

@@ -1,4 +1,4 @@
$latex = 'platex ' . $ENV{'LATEXOPTS'} . ' -kanji=utf8 %O %S'; $latex = '{{ latex_engine }} ' . $ENV{'LATEXOPTS'} . ' -kanji=utf8 %O %S';
$dvipdf = 'dvipdfmx %O -o %D %S'; $dvipdf = 'dvipdfmx %O -o %D %S';
$makeindex = 'internal mendex %S %B %D'; $makeindex = 'internal mendex %S %B %D';
sub mendex { sub mendex {

View File

@@ -155,7 +155,7 @@ def educateQuotes(text: str, language: str = 'en') -> str:
# Special case for decade abbreviations (the '80s): # Special case for decade abbreviations (the '80s):
if language.startswith('en'): # TODO similar cases in other languages? if language.startswith('en'): # TODO similar cases in other languages?
text = re.sub(r"""'(?=\d{2}s)""", apostrophe, text, re.UNICODE) text = re.sub(r"""'(?=\d{2}s)""", apostrophe, text, flags=re.UNICODE)
close_class = r"""[^\ \t\r\n\[\{\(\-]""" close_class = r"""[^\ \t\r\n\[\{\(\-]"""
dec_dashes = r"""&#8211;|&#8212;""" dec_dashes = r"""&#8211;|&#8212;"""

View File

@@ -9,6 +9,7 @@
""" """
import os import os
from functools import partial
from typing import Dict, List, Union from typing import Dict, List, Union
from jinja2.loaders import BaseLoader from jinja2.loaders import BaseLoader
@@ -69,8 +70,9 @@ class LaTeXRenderer(SphinxRenderer):
super().__init__(template_path) super().__init__(template_path)
# use texescape as escape filter # use texescape as escape filter
self.env.filters['e'] = texescape.get_escape_func(latex_engine) escape = partial(texescape.escape, latex_engine=latex_engine)
self.env.filters['escape'] = texescape.get_escape_func(latex_engine) self.env.filters['e'] = escape
self.env.filters['escape'] = escape
self.env.filters['eabbr'] = texescape.escape_abbr self.env.filters['eabbr'] = texescape.escape_abbr
# use JSP/eRuby like tagging instead because curly bracket; the default # use JSP/eRuby like tagging instead because curly bracket; the default

View File

@@ -9,7 +9,10 @@
""" """
import re import re
from typing import Callable, Dict from typing import Dict
from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
tex_replacements = [ tex_replacements = [
# map TeX special chars # map TeX special chars
@@ -20,30 +23,26 @@ tex_replacements = [
('_', r'\_'), ('_', r'\_'),
('{', r'\{'), ('{', r'\{'),
('}', r'\}'), ('}', r'\}'),
('[', r'{[}'),
(']', r'{]}'),
('`', r'{}`'),
('\\', r'\textbackslash{}'), ('\\', r'\textbackslash{}'),
('~', r'\textasciitilde{}'), ('~', r'\textasciitilde{}'),
('^', r'\textasciicircum{}'),
# map chars to avoid mis-interpretation in LaTeX
('[', r'{[}'),
(']', r'{]}'),
# map chars to avoid TeX ligatures
# 1. ' - and , not here for some legacy reason
# 2. no effect with lualatex (done otherwise: #5790)
('`', r'{}`'),
('<', r'\textless{}'), ('<', r'\textless{}'),
('>', r'\textgreater{}'), ('>', r'\textgreater{}'),
('^', r'\textasciicircum{}'),
# map special Unicode characters to TeX commands # map special Unicode characters to TeX commands
('', r'\P{}'),
('§', r'\S{}'),
('', r'\texteuro{}'),
('', r'\(\infty\)'),
('±', r'\(\pm\)'),
('', r'\(\rightarrow\)'),
('', r'\(\rightarrow\)'),
('', r'\(\checkmark\)'), ('', r'\(\checkmark\)'),
('', r'\(\pmb{\checkmark}\)'), ('', r'\(\pmb{\checkmark}\)'),
# used to separate -- in options # used to separate -- in options
('', r'{}'), ('', r'{}'),
# map some special Unicode characters to similar ASCII ones # map some special Unicode characters to similar ASCII ones
# (even for Unicode LaTeX as may not be supported by OpenType font)
('', r'\_'), ('', r'\_'),
('', r'\textendash{}'),
('|', r'\textbar{}'),
('', r'e'), ('', r'e'),
('', r'i'), ('', r'i'),
# Greek alphabet not escaped: pdflatex handles it via textalpha and inputenc # Greek alphabet not escaped: pdflatex handles it via textalpha and inputenc
@@ -53,6 +52,15 @@ tex_replacements = [
# A map Unicode characters to LaTeX representation # A map Unicode characters to LaTeX representation
# (for LaTeX engines which don't support unicode) # (for LaTeX engines which don't support unicode)
unicode_tex_replacements = [ unicode_tex_replacements = [
# map some more common Unicode characters to TeX commands
('', r'\P{}'),
('§', r'\S{}'),
('', r'\texteuro{}'),
('', r'\(\infty\)'),
('±', r'\(\pm\)'),
('', r'\(\rightarrow\)'),
('', r'\(\rightarrow\)'),
('', r'\textendash{}'),
# superscript # superscript
('', r'\(\sp{\text{0}}\)'), ('', r'\(\sp{\text{0}}\)'),
('¹', r'\(\sp{\text{1}}\)'), ('¹', r'\(\sp{\text{1}}\)'),
@@ -77,28 +85,38 @@ unicode_tex_replacements = [
('', r'\(\sb{\text{9}}\)'), ('', r'\(\sb{\text{9}}\)'),
] ]
tex_escape_map = {} # type: Dict[int, str] tex_replace_map = {} # type: Dict[int, str]
tex_escape_map_without_unicode = {} # type: Dict[int, str]
tex_replace_map = {} _tex_escape_map = {} # type: Dict[int, str]
tex_hl_escape_map_new = {} _tex_escape_map_without_unicode = {} # type: Dict[int, str]
_tex_hlescape_map = {} # type: Dict[int, str]
_tex_hlescape_map_without_unicode = {} # type: Dict[int, str]
def get_escape_func(latex_engine: str) -> Callable[[str], str]: deprecated_alias('sphinx.util.texescape',
"""Get escape() function for given latex_engine.""" {
if latex_engine in ('lualatex', 'xelatex'): 'tex_escape_map': _tex_escape_map,
return escape_for_unicode_latex_engine 'tex_hl_escape_map_new': _tex_hlescape_map,
else: },
return escape RemovedInSphinx40Warning)
def escape(s: str) -> str: def escape(s: str, latex_engine: str = None) -> str:
"""Escape text for LaTeX output.""" """Escape text for LaTeX output."""
return s.translate(tex_escape_map) if latex_engine in ('lualatex', 'xelatex'):
# unicode based LaTeX engine
return s.translate(_tex_escape_map_without_unicode)
else:
return s.translate(_tex_escape_map)
def escape_for_unicode_latex_engine(s: str) -> str: def hlescape(s: str, latex_engine: str = None) -> str:
"""Escape text for unicode supporting LaTeX engine.""" """Escape text for LaTeX highlighter."""
return s.translate(tex_escape_map_without_unicode) if latex_engine in ('lualatex', 'xelatex'):
# unicode based LaTeX engine
return s.translate(_tex_hlescape_map_without_unicode)
else:
return s.translate(_tex_hlescape_map)
def escape_abbr(text: str) -> str: def escape_abbr(text: str) -> str:
@@ -108,15 +126,19 @@ def escape_abbr(text: str) -> str:
def init() -> None: def init() -> None:
for a, b in tex_replacements: for a, b in tex_replacements:
tex_escape_map[ord(a)] = b _tex_escape_map[ord(a)] = b
tex_escape_map_without_unicode[ord(a)] = b _tex_escape_map_without_unicode[ord(a)] = b
tex_replace_map[ord(a)] = '_' tex_replace_map[ord(a)] = '_'
for a, b in unicode_tex_replacements: for a, b in unicode_tex_replacements:
tex_escape_map[ord(a)] = b _tex_escape_map[ord(a)] = b
tex_replace_map[ord(a)] = '_' tex_replace_map[ord(a)] = '_'
for a, b in tex_replacements: for a, b in tex_replacements:
if a in '[]{}\\': if a in '[]{}\\':
continue continue
tex_hl_escape_map_new[ord(a)] = b _tex_hlescape_map[ord(a)] = b
_tex_hlescape_map_without_unicode[ord(a)] = b
for a, b in unicode_tex_replacements:
_tex_hlescape_map[ord(a)] = b

View File

@@ -28,11 +28,11 @@ from sphinx.deprecation import (
from sphinx.domains.std import StandardDomain from sphinx.domains.std import StandardDomain
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, texescape
from sphinx.util.docutils import SphinxTranslator from sphinx.util.docutils import SphinxTranslator
from sphinx.util.nodes import clean_astext, get_prev_node from sphinx.util.nodes import clean_astext, get_prev_node
from sphinx.util.template import LaTeXRenderer from sphinx.util.template import LaTeXRenderer
from sphinx.util.texescape import get_escape_func, tex_replace_map from sphinx.util.texescape import tex_replace_map
try: try:
from docutils.utils.roman import toRoman from docutils.utils.roman import toRoman
@@ -221,6 +221,15 @@ ADDITIONAL_SETTINGS = {
'fncychap': '', 'fncychap': '',
'geometry': '\\usepackage[dvipdfm]{geometry}', 'geometry': '\\usepackage[dvipdfm]{geometry}',
}, },
'uplatex': {
'latex_engine': 'uplatex',
'babel': '',
'classoptions': ',dvipdfmx',
'fontpkg': '\\usepackage{times}',
'textgreek': '',
'fncychap': '',
'geometry': '\\usepackage[dvipdfm]{geometry}',
},
# special settings for latex_engine + language_code # special settings for latex_engine + language_code
('xelatex', 'fr'): { ('xelatex', 'fr'): {
@@ -500,9 +509,6 @@ class LaTeXTranslator(SphinxTranslator):
self.compact_list = 0 self.compact_list = 0
self.first_param = 0 self.first_param = 0
# escape helper
self.escape = get_escape_func(self.config.latex_engine)
# sort out some elements # sort out some elements
self.elements = self.builder.context.copy() self.elements = self.builder.context.copy()
@@ -653,7 +659,8 @@ class LaTeXTranslator(SphinxTranslator):
self.elements['classoptions'] += ',' + \ self.elements['classoptions'] += ',' + \
self.elements['extraclassoptions'] self.elements['extraclassoptions']
self.highlighter = highlighting.PygmentsBridge('latex', self.config.pygments_style) self.highlighter = highlighting.PygmentsBridge('latex', self.config.pygments_style,
latex_engine=self.config.latex_engine)
self.context = [] # type: List[Any] self.context = [] # type: List[Any]
self.descstack = [] # type: List[str] self.descstack = [] # type: List[str]
self.table = None # type: Table self.table = None # type: Table
@@ -735,6 +742,9 @@ class LaTeXTranslator(SphinxTranslator):
# type: (str) -> str # type: (str) -> str
return '\\autopageref*{%s}' % self.idescape(id) return '\\autopageref*{%s}' % self.idescape(id)
def escape(self, s: str) -> str:
return texescape.escape(s, self.config.latex_engine)
def idescape(self, id): def idescape(self, id):
# type: (str) -> str # type: (str) -> str
return '\\detokenize{%s}' % str(id).translate(tex_replace_map).\ return '\\detokenize{%s}' % str(id).translate(tex_replace_map).\
@@ -1815,6 +1825,7 @@ class LaTeXTranslator(SphinxTranslator):
value = value.replace('"', '""') value = value.replace('"', '""')
value = value.replace('@', '"@') value = value.replace('@', '"@')
value = value.replace('!', '"!') value = value.replace('!', '"!')
value = value.replace('|', r'\textbar{}')
return value return value
def style(string): def style(string):
@@ -2163,8 +2174,6 @@ class LaTeXTranslator(SphinxTranslator):
node.rawsource, lang, opts=opts, linenos=linenos, node.rawsource, lang, opts=opts, linenos=linenos,
location=(self.curfilestack[-1], node.line), **highlight_args location=(self.curfilestack[-1], node.line), **highlight_args
) )
# workaround for Unicode issue
hlcode = hlcode.replace('', '@texteuro[]')
if self.in_footnote: if self.in_footnote:
self.body.append('\n\\sphinxSetupCodeBlockInFootnote') self.body.append('\n\\sphinxSetupCodeBlockInFootnote')
hlcode = hlcode.replace('\\begin{Verbatim}', hlcode = hlcode.replace('\\begin{Verbatim}',

View File

@@ -1284,6 +1284,7 @@ class TexinfoTranslator(SphinxTranslator):
title = cast(nodes.title, node[0]) title = cast(nodes.title, node[0])
self.visit_rubric(title) self.visit_rubric(title)
self.body.append('%s\n' % self.escape(title.astext())) self.body.append('%s\n' % self.escape(title.astext()))
self.depart_rubric(title)
def depart_topic(self, node): def depart_topic(self, node):
# type: (nodes.Element) -> None # type: (nodes.Element) -> None

View File

@@ -0,0 +1,10 @@
i18n with toctree
=================
.. toctree::
:caption: caption
figure <figure>
table
https://www.sphinx-doc.org/
self

View File

@@ -0,0 +1,31 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C)
# This file is distributed under the same license as the Sphinx intl <Tests> package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Sphinx intl <Tests> 2013.120\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-01 10:24+0900\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"
#: ../../toctree.txt:4
msgid "figure"
msgstr "FIGURE"
#: ../../toctree.txt:4
#: ../../toctree.txt:4
msgid "caption"
msgstr "CAPTION"
#: ../../toctree.txt:2
msgid "i18n with toctree"
msgstr "I18N WITH TOCTREE"

View File

@@ -315,7 +315,7 @@ def test_numref_with_prefix2(app, status, warning):
assert ('\\hyperref[\\detokenize{baz:table22}]' assert ('\\hyperref[\\detokenize{baz:table22}]'
'{Table:\\ref{\\detokenize{baz:table22}}}') in result '{Table:\\ref{\\detokenize{baz:table22}}}') in result
assert ('\\hyperref[\\detokenize{index:code-1}]{Code-\\ref{\\detokenize{index:code-1}} ' assert ('\\hyperref[\\detokenize{index:code-1}]{Code-\\ref{\\detokenize{index:code-1}} '
'\\textbar{} }') in result '| }') in result
assert ('\\hyperref[\\detokenize{baz:code22}]' assert ('\\hyperref[\\detokenize{baz:code22}]'
'{Code-\\ref{\\detokenize{baz:code22}}}') in result '{Code-\\ref{\\detokenize{baz:code22}}}') in result
assert ('\\hyperref[\\detokenize{foo:foo}]' assert ('\\hyperref[\\detokenize{foo:foo}]'
@@ -1414,6 +1414,7 @@ def test_default_latex_documents():
'project': 'STASI™ Documentation', 'project': 'STASI™ Documentation',
'author': "Wolfgang Schäuble & G'Beckstein."}) 'author': "Wolfgang Schäuble & G'Beckstein."})
config.init_values() config.init_values()
config.add('latex_engine', None, True, None)
expected = [('index', 'stasi.tex', 'STASI™ Documentation', expected = [('index', 'stasi.tex', 'STASI™ Documentation',
r"Wolfgang Schäuble \& G'Beckstein.\@{}", 'manual')] r"Wolfgang Schäuble \& G'Beckstein.\@{}", 'manual')]
assert default_latex_documents(config) == expected assert default_latex_documents(config) == expected

View File

@@ -465,6 +465,30 @@ def test_text_table(app):
assert expect_msg.string in result assert expect_msg.string in result
@sphinx_intl
@pytest.mark.sphinx('gettext')
@pytest.mark.test_params(shared_result='test_intl_gettext')
def test_gettext_toctree(app):
app.build()
# --- toctree
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'toctree.po')
actual = read_po(app.outdir / 'toctree.pot')
for expect_msg in [m for m in expect if m.id]:
assert expect_msg.id in [m.id for m in actual if m.id]
@sphinx_intl
@pytest.mark.sphinx('text')
@pytest.mark.test_params(shared_result='test_intl_basic')
def test_text_toctree(app):
app.build()
# --- toctree
result = (app.outdir / 'toctree.txt').text()
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'toctree.po')
for expect_msg in [m for m in expect if m.id]:
assert expect_msg.string in result
@sphinx_intl @sphinx_intl
@pytest.mark.sphinx('gettext') @pytest.mark.sphinx('gettext')
@pytest.mark.test_params(shared_result='test_intl_gettext') @pytest.mark.test_params(shared_result='test_intl_gettext')

View File

@@ -312,6 +312,24 @@ def test_inline(get_verifier, type, rst, html_expected, latex_expected):
verifier(rst, html_expected, latex_expected) verifier(rst, html_expected, latex_expected)
@pytest.mark.sphinx(confoverrides={'latex_engine': 'xelatex'})
@pytest.mark.parametrize('type,rst,html_expected,latex_expected', [
(
# in verbatim code fragments
'verify',
'::\n\n\\∞${}',
None,
('\\begin{sphinxVerbatim}[commandchars=\\\\\\{\\}]\n'
'\\PYGZbs{}\\PYGZdl{}\\PYGZob{}\\PYGZcb{}\n'
'\\end{sphinxVerbatim}'),
),
])
def test_inline_for_unicode_latex_engine(get_verifier, type, rst,
html_expected, latex_expected):
verifier = get_verifier(type)
verifier(rst, html_expected, latex_expected)
def test_samp_role(parse): def test_samp_role(parse):
# no braces # no braces
text = ':samp:`a{b}c`' text = ':samp:`a{b}c`'