Merge branch '2.0'

This commit is contained in:
Takeshi KOMIYA 2019-12-22 18:47:30 +09:00
commit 5640cf879f
31 changed files with 314 additions and 69 deletions

View File

@ -46,6 +46,8 @@ Incompatible changes
Deprecated
----------
* ``sphinx.environment.BuildEnvironment.indexentries``
* ``sphinx.environment.collectors.indexentries.IndexEntriesCollector``
* ``sphinx.io.FiletypeNotFoundError``
* ``sphinx.io.get_filetype()``
@ -55,11 +57,13 @@ Features added
* #6910: inheritance_diagram: Make the background of diagrams transparent
* #6446: duration: Add ``sphinx.ext.durations`` to inspect which documents slow
down the build
* #6837: LaTeX: Support a nested table
Bugs fixed
----------
* #6925: html: Remove redundant type="text/javascript" from <script> elements
* #6906: autodoc: failed to read the source codes encoeded in cp1251
Testing
--------

View File

@ -26,6 +26,16 @@ The following is a list of deprecated interfaces.
- (will be) Removed
- Alternatives
* - ``sphinx.environment.BuildEnvironment.indexentries``
- 2.4
- 4.0
- ``sphinx.domains.index.IndexDomain``
* - ``sphinx.environment.collectors.indexentries.IndexEntriesCollector``
- 2.4
- 4.0
- ``sphinx.domains.index.IndexDomain``
* - ``sphinx.io.FiletypeNotFoundError``
- 2.4
- 4.0

View File

@ -78,6 +78,7 @@ builtin_extensions = (
'sphinx.domains.changeset',
'sphinx.domains.citation',
'sphinx.domains.cpp',
'sphinx.domains.index',
'sphinx.domains.javascript',
'sphinx.domains.math',
'sphinx.domains.python',
@ -106,7 +107,6 @@ builtin_extensions = (
'sphinx.environment.collectors.metadata',
'sphinx.environment.collectors.title',
'sphinx.environment.collectors.toctree',
'sphinx.environment.collectors.indexentries',
# 1st party extensions
'sphinxcontrib.applehelp',
'sphinxcontrib.devhelp',
@ -350,8 +350,8 @@ class Sphinx:
if self._warncount and self.keep_going:
self.statuscode = 1
status = (self.statuscode == 0 and
__('succeeded') or __('finished with problems'))
status = (__('succeeded') if self.statuscode == 0
else __('finished with problems'))
if self._warncount:
if self.warningiserror:
msg = __('build %s, %s warning (with warnings treated as errors).',
@ -494,7 +494,7 @@ class Sphinx:
logger.debug('[app] adding config value: %r',
(name, default, rebuild) + ((types,) if types else ())) # type: ignore
if rebuild in (False, True):
rebuild = rebuild and 'env' or ''
rebuild = 'env' if rebuild else ''
self.config.add(name, default, rebuild, types)
def add_event(self, name):

View File

@ -432,11 +432,8 @@ class StandaloneHTMLBuilder(Builder):
else:
self.last_updated = None
logo = self.config.html_logo and \
path.basename(self.config.html_logo) or ''
favicon = self.config.html_favicon and \
path.basename(self.config.html_favicon) or ''
logo = path.basename(self.config.html_logo) if self.config.html_logo else ''
favicon = path.basename(self.config.html_favicon) if self.config.html_favicon else ''
if not isinstance(self.config.html_use_opensearch, str):
logger.warning(__('html_use_opensearch config value must now be a string'))
@ -538,7 +535,7 @@ class StandaloneHTMLBuilder(Builder):
# title rendered as HTML
title_node = self.env.longtitles.get(docname)
title = title_node and self.render_partial(title_node)['title'] or ''
title = self.render_partial(title_node)['title'] if title_node else ''
# Suffix for the document
source_suffix = path.splitext(self.env.doc2path(docname))[1]
@ -595,7 +592,7 @@ class StandaloneHTMLBuilder(Builder):
self.imgpath = relative_uri(self.get_target_uri(docname), self.imagedir)
self.post_process_images(doctree)
title_node = self.env.longtitles.get(docname)
title = title_node and self.render_partial(title_node)['title'] or ''
title = self.render_partial(title_node)['title'] if title_node else ''
self.index_page(docname, doctree, title)
def finish(self) -> None:

View File

@ -243,7 +243,7 @@ class LaTeXBuilder(Builder):
doctree = self.assemble_doctree(
docname, toctree_only,
appendices=((docclass != 'howto') and self.config.latex_appendices or []))
appendices=(self.config.latex_appendices if docclass != 'howto' else []))
doctree['tocdepth'] = tocdepth
self.post_process_images(doctree)
self.update_doc_context(title, author)
@ -360,13 +360,6 @@ class LaTeXBuilder(Builder):
copy_asset_file(path.join(staticdirname, 'Makefile_t'),
self.outdir, context=context)
# the logo is handled differently
if self.config.latex_logo:
if not path.isfile(path.join(self.confdir, self.config.latex_logo)):
raise SphinxError(__('logo file %r does not exist') % self.config.latex_logo)
else:
copy_asset_file(path.join(self.confdir, self.config.latex_logo), self.outdir)
@progress_message(__('copying additional files'))
def copy_latex_additional_files(self) -> None:
for filename in self.config.latex_additional_files:
@ -386,6 +379,11 @@ class LaTeXBuilder(Builder):
except Exception as err:
logger.warning(__('cannot copy image file %r: %s'),
path.join(self.srcdir, src), err)
if self.config.latex_logo:
if not path.isfile(path.join(self.confdir, self.config.latex_logo)):
raise SphinxError(__('logo file %r does not exist') % self.config.latex_logo)
else:
copy_asset_file(path.join(self.confdir, self.config.latex_logo), self.outdir)
def write_message_catalog(self) -> None:
formats = self.config.numfig_format

View File

@ -359,7 +359,7 @@ def generate(d: Dict, overwrite: bool = True, silent: bool = False, templatedir:
ensuredir(d['path'])
srcdir = d['sep'] and path.join(d['path'], 'source') or d['path']
srcdir = path.join(d['path'], 'source') if d['sep'] else d['path']
ensuredir(srcdir)
if d['sep']:
@ -405,15 +405,15 @@ def generate(d: Dict, overwrite: bool = True, silent: bool = False, templatedir:
batchfile_template = 'quickstart/make.bat_t'
if d['makefile'] is True:
d['rsrcdir'] = d['sep'] and 'source' or '.'
d['rbuilddir'] = d['sep'] and 'build' or d['dot'] + 'build'
d['rsrcdir'] = 'source' if d['sep'] else '.'
d['rbuilddir'] = 'build' if d['sep'] else d['dot'] + 'build'
# use binary mode, to avoid writing \r\n on Windows
write_file(path.join(d['path'], 'Makefile'),
template.render(makefile_template, d), '\n')
if d['batchfile'] is True:
d['rsrcdir'] = d['sep'] and 'source' or '.'
d['rbuilddir'] = d['sep'] and 'build' or d['dot'] + 'build'
d['rsrcdir'] = 'source' if d['sep'] else '.'
d['rbuilddir'] = 'build' if d['sep'] else d['dot'] + 'build'
write_file(path.join(d['path'], 'make.bat'),
template.render(batchfile_template, d), '\r\n')

View File

@ -300,7 +300,7 @@ class HList(SphinxDirective):
index = 0
newnode = addnodes.hlist()
for column in range(ncolumns):
endindex = index + (column < nmore and (npercol + 1) or npercol)
endindex = index + ((npercol + 1) if column < nmore else npercol)
bullet_list = nodes.bullet_list()
bullet_list += fulllist.children[index:endindex]
newnode += addnodes.hlistcol('', bullet_list)

65
sphinx/domains/index.py Normal file
View File

@ -0,0 +1,65 @@
"""
sphinx.domains.index
~~~~~~~~~~~~~~~~~~~~
The index domain.
:copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
from typing import Any, Dict, Iterable, List, Tuple
from docutils.nodes import Node
from sphinx import addnodes
from sphinx.application import Sphinx
from sphinx.domains import Domain
from sphinx.environment import BuildEnvironment
from sphinx.util import logging
from sphinx.util import split_index_msg
logger = logging.getLogger(__name__)
class IndexDomain(Domain):
"""Mathematics domain."""
name = 'index'
label = 'index'
@property
def entries(self) -> Dict[str, List[Tuple[str, str, str, str, str]]]:
return self.data.setdefault('entries', {})
def clear_doc(self, docname: str) -> None:
self.entries.pop(docname, None)
def merge_domaindata(self, docnames: Iterable[str], otherdata: Dict) -> None:
for docname in docnames:
self.entries[docname] = otherdata['entries'][docname]
def process_doc(self, env: BuildEnvironment, docname: str, document: Node) -> None:
"""Process a document after it is read by the environment."""
entries = self.entries.setdefault(env.docname, [])
for node in document.traverse(addnodes.index):
try:
for entry in node['entries']:
split_index_msg(entry[0], entry[1])
except ValueError as exc:
logger.warning(str(exc), location=node)
node.parent.remove(node)
else:
for entry in node['entries']:
entries.append(entry)
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_domain(IndexDomain)
return {
'version': 'builtin',
'env_version': 1,
'parallel_read_safe': True,
'parallel_write_safe': True,
}

View File

@ -105,7 +105,7 @@ class JSObject(ObjectDescription):
def add_target_and_index(self, name_obj: Tuple[str, str], sig: str,
signode: desc_signature) -> None:
mod_name = self.env.ref_context.get('js:module')
fullname = (mod_name and mod_name + '.' or '') + name_obj[0]
fullname = (mod_name + '.' if mod_name else '') + name_obj[0]
if fullname not in self.state.document.ids:
signode['names'].append(fullname)
signode['ids'].append(fullname.replace('$', '_S_'))
@ -385,7 +385,7 @@ class JavaScriptDomain(Domain):
) -> Element:
mod_name = node.get('js:module')
prefix = node.get('js:object')
searchorder = node.hasattr('refspecific') and 1 or 0
searchorder = 1 if node.hasattr('refspecific') else 0
name, obj = self.find_obj(env, mod_name, prefix, target, typ, searchorder)
if not obj:
return None

View File

@ -306,7 +306,7 @@ class PyObject(ObjectDescription):
def add_target_and_index(self, name_cls: Tuple[str, str], sig: str,
signode: desc_signature) -> None:
modname = self.options.get('module', self.env.ref_context.get('py:module'))
fullname = (modname and modname + '.' or '') + name_cls[0]
fullname = (modname + '.' if modname else '') + name_cls[0]
# note target
if fullname not in self.state.document.ids:
signode['names'].append(fullname)
@ -821,7 +821,7 @@ class PythonModuleIndex(Index):
num_toplevels += 1
subtype = 0
qualifier = deprecated and _('Deprecated') or ''
qualifier = _('Deprecated') if deprecated else ''
entries.append(IndexEntry(stripped + modname, subtype, docname,
'module-' + stripped + modname, platforms,
qualifier, synopsis))
@ -999,7 +999,7 @@ class PythonDomain(Domain):
) -> Element:
modname = node.get('py:module')
clsname = node.get('py:class')
searchmode = node.hasattr('refspecific') and 1 or 0
searchmode = 1 if node.hasattr('refspecific') else 0
matches = self.find_obj(env, modname, clsname, target,
type, searchmode)
if not matches:

View File

@ -14,7 +14,8 @@ import warnings
from collections import defaultdict
from copy import copy
from os import path
from typing import Any, Callable, Dict, Generator, Iterator, List, Set, Tuple, Union
from typing import Any, Callable, Dict, Generator, IO, Iterator, List, Set, Tuple, Union
from typing import cast
from docutils import nodes
from docutils.nodes import Node
@ -167,11 +168,6 @@ class BuildEnvironment:
self.domaindata = {} # type: Dict[str, Dict]
# domainname -> domain-specific dict
# Other inventories
self.indexentries = {} # type: Dict[str, List[Tuple[str, str, str, str, str]]]
# docname -> list of
# (type, str, target, aliasname)
# these map absolute path -> (docnames, unique filename)
self.images = FilenameUniqDict() # type: FilenameUniqDict
self.dlfiles = DownloadFiles() # type: DownloadFiles
@ -640,3 +636,131 @@ class BuildEnvironment:
for domain in self.domains.values():
domain.check_consistency()
self.events.emit('env-check-consistency', self)
<<<<<<< HEAD
=======
# --------- METHODS FOR COMPATIBILITY --------------------------------------
def update(self, config: Config, srcdir: str, doctreedir: str) -> List[str]:
warnings.warn('env.update() is deprecated. Please use builder.read() instead.',
RemovedInSphinx30Warning, stacklevel=2)
return self.app.builder.read()
def _read_serial(self, docnames: List[str], app: "Sphinx") -> None:
warnings.warn('env._read_serial() is deprecated. Please use builder.read() instead.',
RemovedInSphinx30Warning, stacklevel=2)
return self.app.builder._read_serial(docnames)
def _read_parallel(self, docnames: List[str], app: "Sphinx", nproc: int) -> None:
warnings.warn('env._read_parallel() is deprecated. Please use builder.read() instead.',
RemovedInSphinx30Warning, stacklevel=2)
return self.app.builder._read_parallel(docnames, nproc)
def read_doc(self, docname: str, app: "Sphinx" = None) -> None:
warnings.warn('env.read_doc() is deprecated. Please use builder.read_doc() instead.',
RemovedInSphinx30Warning, stacklevel=2)
self.app.builder.read_doc(docname)
def write_doctree(self, docname: str, doctree: nodes.document) -> None:
warnings.warn('env.write_doctree() is deprecated. '
'Please use builder.write_doctree() instead.',
RemovedInSphinx30Warning, stacklevel=2)
self.app.builder.write_doctree(docname, doctree)
@property
def _nitpick_ignore(self) -> List[str]:
warnings.warn('env._nitpick_ignore is deprecated. '
'Please use config.nitpick_ignore instead.',
RemovedInSphinx30Warning, stacklevel=2)
return self.config.nitpick_ignore
@staticmethod
def load(f: IO, app: "Sphinx" = None) -> "BuildEnvironment":
warnings.warn('BuildEnvironment.load() is deprecated. '
'Please use pickle.load() instead.',
RemovedInSphinx30Warning, stacklevel=2)
try:
env = pickle.load(f)
except Exception as exc:
# This can happen for example when the pickle is from a
# different version of Sphinx.
raise OSError(exc)
if app:
env.app = app
env.config.values = app.config.values
return env
@classmethod
def loads(cls, string: bytes, app: "Sphinx" = None) -> "BuildEnvironment":
warnings.warn('BuildEnvironment.loads() is deprecated. '
'Please use pickle.loads() instead.',
RemovedInSphinx30Warning, stacklevel=2)
io = BytesIO(string)
return cls.load(io, app)
@classmethod
def frompickle(cls, filename: str, app: "Sphinx") -> "BuildEnvironment":
warnings.warn('BuildEnvironment.frompickle() is deprecated. '
'Please use pickle.load() instead.',
RemovedInSphinx30Warning, stacklevel=2)
with open(filename, 'rb') as f:
return cls.load(f, app)
@staticmethod
def dump(env: "BuildEnvironment", f: IO) -> None:
warnings.warn('BuildEnvironment.dump() is deprecated. '
'Please use pickle.dump() instead.',
RemovedInSphinx30Warning, stacklevel=2)
pickle.dump(env, f, pickle.HIGHEST_PROTOCOL)
@classmethod
def dumps(cls, env: "BuildEnvironment") -> bytes:
warnings.warn('BuildEnvironment.dumps() is deprecated. '
'Please use pickle.dumps() instead.',
RemovedInSphinx30Warning, stacklevel=2)
io = BytesIO()
cls.dump(env, io)
return io.getvalue()
def topickle(self, filename: str) -> None:
warnings.warn('env.topickle() is deprecated. '
'Please use pickle.dump() instead.',
RemovedInSphinx30Warning, stacklevel=2)
with open(filename, 'wb') as f:
self.dump(self, f)
@property
def versionchanges(self) -> Dict[str, List[Tuple[str, str, int, str, str, str]]]:
warnings.warn('env.versionchanges() is deprecated. '
'Please use ChangeSetDomain instead.',
RemovedInSphinx30Warning, stacklevel=2)
return self.domaindata['changeset']['changes']
def note_versionchange(self, type: str, version: str,
node: addnodes.versionmodified, lineno: int) -> None:
warnings.warn('env.note_versionchange() is deprecated. '
'Please use ChangeSetDomain.note_changeset() instead.',
RemovedInSphinx30Warning, stacklevel=2)
node['type'] = type
node['version'] = version
node.line = lineno
self.get_domain('changeset').note_changeset(node) # type: ignore
@property
def indexentries(self) -> Dict[str, List[Tuple[str, str, str, str, str]]]:
warnings.warn('env.indexentries() is deprecated. Please use IndexDomain instead.',
RemovedInSphinx40Warning)
from sphinx.domains.index import IndexDomain
domain = cast(IndexDomain, self.get_domain('index'))
return domain.entries
from sphinx.errors import NoUri # NOQA
deprecated_alias('sphinx.environment',
{
'NoUri': NoUri,
},
RemovedInSphinx30Warning)
>>>>>>> 2.0

View File

@ -252,7 +252,7 @@ class TocTree:
# prune the tree to maxdepth, also set toc depth and current classes
_toctree_add_classes(newnode, 1)
self._toctree_prune(newnode, 1, prune and maxdepth or 0, collapse)
self._toctree_prune(newnode, 1, maxdepth if prune else 0, collapse)
if isinstance(newnode[-1], nodes.Element) and len(newnode[-1]) == 0: # No titles found
return None

View File

@ -8,22 +8,29 @@
:license: BSD, see LICENSE for details.
"""
import warnings
from typing import Any, Dict, Set
from docutils import nodes
from sphinx import addnodes
from sphinx.application import Sphinx
from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.environment import BuildEnvironment
from sphinx.environment.collectors import EnvironmentCollector
from sphinx.util import split_index_msg, logging
logger = logging.getLogger(__name__)
class IndexEntriesCollector(EnvironmentCollector):
name = 'indices'
def __init__(self) -> None:
warnings.warn('IndexEntriesCollector is deprecated.',
RemovedInSphinx40Warning)
def clear_doc(self, app: Sphinx, env: BuildEnvironment, docname: str) -> None:
env.indexentries.pop(docname, None)

View File

@ -301,7 +301,7 @@ class Documenter:
# support explicit module and class name separation via ::
if explicit_modname is not None:
modname = explicit_modname[:-2]
parents = path and path.rstrip('.').split('.') or []
parents = path.rstrip('.').split('.') if path else []
else:
modname = None
parents = []
@ -314,7 +314,7 @@ class Documenter:
self.args = args
self.retann = retann
self.fullname = (self.modname or '') + \
(self.objpath and '.' + '.'.join(self.objpath) or '')
('.' + '.'.join(self.objpath) if self.objpath else '')
return True
def import_object(self) -> bool:
@ -405,7 +405,7 @@ class Documenter:
args, retann = result
if args is not None:
return args + (retann and (' -> %s' % retann) or '')
return args + ((' -> %s' % retann) if retann else '')
else:
return ''
@ -1126,9 +1126,9 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
sourcename = self.get_sourcename()
self.add_line('', sourcename)
if hasattr(self.object, '__bases__') and len(self.object.__bases__):
bases = [b.__module__ in ('__builtin__', 'builtins') and
':class:`%s`' % b.__name__ or
':class:`%s.%s`' % (b.__module__, b.__name__)
bases = [':class:`%s`' % b.__name__
if b.__module__ in ('__builtin__', 'builtins')
else ':class:`%s.%s`' % (b.__module__, b.__name__)
for b in self.object.__bases__]
self.add_line(' ' + _('Bases: %s') % ', '.join(bases),
sourcename)

View File

@ -722,7 +722,7 @@ def process_generate_options(app: Sphinx) -> None:
if os.path.isfile(env.doc2path(x))]
else:
ext = list(app.config.source_suffix)
genfiles = [genfile + (not genfile.endswith(tuple(ext)) and ext[0] or '')
genfiles = [genfile + (ext[0] if not genfile.endswith(tuple(ext)) else '')
for genfile in genfiles]
for entry in genfiles[:]:

View File

@ -326,7 +326,7 @@ class DocTestBuilder(Builder):
def finish(self) -> None:
# write executive summary
def s(v: int) -> str:
return v != 1 and 's' or ''
return 's' if v != 1 else ''
repl = (self.total_tries, s(self.total_tries),
self.total_failures, s(self.total_failures),
self.setup_failures, s(self.setup_failures),
@ -523,8 +523,8 @@ Doctest summary
self.type = 'single' # as for ordinary doctests
else:
# testcode and output separate
output = code[1] and code[1].code or ''
options = code[1] and code[1].options or {}
output = code[1].code if code[1] else ''
options = code[1].options if code[1] else {}
# disable <BLANKLINE> processing as it is not needed
options[doctest.DONT_ACCEPT_BLANKLINE] = True
# find out if we're testing an exception

View File

@ -176,7 +176,7 @@ def fetch_inventory(app: Sphinx, uri: str, inv: Any) -> Any:
uri = path.dirname(newinv)
with f:
try:
join = localuri and path.join or posixpath.join
join = path.join if localuri else posixpath.join
invdata = InventoryFile.load(f, uri, join)
except ValueError as exc:
raise ValueError('unknown or unsupported inventory version: %r' % exc)
@ -401,7 +401,7 @@ def inspect_main(argv: List[str]) -> None:
print(key)
for entry, einfo in sorted(invdata[key].items()):
print('\t%-40s %s%s' % (entry,
einfo[3] != '-' and '%-40s: ' % einfo[3] or '',
'%-40s: ' % einfo[3] if einfo[3] != '-' else '',
einfo[2]))
except ValueError as exc:
print(exc.args[0] % exc.args[1:])

View File

@ -112,7 +112,7 @@ class GoogleDocstring:
if not self._config:
from sphinx.ext.napoleon import Config
self._config = self._app and self._app.config or Config() # type: ignore
self._config = self._app.config if self._app else Config() # type: ignore
if not what:
if inspect.isclass(obj):
@ -385,7 +385,7 @@ class GoogleDocstring:
def _format_field(self, _name: str, _type: str, _desc: List[str]) -> List[str]:
_desc = self._strip_empty(_desc)
has_desc = any(_desc)
separator = has_desc and ' -- ' or ''
separator = ' -- ' if has_desc else ''
if _name:
if _type:
if '`' in _type:

View File

@ -188,7 +188,7 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader):
self.loaders = [SphinxFileSystemLoader(x) for x in loaderchain]
use_i18n = builder.app.translator is not None
extensions = use_i18n and ['jinja2.ext.i18n'] or []
extensions = ['jinja2.ext.i18n'] if use_i18n else []
self.environment = SandboxedEnvironment(loader=self,
extensions=extensions)
self.environment.filters['tobool'] = _tobool

View File

@ -479,7 +479,7 @@ class Parser:
def parse_comments(self) -> None:
"""Parse the code and pick up comments."""
tree = ast.parse(self.code.encode())
tree = ast.parse(self.code)
picker = VariableCommentPicker(self.code.splitlines(True), self.encoding)
picker.visit(tree)
self.comments = picker.comments

View File

@ -296,7 +296,7 @@ class FilterSystemMessages(SphinxTransform):
default_priority = 999
def apply(self, **kwargs) -> None:
filterlevel = self.config.keep_warnings and 2 or 5
filterlevel = 2 if self.config.keep_warnings else 5
for node in self.document.traverse(nodes.system_message):
if node['level'] < filterlevel:
logger.debug('%s [filtered system message]', node.astext())

View File

@ -155,7 +155,7 @@ class ReferencesResolver(SphinxPostTransform):
if self.config.nitpicky:
warn = True
if self.config.nitpick_ignore:
dtype = domain and '%s:%s' % (domain.name, typ) or typ
dtype = '%s:%s' % (domain.name, typ) if domain else typ
if (dtype, target) in self.config.nitpick_ignore:
warn = False
# for "std" types also try without domain name

View File

@ -83,7 +83,7 @@ def dumps(obj: Any, key: bool = False) -> str:
if obj is None:
return 'null'
elif obj is True or obj is False:
return obj and 'true' or 'false'
return 'true' if obj else 'false'
elif isinstance(obj, (int, float)):
return str(obj)
elif isinstance(obj, dict):

View File

@ -89,7 +89,7 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator):
self.permalink_text = self.config.html_add_permalinks
# support backwards-compatible setting to a bool
if not isinstance(self.permalink_text, str):
self.permalink_text = self.permalink_text and '' or ''
self.permalink_text = '' if self.permalink_text else ''
self.permalink_text = self.encode(self.permalink_text)
self.secnumber_suffix = self.config.html_secnumber_suffix
self.param_separator = ''

View File

@ -61,7 +61,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
self.permalink_text = self.config.html_add_permalinks
# support backwards-compatible setting to a bool
if not isinstance(self.permalink_text, str):
self.permalink_text = self.permalink_text and '' or ''
self.permalink_text = '' if self.permalink_text else ''
self.permalink_text = self.encode(self.permalink_text)
self.secnumber_suffix = self.config.html_secnumber_suffix
self.param_separator = ''

View File

@ -631,7 +631,7 @@ class LaTeXTranslator(SphinxTranslator):
latex_engine=self.config.latex_engine)
self.context = [] # type: List[Any]
self.descstack = [] # type: List[str]
self.table = None # type: Table
self.tables = [] # type: List[Table]
self.next_table_colspec = None # type: str
self.bodystack = [] # type: List[List[str]]
self.footnote_restricted = None # type: nodes.Element
@ -665,7 +665,7 @@ class LaTeXTranslator(SphinxTranslator):
def hypertarget(self, id: str, withdoc: bool = True, anchor: bool = True) -> str:
if withdoc:
id = self.curfilestack[-1] + ':' + id
return (anchor and '\\phantomsection' or '') + \
return ('\\phantomsection' if anchor else '') + \
'\\label{%s}' % self.idescape(id)
def hypertarget_to(self, node: Element, anchor: bool = False) -> str:
@ -749,6 +749,14 @@ class LaTeXTranslator(SphinxTranslator):
return renderer.render(template_name, variables)
@property
def table(self) -> Table:
"""Get current table."""
if self.tables:
return self.tables[-1]
else:
return None
def visit_document(self, node: Element) -> None:
self.curfilestack.append(node.get('docname', ''))
if self.first_document == 1:
@ -1045,11 +1053,21 @@ class LaTeXTranslator(SphinxTranslator):
raise nodes.SkipNode
def visit_table(self, node: Element) -> None:
if self.table:
if len(self.tables) == 1:
if self.table.get_table_type() == 'longtable':
raise UnsupportedError(
'%s:%s: longtable does not support nesting a table.' %
(self.curfilestack[-1], node.line or ''))
else:
# change type of parent table to tabular
# see https://groups.google.com/d/msg/sphinx-users/7m3NeOBixeo/9LKP2B4WBQAJ
self.table.has_problematic = True
elif len(self.tables) > 2:
raise UnsupportedError(
'%s:%s: nested tables are not yet implemented.' %
'%s:%s: deeply nested tables are not implemented.' %
(self.curfilestack[-1], node.line or ''))
self.table = Table(node)
self.tables.append(Table(node))
if self.next_table_colspec:
self.table.colspec = '{%s}\n' % self.next_table_colspec
if 'colwidths-given' in node.get('classes', []):
@ -1066,7 +1084,7 @@ class LaTeXTranslator(SphinxTranslator):
self.body.append(table)
self.body.append("\n")
self.table = None
self.tables.pop()
def visit_colspec(self, node: Element) -> None:
self.table.colcount += 1
@ -1491,7 +1509,7 @@ class LaTeXTranslator(SphinxTranslator):
elif isinstance(node[0], nodes.image) and 'width' in node[0]:
length = self.latex_image_length(node[0]['width'])
self.body.append('\\begin{wrapfigure}{%s}{%s}\n\\centering' %
(node['align'] == 'right' and 'r' or 'l', length or '0pt'))
('r' if node['align'] == 'right' else 'l', length or '0pt'))
self.context.append('\\end{wrapfigure}\n')
elif self.in_minipage:
self.body.append('\n\\begin{center}')

View File

@ -240,7 +240,7 @@ class TexinfoTranslator(SphinxTranslator):
title = self.settings.title # type: str
if not title:
title_node = self.document.next_node(nodes.title)
title = (title_node and title_node.astext()) or '<untitled>'
title = title_node.astext() if title_node else '<untitled>'
elements['title'] = self.escape_id(title) or '<untitled>'
# filename
if not elements['filename']:
@ -290,7 +290,7 @@ class TexinfoTranslator(SphinxTranslator):
# each section is also a node
for section in self.document.traverse(nodes.section):
title = cast(nodes.TextElement, section.next_node(nodes.Titular))
name = (title and title.astext()) or '<untitled>'
name = title.astext() if title else '<untitled>'
section['node_name'] = add_node_name(name)
def collect_node_menus(self) -> None:
@ -304,7 +304,7 @@ class TexinfoTranslator(SphinxTranslator):
node_menus[node['node_name']] = entries
# try to find a suitable "Top" node
title = self.document.next_node(nodes.title)
top = (title and title.parent) or self.document
top = title.parent if title else self.document
if not isinstance(top, (nodes.document, nodes.section)):
top = self.document
if top is not self.document:

View File

View File

@ -0,0 +1,16 @@
nested-tables
=============
.. list-table::
:header-rows: 1
* - heading
- heading
* - content
- .. list-table::
:header-rows: 1
* - heading
- heading
* - content
- content

View File

@ -28,9 +28,9 @@ ENV_WARNINGS = """\
WARNING: Explicit markup ends without a blank line; unexpected unindent.
%(root)s/index.rst:\\d+: WARNING: Encoding 'utf-8-sig' used for reading included \
file '%(root)s/wrongenc.inc' seems to be wrong, try giving an :encoding: option
%(root)s/index.rst:\\d+: WARNING: invalid single index entry ''
%(root)s/index.rst:\\d+: WARNING: image file not readable: foo.png
%(root)s/index.rst:\\d+: WARNING: download file not readable: %(root)s/nonexisting.png
%(root)s/index.rst:\\d+: WARNING: invalid single index entry ''
%(root)s/undecodable.rst:\\d+: WARNING: undecodable source characters, replacing \
with "\\?": b?'here: >>>(\\\\|/)xbb<<<((\\\\|/)r)?'
"""

View File

@ -1470,3 +1470,9 @@ def test_latex_elements_extrapackages(app, status, warning):
app.builder.build_all()
result = (app.outdir / 'test.tex').text()
assert r'\usepackage{foo}' in result
@pytest.mark.sphinx('latex', testroot='nested-tables')
def test_latex_nested_tables(app, status, warning):
app.builder.build_all()
assert '' == warning.getvalue()