Merge branch '3.x'

This commit is contained in:
Takeshi KOMIYA
2021-02-13 01:01:30 +09:00
8 changed files with 350 additions and 250 deletions

View File

@@ -104,7 +104,10 @@ Deprecated
* ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.broken``
* ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.good``
* ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.redirected``
* ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.rqueue``
* ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.to_ignore``
* ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.workers``
* ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.wqueue``
* ``sphinx.builders.linkcheck.node_line_or_0()``
* ``sphinx.ext.autodoc.AttributeDocumenter.isinstanceattribute()``
* ``sphinx.ext.autodoc.directive.DocumenterBridge.reporter``
@@ -134,6 +137,7 @@ Features added
* #6550: html: Allow to use HTML permalink texts via
:confval:`html_permalinks_icon`
* #1638: html: Add permalink icons to glossary terms
* #8868: html search: performance issue with massive lists
* #8852: i18n: Allow to translate heading syntax in MyST-Parser
* #8649: imgconverter: Skip availability check if builder supports the image
type
@@ -221,6 +225,10 @@ Bugs fixed
* #8849: LaTex: code-block printed out of margin (see the opt-in LaTeX syntax
boolean :ref:`verbatimforcewraps <latexsphinxsetupforcewraps>` for use via
the :ref:`'sphinxsetup' <latexsphinxsetup>` key of ``latex_elements``)
* #8183: LaTeX: Remove substitution_reference nodes from doctree only on LaTeX
builds
* #8865: LaTeX: Restructure the index nodes inside title nodes only on LaTeX
builds
Testing
--------

View File

@@ -24,7 +24,7 @@ class RecipeDirective(ObjectDescription):
def add_target_and_index(self, name_cls, sig, signode):
signode['ids'].append('recipe' + '-' + sig)
if 'noindex' not in self.options:
if 'contains' not in self.options:
ingredients = [
x.strip() for x in self.options.get('contains').split(',')]

View File

@@ -97,11 +97,26 @@ The following is a list of deprecated interfaces.
- 5.0
- N/A
* - ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.rqueue``
- 3.5
- 5.0
- N/A
* - ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.to_ignore``
- 3.5
- 5.0
- N/A
* - ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.workers``
- 3.5
- 5.0
- N/A
* - ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.wqueue``
- 3.5
- 5.0
- N/A
* - ``sphinx.builders.linkcheck.node_line_or_0()``
- 3.5
- 5.0

View File

@@ -44,7 +44,7 @@ class SubstitutionDefinitionsRemover(SphinxPostTransform):
default_priority = Substitutions.default_priority + 1
builders = ('latex',)
def apply(self, **kwargs: Any) -> None:
def run(self, **kwargs: Any) -> None:
for node in self.document.traverse(nodes.substitution_definition):
node.parent.remove(node)
@@ -192,7 +192,7 @@ class LaTeXFootnoteTransform(SphinxPostTransform):
headings having footnotes
<footnote_reference>
1
<footnote ids="1">
<footnote ids="id1">
<label>
1
<paragraph>
@@ -203,11 +203,9 @@ class LaTeXFootnoteTransform(SphinxPostTransform):
<section>
<title>
headings having footnotes
<footnotemark>
<footnotemark refid="id1">
1
<footnotetext>
footnote body
<footnotetext>
<footnotetext ids="id1">
<label>
1
<paragraph>
@@ -222,7 +220,7 @@ class LaTeXFootnoteTransform(SphinxPostTransform):
1
blah blah blah ...
<footnote ids="1">
<footnote ids="id1">
<label>
1
<paragraph>
@@ -231,7 +229,7 @@ class LaTeXFootnoteTransform(SphinxPostTransform):
After::
blah blah blah
<footnote ids="1">
<footnote ids="id1">
<label>
1
<paragraph>
@@ -251,7 +249,7 @@ class LaTeXFootnoteTransform(SphinxPostTransform):
1
blah blah blah ...
<footnote ids="1">
<footnote ids="id1">
<label>
1
<paragraph>
@@ -260,13 +258,13 @@ class LaTeXFootnoteTransform(SphinxPostTransform):
After::
blah blah blah
<footnote ids="1">
<footnote ids="id1">
<label>
1
<paragraph>
footnote body
blah blah blah
<footnotemark>
<footnotemark refid="id1">
1
blah blah blah ...
@@ -274,7 +272,7 @@ class LaTeXFootnoteTransform(SphinxPostTransform):
Before::
<footnote ids="1">
<footnote ids="id1">
<label>
1
<paragraph>
@@ -291,26 +289,26 @@ class LaTeXFootnoteTransform(SphinxPostTransform):
<table>
<title>
title having footnote_reference
<footnote_reference refid="1">
<footnote_reference refid="id1">
1
<tgroup>
<thead>
<row>
<entry>
header having footnote_reference
<footnote_reference refid="2">
<footnote_reference refid="id2">
2
<tbody>
<row>
...
<footnote ids="1">
<footnote ids="id1">
<label>
1
<paragraph>
footnote body
<footnote ids="2">
<footnote ids="id2">
<label>
2
<paragraph>
@@ -321,23 +319,23 @@ class LaTeXFootnoteTransform(SphinxPostTransform):
<table>
<title>
title having footnote_reference
<footnotemark>
<footnotemark refid="id1">
1
<tgroup>
<thead>
<row>
<entry>
header having footnote_reference
<footnotemark>
<footnotemark refid="id2">
2
<tbody>
<footnotetext>
<footnotetext ids="id1">
<label>
1
<paragraph>
footnote body
<footnotetext>
<footnotetext ids="id2">
<label>
2
<paragraph>
@@ -382,7 +380,7 @@ class LaTeXFootnoteVisitor(nodes.NodeVisitor):
self.restricted = None
pos = node.parent.index(node)
for i, footnote, in enumerate(self.pendings):
fntext = footnotetext('', *footnote.children)
fntext = footnotetext('', *footnote.children, ids=footnote['ids'])
node.parent.insert(pos + i + 1, fntext)
self.pendings = []
@@ -427,7 +425,7 @@ class LaTeXFootnoteVisitor(nodes.NodeVisitor):
def depart_table(self, node: nodes.table) -> None:
tbody = list(node.traverse(nodes.tbody))[0]
for footnote in reversed(self.table_footnotes):
fntext = footnotetext('', *footnote.children)
fntext = footnotetext('', *footnote.children, ids=footnote['ids'])
tbody.insert(0, fntext)
self.table_footnotes = []
@@ -442,13 +440,13 @@ class LaTeXFootnoteVisitor(nodes.NodeVisitor):
number = node.astext().strip()
docname = node['docname']
if self.restricted:
mark = footnotemark('', number)
mark = footnotemark('', number, refid=node['refid'])
node.replace_self(mark)
if (docname, number) not in self.appeared:
footnote = self.get_footnote_by_reference(node)
self.pendings.append(footnote)
elif (docname, number) in self.appeared:
mark = footnotemark('', number)
mark = footnotemark('', number, refid=node['refid'])
node.replace_self(mark)
else:
footnote = self.get_footnote_by_reference(node)
@@ -574,7 +572,7 @@ class DocumentTargetTransform(SphinxPostTransform):
section['ids'].append(':doc') # special label for :doc:
class IndexInSectionTitleTransform(SphinxTransform):
class IndexInSectionTitleTransform(SphinxPostTransform):
"""Move index nodes in section title to outside of the title.
LaTeX index macro is not compatible with some handling of section titles
@@ -601,8 +599,9 @@ class IndexInSectionTitleTransform(SphinxTransform):
...
"""
default_priority = 400
builders = ('latex',)
def apply(self, **kwargs: Any) -> None:
def run(self, **kwargs: Any) -> None:
for node in self.document.traverse(nodes.title):
if isinstance(node.parent, nodes.section):
for i, index in enumerate(node.traverse(addnodes.index)):

View File

@@ -19,7 +19,8 @@ from email.utils import parsedate_to_datetime
from html.parser import HTMLParser
from os import path
from threading import Thread
from typing import Any, Dict, List, NamedTuple, Optional, Pattern, Set, Tuple, cast
from typing import (Any, Dict, Generator, List, NamedTuple, Optional, Pattern, Set, Tuple,
Union, cast)
from urllib.parse import unquote, urlparse
from docutils import nodes
@@ -29,7 +30,9 @@ from requests.exceptions import HTTPError, TooManyRedirects
from sphinx.application import Sphinx
from sphinx.builders.dummy import DummyBuilder
from sphinx.config import Config
from sphinx.deprecation import RemovedInSphinx50Warning
from sphinx.environment import BuildEnvironment
from sphinx.locale import __
from sphinx.transforms.post_transforms import SphinxPostTransform
from sphinx.util import encode_uri, logging, requests
@@ -40,10 +43,11 @@ logger = logging.getLogger(__name__)
uri_re = re.compile('([a-z]+:)?//') # matches to foo:// and // (a protocol relative URL)
Hyperlink = NamedTuple('Hyperlink', (('next_check', float),
('uri', Optional[str]),
('docname', Optional[str]),
Hyperlink = NamedTuple('Hyperlink', (('uri', str),
('docname', str),
('lineno', Optional[int])))
CheckRequest = NamedTuple('CheckRequest', (('next_check', float),
('hyperlink', Optional[Hyperlink])))
CheckResult = NamedTuple('CheckResult', (('uri', str),
('docname', str),
('lineno', int),
@@ -52,6 +56,9 @@ CheckResult = NamedTuple('CheckResult', (('uri', str),
('code', int)))
RateLimit = NamedTuple('RateLimit', (('delay', float), ('next_check', float)))
# Tuple is old styled CheckRequest
CheckRequestType = Union[CheckRequest, Tuple[float, str, str, int]]
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml;q=0.9,*/*;q=0.8',
}
@@ -121,17 +128,8 @@ class CheckExternalLinksBuilder(DummyBuilder):
socket.setdefaulttimeout(5.0)
# create queues and worker threads
self.rate_limits = {} # type: Dict[str, RateLimit]
self.wqueue = queue.PriorityQueue() # type: queue.PriorityQueue
self.rqueue = queue.Queue() # type: queue.Queue
self.workers = [] # type: List[Thread]
for i in range(self.config.linkcheck_workers):
thread = HyperlinkAvailabilityCheckWorker(self)
thread.start()
self.workers.append(thread)
def is_ignored_uri(self, uri: str) -> bool:
return any(pat.match(uri) for pat in self.to_ignore)
self._wqueue = queue.PriorityQueue() # type: queue.PriorityQueue[CheckRequestType]
self._rqueue = queue.Queue() # type: queue.Queue
@property
def anchors_ignore(self) -> List[Pattern]:
@@ -202,7 +200,33 @@ class CheckExternalLinksBuilder(DummyBuilder):
RemovedInSphinx50Warning,
stacklevel=2,
)
return HyperlinkAvailabilityCheckWorker(self).limit_rate(response)
worker = HyperlinkAvailabilityCheckWorker(self.env, self.config,
None, None, {})
return worker.limit_rate(response)
def rqueue(self, response: Response) -> queue.Queue:
warnings.warn(
"%s.%s is deprecated." % (self.__class__.__name__, "rqueue"),
RemovedInSphinx50Warning,
stacklevel=2,
)
return self._rqueue
def workers(self, response: Response) -> List[Thread]:
warnings.warn(
"%s.%s is deprecated." % (self.__class__.__name__, "workers"),
RemovedInSphinx50Warning,
stacklevel=2,
)
return []
def wqueue(self, response: Response) -> queue.Queue:
warnings.warn(
"%s.%s is deprecated." % (self.__class__.__name__, "wqueue"),
RemovedInSphinx50Warning,
stacklevel=2,
)
return self._wqueue
def process_result(self, result: Tuple[str, str, int, str, str, int]) -> None:
uri, docname, lineno, status, info, code = result
@@ -268,53 +292,105 @@ class CheckExternalLinksBuilder(DummyBuilder):
self.json_outfile.write('\n')
def finish(self) -> None:
checker = HyperlinkAvailabilityChecker(self.env, self.config, self)
logger.info('')
with open(path.join(self.outdir, 'output.txt'), 'w') as self.txt_outfile,\
open(path.join(self.outdir, 'output.json'), 'w') as self.json_outfile:
total_links = 0
for hyperlink in self.hyperlinks.values():
if self.is_ignored_uri(hyperlink.uri):
self.process_result(
CheckResult(hyperlink.uri, hyperlink.docname, hyperlink.lineno,
'ignored', '', 0))
else:
self.wqueue.put(hyperlink, False)
total_links += 1
done = 0
while done < total_links:
self.process_result(self.rqueue.get())
done += 1
for result in checker.check(self.hyperlinks):
self.process_result(result)
if self._broken:
self.app.statuscode = 1
class HyperlinkAvailabilityChecker:
def __init__(self, env: BuildEnvironment, config: Config,
builder: CheckExternalLinksBuilder = None) -> None:
# Warning: builder argument will be removed in the sphinx-5.0.
# Don't use it from extensions.
# tag: RemovedInSphinx50Warning
self.builder = builder
self.config = config
self.env = env
self.rate_limits = {} # type: Dict[str, RateLimit]
self.workers = [] # type: List[Thread]
self.to_ignore = [re.compile(x) for x in self.config.linkcheck_ignore]
if builder:
self.rqueue = builder._rqueue
self.wqueue = builder._wqueue
else:
self.rqueue = queue.Queue()
self.wqueue = queue.PriorityQueue()
def invoke_threads(self) -> None:
for i in range(self.config.linkcheck_workers):
thread = HyperlinkAvailabilityCheckWorker(self.env, self.config,
self.rqueue, self.wqueue,
self.rate_limits, self.builder)
thread.start()
self.workers.append(thread)
def shutdown_threads(self) -> None:
self.wqueue.join()
# Shutdown threads.
for worker in self.workers:
self.wqueue.put((CHECK_IMMEDIATELY, None, None, None), False)
self.wqueue.put(CheckRequest(CHECK_IMMEDIATELY, None), False)
def check(self, hyperlinks: Dict[str, Hyperlink]) -> Generator[CheckResult, None, None]:
self.invoke_threads()
total_links = 0
for hyperlink in hyperlinks.values():
if self.is_ignored_uri(hyperlink.uri):
yield CheckResult(hyperlink.uri, hyperlink.docname, hyperlink.lineno,
'ignored', '', 0)
else:
self.wqueue.put(CheckRequest(CHECK_IMMEDIATELY, hyperlink), False)
total_links += 1
done = 0
while done < total_links:
yield self.rqueue.get()
done += 1
self.shutdown_threads()
def is_ignored_uri(self, uri: str) -> bool:
return any(pat.match(uri) for pat in self.to_ignore)
class HyperlinkAvailabilityCheckWorker(Thread):
"""A worker class for checking the availability of hyperlinks."""
def __init__(self, builder: CheckExternalLinksBuilder) -> None:
self.config = builder.config
self.env = builder.env
self.rate_limits = builder.rate_limits
self.rqueue = builder.rqueue
self.wqueue = builder.wqueue
def __init__(self, env: BuildEnvironment, config: Config, rqueue: queue.Queue,
wqueue: queue.Queue, rate_limits: Dict[str, RateLimit],
builder: CheckExternalLinksBuilder = None) -> None:
# Warning: builder argument will be removed in the sphinx-5.0.
# Don't use it from extensions.
# tag: RemovedInSphinx50Warning
self.config = config
self.env = env
self.rate_limits = rate_limits
self.rqueue = rqueue
self.wqueue = wqueue
self.anchors_ignore = [re.compile(x)
for x in self.config.linkcheck_anchors_ignore]
self.auth = [(re.compile(pattern), auth_info) for pattern, auth_info
in self.config.linkcheck_auth]
self.to_ignore = [re.compile(x) for x in self.config.linkcheck_ignore]
self._good = builder._good
self._broken = builder._broken
self._redirected = builder._redirected
if builder:
# if given, fill the result of checks as cache
self._good = builder._good
self._broken = builder._broken
self._redirected = builder._redirected
else:
# only for compatibility. Will be removed in Sphinx-5.0
self._good = set()
self._broken = {}
self._redirected = {}
super().__init__(daemon=True)
@@ -400,7 +476,7 @@ class HyperlinkAvailabilityCheckWorker(Thread):
elif err.response.status_code == 429:
next_check = self.limit_rate(err.response)
if next_check is not None:
self.wqueue.put((next_check, uri, docname, lineno), False)
self.wqueue.put(CheckRequest(next_check, hyperlink), False)
return 'rate-limited', '', 0
return 'broken', str(err), 0
elif err.response.status_code == 503:
@@ -467,7 +543,17 @@ class HyperlinkAvailabilityCheckWorker(Thread):
return (status, info, code)
while True:
next_check, uri, docname, lineno = self.wqueue.get()
check_request = self.wqueue.get()
try:
next_check, hyperlink = check_request
if hyperlink is None:
break
uri, docname, lineno = hyperlink
except ValueError:
# old styled check_request (will be deprecated in Sphinx-5.0)
next_check, uri, docname, lineno = check_request
if uri is None:
break
netloc = urlparse(uri).netloc
@@ -483,7 +569,7 @@ class HyperlinkAvailabilityCheckWorker(Thread):
# Sleep before putting message back in the queue to avoid
# waking up other threads.
time.sleep(QUEUE_POLL_SECS)
self.wqueue.put((next_check, uri, docname, lineno), False)
self.wqueue.put(CheckRequest(next_check, hyperlink), False)
self.wqueue.task_done()
continue
status, info, code = check(docname)
@@ -546,7 +632,7 @@ class HyperlinkCollector(SphinxPostTransform):
continue
uri = refnode['refuri']
lineno = get_node_line(refnode)
uri_info = Hyperlink(CHECK_IMMEDIATELY, uri, self.env.docname, lineno)
uri_info = Hyperlink(uri, self.env.docname, lineno)
if uri not in hyperlinks:
hyperlinks[uri] = uri_info
@@ -555,7 +641,7 @@ class HyperlinkCollector(SphinxPostTransform):
uri = imgnode['candidates'].get('?')
if uri and '://' in uri:
lineno = get_node_line(imgnode)
uri_info = Hyperlink(CHECK_IMMEDIATELY, uri, self.env.docname, lineno)
uri_info = Hyperlink(uri, self.env.docname, lineno)
if uri not in hyperlinks:
hyperlinks[uri] = uri_info

View File

@@ -248,7 +248,7 @@ var Search = {
// results left, load the summary and display it
if (results.length) {
var item = results.pop();
var listItem = $('<li style="display:none"></li>');
var listItem = $('<li></li>');
var requestUrl = "";
var linkUrl = "";
if (DOCUMENTATION_OPTIONS.BUILDER === 'dirhtml') {
@@ -273,9 +273,9 @@ var Search = {
if (item[3]) {
listItem.append($('<span> (' + item[3] + ')</span>'));
Search.output.append(listItem);
listItem.slideDown(5, function() {
setTimeout(function() {
displayNextItem();
});
}, 5);
} else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) {
$.ajax({url: requestUrl,
dataType: "text",
@@ -285,16 +285,16 @@ var Search = {
listItem.append(Search.makeSearchSummary(data, searchterms, hlterms));
}
Search.output.append(listItem);
listItem.slideDown(5, function() {
setTimeout(function() {
displayNextItem();
});
}, 5);
}});
} else {
// no source available, just display title
Search.output.append(listItem);
listItem.slideDown(5, function() {
setTimeout(function() {
displayNextItem();
});
}, 5);
}
}
// search finished, update title and status message

View File

@@ -57,6 +57,8 @@ ENUMERATE_LIST_STYLE = defaultdict(lambda: r'\arabic',
'upperroman': r'\Roman',
})
CR = '\n'
BLANKLINE = '\n\n'
EXTRA_RE = re.compile(r'^(.*\S)\s+\(([^()]*)\)\s*$')
@@ -163,16 +165,16 @@ class Table:
elif self.colwidths and 'colwidths-given' in self.classes:
total = sum(self.colwidths)
colspecs = ['\\X{%d}{%d}' % (width, total) for width in self.colwidths]
return '{|%s|}\n' % '|'.join(colspecs)
return '{|%s|}' % '|'.join(colspecs) + CR
elif self.has_problematic:
return '{|*{%d}{\\X{1}{%d}|}}\n' % (self.colcount, self.colcount)
return '{|*{%d}{\\X{1}{%d}|}}' % (self.colcount, self.colcount) + CR
elif self.get_table_type() == 'tabulary':
# sphinx.sty sets T to be J by default.
return '{|' + ('T|' * self.colcount) + '}\n'
return '{|' + ('T|' * self.colcount) + '}' + CR
elif self.has_oldproblematic:
return '{|*{%d}{\\X{1}{%d}|}}\n' % (self.colcount, self.colcount)
return '{|*{%d}{\\X{1}{%d}|}}' % (self.colcount, self.colcount) + CR
else:
return '{|' + ('l|' * self.colcount) + '}\n'
return '{|' + ('l|' * self.colcount) + '}' + CR
def add_cell(self, height: int, width: int) -> None:
"""Adds a new cell to a table.
@@ -371,8 +373,8 @@ class LaTeXTranslator(SphinxTranslator):
if (self.config.language not in {None, 'en', 'ja'} and
'fncychap' not in self.config.latex_elements):
# use Sonny style if any language specified (except English)
self.elements['fncychap'] = ('\\usepackage[Sonny]{fncychap}\n'
'\\ChNameVar{\\Large\\normalfont\\sffamily}\n'
self.elements['fncychap'] = ('\\usepackage[Sonny]{fncychap}' + CR +
'\\ChNameVar{\\Large\\normalfont\\sffamily}' + CR +
'\\ChTitleVar{\\Large\\normalfont\\sffamily}')
self.babel = self.builder.babel
@@ -496,16 +498,16 @@ class LaTeXTranslator(SphinxTranslator):
prefix = ''
suffix = ''
return ('%s\\renewcommand{%s}{%s}%s\n' % (prefix, command, definition, suffix))
return '%s\\renewcommand{%s}{%s}%s' % (prefix, command, definition, suffix) + CR
def generate_indices(self) -> str:
def generate(content: List[Tuple[str, List[IndexEntry]]], collapsed: bool) -> None:
ret.append('\\begin{sphinxtheindex}\n')
ret.append('\\let\\bigletter\\sphinxstyleindexlettergroup\n')
ret.append('\\begin{sphinxtheindex}' + CR)
ret.append('\\let\\bigletter\\sphinxstyleindexlettergroup' + CR)
for i, (letter, entries) in enumerate(content):
if i > 0:
ret.append('\\indexspace\n')
ret.append('\\bigletter{%s}\n' % self.escape(letter))
ret.append('\\indexspace' + CR)
ret.append('\\bigletter{%s}' % self.escape(letter) + CR)
for entry in entries:
if not entry[3]:
continue
@@ -514,9 +516,9 @@ class LaTeXTranslator(SphinxTranslator):
if entry[4]:
# add "extra" info
ret.append('\\sphinxstyleindexextra{%s}' % self.encode(entry[4]))
ret.append('\\sphinxstyleindexpageref{%s:%s}\n' %
(entry[2], self.idescape(entry[3])))
ret.append('\\end{sphinxtheindex}\n')
ret.append('\\sphinxstyleindexpageref{%s:%s}' %
(entry[2], self.idescape(entry[3])) + CR)
ret.append('\\end{sphinxtheindex}' + CR)
ret = []
# latex_domain_indices can be False/True or a list of index names
@@ -532,8 +534,7 @@ class LaTeXTranslator(SphinxTranslator):
self.builder.docnames)
if not content:
continue
ret.append('\\renewcommand{\\indexname}{%s}\n' %
indexcls.localname)
ret.append('\\renewcommand{\\indexname}{%s}' % indexcls.localname + CR)
generate(content, collapsed)
return ''.join(ret)
@@ -563,7 +564,7 @@ class LaTeXTranslator(SphinxTranslator):
self.first_document = 0
elif self.first_document == 0:
# ... and all others are the appendices
self.body.append('\n\\appendix\n')
self.body.append(CR + '\\appendix' + CR)
self.first_document = -1
if 'docname' in node:
self.body.append(self.hypertarget(':doc'))
@@ -582,7 +583,7 @@ class LaTeXTranslator(SphinxTranslator):
def visit_section(self, node: Element) -> None:
if not self.this_is_the_title:
self.sectionlevel += 1
self.body.append('\n\n')
self.body.append(BLANKLINE)
def depart_section(self, node: Element) -> None:
self.sectionlevel = max(self.sectionlevel - 1,
@@ -596,11 +597,11 @@ class LaTeXTranslator(SphinxTranslator):
def visit_topic(self, node: Element) -> None:
self.in_minipage = 1
self.body.append('\n\\begin{sphinxShadowBox}\n')
self.body.append(CR + '\\begin{sphinxShadowBox}' + CR)
def depart_topic(self, node: Element) -> None:
self.in_minipage = 0
self.body.append('\\end{sphinxShadowBox}\n')
self.body.append('\\end{sphinxShadowBox}' + CR)
visit_sidebar = visit_topic
depart_sidebar = depart_topic
@@ -611,12 +612,12 @@ class LaTeXTranslator(SphinxTranslator):
pass
def visit_productionlist(self, node: Element) -> None:
self.body.append('\n\n')
self.body.append('\\begin{productionlist}\n')
self.body.append(BLANKLINE)
self.body.append('\\begin{productionlist}' + CR)
self.in_production_list = 1
def depart_productionlist(self, node: Element) -> None:
self.body.append('\\end{productionlist}\n\n')
self.body.append('\\end{productionlist}' + BLANKLINE)
self.in_production_list = 0
def visit_production(self, node: Element) -> None:
@@ -628,7 +629,7 @@ class LaTeXTranslator(SphinxTranslator):
self.body.append('\\productioncont{')
def depart_production(self, node: Element) -> None:
self.body.append('}\n')
self.body.append('}' + CR)
def visit_transition(self, node: Element) -> None:
self.body.append(self.elements['transition'])
@@ -663,16 +664,16 @@ class LaTeXTranslator(SphinxTranslator):
except IndexError:
# just use "subparagraph", it's not numbered anyway
self.body.append(r'\%s%s{' % (self.sectionnames[-1], short))
self.context.append('}\n' + self.hypertarget_to(node.parent))
self.context.append('}' + CR + self.hypertarget_to(node.parent))
elif isinstance(parent, nodes.topic):
self.body.append(r'\sphinxstyletopictitle{')
self.context.append('}\n')
self.context.append('}' + CR)
elif isinstance(parent, nodes.sidebar):
self.body.append(r'\sphinxstylesidebartitle{')
self.context.append('}\n')
self.context.append('}' + CR)
elif isinstance(parent, nodes.Admonition):
self.body.append('{')
self.context.append('}\n')
self.context.append('}' + CR)
elif isinstance(parent, nodes.table):
# Redirect body output until title is finished.
self.pushbody([])
@@ -681,7 +682,7 @@ class LaTeXTranslator(SphinxTranslator):
'admonition or sidebar'),
location=node)
self.body.append('\\sphinxstyleothertitle{')
self.context.append('}\n')
self.context.append('}' + CR)
self.in_title = 1
def depart_title(self, node: Element) -> None:
@@ -694,7 +695,7 @@ class LaTeXTranslator(SphinxTranslator):
def visit_subtitle(self, node: Element) -> None:
if isinstance(node.parent, nodes.sidebar):
self.body.append('\\sphinxstylesidebarsubtitle{')
self.context.append('}\n')
self.context.append('}' + CR)
else:
self.context.append('')
@@ -703,19 +704,19 @@ class LaTeXTranslator(SphinxTranslator):
def visit_desc(self, node: Element) -> None:
if self.config.latex_show_urls == 'footnote':
self.body.append('\n\n')
self.body.append('\\begin{savenotes}\\begin{fulllineitems}\n')
self.body.append(BLANKLINE)
self.body.append('\\begin{savenotes}\\begin{fulllineitems}' + CR)
else:
self.body.append('\n\n')
self.body.append('\\begin{fulllineitems}\n')
self.body.append(BLANKLINE)
self.body.append('\\begin{fulllineitems}' + CR)
if self.table:
self.table.has_problematic = True
def depart_desc(self, node: Element) -> None:
if self.config.latex_show_urls == 'footnote':
self.body.append('\n\\end{fulllineitems}\\end{savenotes}\n\n')
self.body.append(CR + '\\end{fulllineitems}\\end{savenotes}' + BLANKLINE)
else:
self.body.append('\n\\end{fulllineitems}\n\n')
self.body.append(CR + '\\end{fulllineitems}' + BLANKLINE)
def _visit_signature_line(self, node: Element) -> None:
for child in node:
@@ -737,14 +738,14 @@ class LaTeXTranslator(SphinxTranslator):
if not node.get('is_multiline'):
self._visit_signature_line(node)
else:
self.body.append('%\n')
self.body.append('\\pysigstartmultiline\n')
self.body.append('%' + CR)
self.body.append('\\pysigstartmultiline' + CR)
def depart_desc_signature(self, node: Element) -> None:
if not node.get('is_multiline'):
self._depart_signature_line(node)
else:
self.body.append('%\n')
self.body.append('%' + CR)
self.body.append('\\pysigstopmultiline')
def visit_desc_signature_line(self, node: Element) -> None:
@@ -823,18 +824,18 @@ class LaTeXTranslator(SphinxTranslator):
pass
def visit_seealso(self, node: Element) -> None:
self.body.append('\n\n')
self.body.append('\\sphinxstrong{%s:}\n' % admonitionlabels['seealso'])
self.body.append('\\nopagebreak\n\n')
self.body.append(BLANKLINE)
self.body.append('\\sphinxstrong{%s:}' % admonitionlabels['seealso'] + CR)
self.body.append('\\nopagebreak' + BLANKLINE)
def depart_seealso(self, node: Element) -> None:
self.body.append("\n\n")
self.body.append(BLANKLINE)
def visit_rubric(self, node: Element) -> None:
if len(node) == 1 and node.astext() in ('Footnotes', _('Footnotes')):
raise nodes.SkipNode
self.body.append('\\subsubsection*{')
self.context.append('}\n')
self.context.append('}' + CR)
self.in_title = 1
def depart_rubric(self, node: Element) -> None:
@@ -849,18 +850,18 @@ class LaTeXTranslator(SphinxTranslator):
if self.in_parsed_literal:
self.body.append('\\begin{footnote}[%s]' % label.astext())
else:
self.body.append('%\n')
self.body.append('%' + CR)
self.body.append('\\begin{footnote}[%s]' % label.astext())
if 'auto' not in node:
self.body.append('\\phantomsection'
'\\label{\\thesphinxscope.%s}%%\n' % label.astext())
self.body.append('\\sphinxAtStartFootnote\n')
'\\label{\\thesphinxscope.%s}%%' % label.astext() + CR)
self.body.append('\\sphinxAtStartFootnote' + CR)
def depart_footnote(self, node: Element) -> None:
if self.in_parsed_literal:
self.body.append('\\end{footnote}')
else:
self.body.append('%\n')
self.body.append('%' + CR)
self.body.append('\\end{footnote}')
self.in_footnote -= 1
@@ -888,7 +889,7 @@ class LaTeXTranslator(SphinxTranslator):
self.tables.append(Table(node))
if self.next_table_colspec:
self.table.colspec = '{%s}\n' % self.next_table_colspec
self.table.colspec = '{%s}' % self.next_table_colspec + CR
if 'colwidths-given' in node.get('classes', []):
logger.info(__('both tabularcolumns and :widths: option are given. '
':widths: is ignored.'), location=node)
@@ -899,9 +900,9 @@ class LaTeXTranslator(SphinxTranslator):
table_type = self.table.get_table_type()
table = self.render(table_type + '.tex_t',
dict(table=self.table, labels=labels))
self.body.append("\n\n")
self.body.append(BLANKLINE)
self.body.append(table)
self.body.append("\n")
self.body.append(CR)
self.tables.pop()
@@ -956,7 +957,7 @@ class LaTeXTranslator(SphinxTranslator):
(cell.width, cell.cell_id))
def depart_row(self, node: Element) -> None:
self.body.append('\\\\\n')
self.body.append('\\\\' + CR)
cells = [self.table.cell(self.table.row, i) for i in range(self.table.colcount)]
underlined = [cell.row + cell.height == self.table.row + 1 for cell in cells]
if all(underlined):
@@ -981,22 +982,22 @@ class LaTeXTranslator(SphinxTranslator):
if cell.width > 1:
if self.config.latex_use_latex_multicolumn:
if self.table.col == 0:
self.body.append('\\multicolumn{%d}{|l|}{%%\n' % cell.width)
self.body.append('\\multicolumn{%d}{|l|}{%%' % cell.width + CR)
else:
self.body.append('\\multicolumn{%d}{l|}{%%\n' % cell.width)
context = '}%\n'
self.body.append('\\multicolumn{%d}{l|}{%%' % cell.width + CR)
context = '}%' + CR
else:
self.body.append('\\sphinxstartmulticolumn{%d}%%\n' % cell.width)
context = '\\sphinxstopmulticolumn\n'
self.body.append('\\sphinxstartmulticolumn{%d}%%' % cell.width + CR)
context = '\\sphinxstopmulticolumn' + CR
if cell.height > 1:
# \sphinxmultirow 2nd arg "cell_id" will serve as id for LaTeX macros as well
self.body.append('\\sphinxmultirow{%d}{%d}{%%\n' % (cell.height, cell.cell_id))
context = '}%\n' + context
self.body.append('\\sphinxmultirow{%d}{%d}{%%' % (cell.height, cell.cell_id) + CR)
context = '}%' + CR + context
if cell.width > 1 or cell.height > 1:
self.body.append('\\begin{varwidth}[t]{\\sphinxcolwidth{%d}{%d}}\n'
% (cell.width, self.table.colcount))
context = ('\\par\n\\vskip-\\baselineskip'
'\\vbox{\\hbox{\\strut}}\\end{varwidth}%\n') + context
self.body.append('\\begin{varwidth}[t]{\\sphinxcolwidth{%d}{%d}}'
% (cell.width, self.table.colcount) + CR)
context = ('\\par' + CR + '\\vskip-\\baselineskip'
'\\vbox{\\hbox{\\strut}}\\end{varwidth}%' + CR + context)
self.needs_linetrimming = 1
if len(node.traverse(nodes.paragraph)) >= 2:
self.table.has_oldproblematic = True
@@ -1015,7 +1016,7 @@ class LaTeXTranslator(SphinxTranslator):
body = self.popbody()
# Remove empty lines from top of merged cell
while body and body[0] == "\n":
while body and body[0] == CR:
body.pop(0)
self.body.extend(body)
@@ -1047,20 +1048,20 @@ class LaTeXTranslator(SphinxTranslator):
# comma-separated list here
bullet_list = cast(nodes.bullet_list, node[0])
list_items = cast(Iterable[nodes.list_item], bullet_list)
self.body.append('\n\n')
self.body.append(BLANKLINE)
self.body.append(', '.join(n.astext() for n in list_items) + '.')
self.body.append('\n\n')
self.body.append(BLANKLINE)
raise nodes.SkipNode
def visit_bullet_list(self, node: Element) -> None:
if not self.compact_list:
self.body.append('\\begin{itemize}\n')
self.body.append('\\begin{itemize}' + CR)
if self.table:
self.table.has_problematic = True
def depart_bullet_list(self, node: Element) -> None:
if not self.compact_list:
self.body.append('\\end{itemize}\n')
self.body.append('\\end{itemize}' + CR)
def visit_enumerated_list(self, node: Element) -> None:
def get_enumtype(node: Element) -> str:
@@ -1085,16 +1086,16 @@ class LaTeXTranslator(SphinxTranslator):
prefix = node.get('prefix', '')
suffix = node.get('suffix', '.')
self.body.append('\\begin{enumerate}\n')
self.body.append('\\sphinxsetlistlabels{%s}{%s}{%s}{%s}{%s}%%\n' %
(style, enum, enumnext, prefix, suffix))
self.body.append('\\begin{enumerate}' + CR)
self.body.append('\\sphinxsetlistlabels{%s}{%s}{%s}{%s}{%s}%%' %
(style, enum, enumnext, prefix, suffix) + CR)
if 'start' in node:
self.body.append('\\setcounter{%s}{%d}\n' % (enum, node['start'] - 1))
self.body.append('\\setcounter{%s}{%d}' % (enum, node['start'] - 1) + CR)
if self.table:
self.table.has_problematic = True
def depart_enumerated_list(self, node: Element) -> None:
self.body.append('\\end{enumerate}\n')
self.body.append('\\end{enumerate}' + CR)
def visit_list_item(self, node: Element) -> None:
# Append "{}" in case the next character is "[", which would break
@@ -1102,15 +1103,15 @@ class LaTeXTranslator(SphinxTranslator):
self.body.append(r'\item {} ')
def depart_list_item(self, node: Element) -> None:
self.body.append('\n')
self.body.append(CR)
def visit_definition_list(self, node: Element) -> None:
self.body.append('\\begin{description}\n')
self.body.append('\\begin{description}' + CR)
if self.table:
self.table.has_problematic = True
def depart_definition_list(self, node: Element) -> None:
self.body.append('\\end{description}\n')
self.body.append('\\end{description}' + CR)
def visit_definition_list_item(self, node: Element) -> None:
pass
@@ -1143,15 +1144,15 @@ class LaTeXTranslator(SphinxTranslator):
pass
def depart_definition(self, node: Element) -> None:
self.body.append('\n')
self.body.append(CR)
def visit_field_list(self, node: Element) -> None:
self.body.append('\\begin{quote}\\begin{description}\n')
self.body.append('\\begin{quote}\\begin{description}' + CR)
if self.table:
self.table.has_problematic = True
def depart_field_list(self, node: Element) -> None:
self.body.append('\\end{description}\\end{quote}\n')
self.body.append('\\end{description}\\end{quote}' + CR)
def visit_field(self, node: Element) -> None:
pass
@@ -1171,7 +1172,7 @@ class LaTeXTranslator(SphinxTranslator):
not isinstance(node.parent[index - 1], nodes.paragraph) and
not isinstance(node.parent[index - 1], nodes.compound)):
# insert blank line, if the paragraph follows a non-paragraph node in a compound
self.body.append('\\noindent\n')
self.body.append('\\noindent' + CR)
elif index == 1 and isinstance(node.parent, (nodes.footnote, footnotetext)):
# don't insert blank line, if the paragraph is second child of a footnote
# (first one is label node)
@@ -1180,33 +1181,33 @@ class LaTeXTranslator(SphinxTranslator):
# the \sphinxAtStartPar is to allow hyphenation of first word of
# a paragraph in narrow contexts such as in a table cell
# added as two items (cf. line trimming in depart_entry())
self.body.extend(['\n', '\\sphinxAtStartPar\n'])
self.body.extend([CR, '\\sphinxAtStartPar' + CR])
def depart_paragraph(self, node: Element) -> None:
self.body.append('\n')
self.body.append(CR)
def visit_centered(self, node: Element) -> None:
self.body.append('\n\\begin{center}')
self.body.append(CR + '\\begin{center}')
if self.table:
self.table.has_problematic = True
def depart_centered(self, node: Element) -> None:
self.body.append('\n\\end{center}')
self.body.append(CR + '\\end{center}')
def visit_hlist(self, node: Element) -> None:
self.compact_list += 1
ncolumns = node['ncolumns']
if self.compact_list > 1:
self.body.append('\\setlength{\\multicolsep}{0pt}\n')
self.body.append('\\begin{multicols}{' + ncolumns + '}\\raggedright\n')
self.body.append('\\setlength{\\multicolsep}{0pt}' + CR)
self.body.append('\\begin{multicols}{' + ncolumns + '}\\raggedright' + CR)
self.body.append('\\begin{itemize}\\setlength{\\itemsep}{0pt}'
'\\setlength{\\parskip}{0pt}\n')
'\\setlength{\\parskip}{0pt}' + CR)
if self.table:
self.table.has_problematic = True
def depart_hlist(self, node: Element) -> None:
self.compact_list -= 1
self.body.append('\\end{itemize}\\raggedcolumns\\end{multicols}\n')
self.body.append('\\end{itemize}\\raggedcolumns\\end{multicols}' + CR)
def visit_hlistcol(self, node: Element) -> None:
pass
@@ -1282,8 +1283,8 @@ class LaTeXTranslator(SphinxTranslator):
pre.append('{\\sphinxunactivateextrasandspace ')
post.append('}')
if not is_inline and not has_hyperlink:
pre.append('\n\\noindent')
post.append('\n')
pre.append(CR + '\\noindent')
post.append(CR)
pre.reverse()
if node['uri'] in self.builder.images:
uri = self.builder.images[node['uri']]
@@ -1322,35 +1323,35 @@ class LaTeXTranslator(SphinxTranslator):
if 'width' in node:
length = self.latex_image_length(node['width'])
if length:
self.body.append('\\begin{sphinxfigure-in-table}[%s]\n' % length)
self.body.append('\\centering\n')
self.body.append('\\begin{sphinxfigure-in-table}[%s]' % length + CR)
self.body.append('\\centering' + CR)
else:
self.body.append('\\begin{sphinxfigure-in-table}\n')
self.body.append('\\centering\n')
self.body.append('\\begin{sphinxfigure-in-table}' + CR)
self.body.append('\\centering' + CR)
if any(isinstance(child, nodes.caption) for child in node):
self.body.append('\\capstart')
self.context.append('\\end{sphinxfigure-in-table}\\relax\n')
self.context.append('\\end{sphinxfigure-in-table}\\relax' + CR)
elif node.get('align', '') in ('left', 'right'):
length = None
if 'width' in node:
length = self.latex_image_length(node['width'])
elif isinstance(node[0], nodes.image) and 'width' in node[0]:
length = self.latex_image_length(node[0]['width'])
self.body.append('\n\n') # Insert a blank line to prevent infinite loop
# https://github.com/sphinx-doc/sphinx/issues/7059
self.body.append('\\begin{wrapfigure}{%s}{%s}\n' %
('r' if node['align'] == 'right' else 'l', length or '0pt'))
self.body.append(BLANKLINE) # Insert a blank line to prevent infinite loop
# https://github.com/sphinx-doc/sphinx/issues/7059
self.body.append('\\begin{wrapfigure}{%s}{%s}' %
('r' if node['align'] == 'right' else 'l', length or '0pt') + CR)
self.body.append('\\centering')
self.context.append('\\end{wrapfigure}\n')
self.context.append('\\end{wrapfigure}' + CR)
elif self.in_minipage:
self.body.append('\n\\begin{center}')
self.context.append('\\end{center}\n')
self.body.append(CR + '\\begin{center}')
self.context.append('\\end{center}' + CR)
else:
self.body.append('\n\\begin{figure}[%s]\n' % align)
self.body.append('\\centering\n')
self.body.append(CR + '\\begin{figure}[%s]' % align + CR)
self.body.append('\\centering' + CR)
if any(isinstance(child, nodes.caption) for child in node):
self.body.append('\\capstart\n')
self.context.append('\\end{figure}\n')
self.body.append('\\capstart' + CR)
self.context.append('\\end{figure}' + CR)
def depart_figure(self, node: Element) -> None:
self.body.append(self.context.pop())
@@ -1374,27 +1375,27 @@ class LaTeXTranslator(SphinxTranslator):
self.in_caption -= 1
def visit_legend(self, node: Element) -> None:
self.body.append('\n\\begin{sphinxlegend}')
self.body.append(CR + '\\begin{sphinxlegend}')
def depart_legend(self, node: Element) -> None:
self.body.append('\\end{sphinxlegend}\n')
self.body.append('\\end{sphinxlegend}' + CR)
def visit_admonition(self, node: Element) -> None:
self.body.append('\n\\begin{sphinxadmonition}{note}')
self.body.append(CR + '\\begin{sphinxadmonition}{note}')
self.no_latex_floats += 1
def depart_admonition(self, node: Element) -> None:
self.body.append('\\end{sphinxadmonition}\n')
self.body.append('\\end{sphinxadmonition}' + CR)
self.no_latex_floats -= 1
def _visit_named_admonition(self, node: Element) -> None:
label = admonitionlabels[node.tagname]
self.body.append('\n\\begin{sphinxadmonition}{%s}{%s:}' %
self.body.append(CR + '\\begin{sphinxadmonition}{%s}{%s:}' %
(node.tagname, label))
self.no_latex_floats += 1
def _depart_named_admonition(self, node: Element) -> None:
self.body.append('\\end{sphinxadmonition}\n')
self.body.append('\\end{sphinxadmonition}' + CR)
self.no_latex_floats -= 1
visit_attention = _visit_named_admonition
@@ -1437,7 +1438,7 @@ class LaTeXTranslator(SphinxTranslator):
# insert blank line, if the target follows a paragraph node
index = node.parent.index(node)
if index > 0 and isinstance(node.parent[index - 1], nodes.paragraph):
self.body.append('\n')
self.body.append(CR)
# do not generate \phantomsection in \section{}
anchor = not self.in_title
@@ -1472,11 +1473,11 @@ class LaTeXTranslator(SphinxTranslator):
pass
def visit_attribution(self, node: Element) -> None:
self.body.append('\n\\begin{flushright}\n')
self.body.append(CR + '\\begin{flushright}' + CR)
self.body.append('---')
def depart_attribution(self, node: Element) -> None:
self.body.append('\n\\end{flushright}\n')
self.body.append(CR + '\\end{flushright}' + CR)
def visit_index(self, node: Element) -> None:
def escape(value: str) -> str:
@@ -1497,7 +1498,7 @@ class LaTeXTranslator(SphinxTranslator):
return '\\spxentry{%s}' % string
if not node.get('inline', True):
self.body.append('\n')
self.body.append(CR)
entries = node['entries']
for type, string, tid, ismain, key_ in entries:
m = ''
@@ -1546,11 +1547,11 @@ class LaTeXTranslator(SphinxTranslator):
def visit_raw(self, node: Element) -> None:
if not self.is_inline(node):
self.body.append('\n')
self.body.append(CR)
if 'latex' in node.get('format', '').split():
self.body.append(node.astext())
if not self.is_inline(node):
self.body.append('\n')
self.body.append(CR)
raise nodes.SkipNode
def visit_reference(self, node: Element) -> None:
@@ -1559,7 +1560,7 @@ class LaTeXTranslator(SphinxTranslator):
anchor = not self.in_caption
self.body += self.hypertarget(id, anchor=anchor)
if not self.is_inline(node):
self.body.append('\n')
self.body.append(CR)
uri = node.get('refuri', '')
if not uri and node.get('refid'):
uri = '%' + self.curfilestack[-1] + '#' + node['refid']
@@ -1612,7 +1613,7 @@ class LaTeXTranslator(SphinxTranslator):
def depart_reference(self, node: Element) -> None:
self.body.append(self.context.pop())
if not self.is_inline(node):
self.body.append('\n')
self.body.append(CR)
def visit_number_reference(self, node: Element) -> None:
if node.get('refid'):
@@ -1703,11 +1704,11 @@ class LaTeXTranslator(SphinxTranslator):
# adjust max width of citation labels not to break the layout
longest_label = longest_label[:MAX_CITATION_LABEL_LENGTH]
self.body.append('\n\\begin{sphinxthebibliography}{%s}\n' %
self.encode(longest_label))
self.body.append(CR + '\\begin{sphinxthebibliography}{%s}' %
self.encode(longest_label) + CR)
def depart_thebibliography(self, node: Element) -> None:
self.body.append('\\end{sphinxthebibliography}\n')
self.body.append('\\end{sphinxthebibliography}' + CR)
def visit_citation(self, node: Element) -> None:
label = cast(nodes.label, node[0])
@@ -1749,15 +1750,15 @@ class LaTeXTranslator(SphinxTranslator):
def visit_footnotetext(self, node: Element) -> None:
label = cast(nodes.label, node[0])
self.body.append('%\n')
self.body.append('%' + CR)
self.body.append('\\begin{footnotetext}[%s]'
'\\phantomsection\\label{\\thesphinxscope.%s}%%\n'
% (label.astext(), label.astext()))
self.body.append('\\sphinxAtStartFootnote\n')
'\\phantomsection\\label{\\thesphinxscope.%s}%%'
% (label.astext(), label.astext()) + CR)
self.body.append('\\sphinxAtStartFootnote' + CR)
def depart_footnotetext(self, node: Element) -> None:
# the \ignorespaces in particular for after table header use
self.body.append('%\n')
self.body.append('%' + CR)
self.body.append('\\end{footnotetext}\\ignorespaces ')
def visit_captioned_literal_block(self, node: Element) -> None:
@@ -1770,13 +1771,13 @@ class LaTeXTranslator(SphinxTranslator):
if node.rawsource != node.astext():
# most probably a parsed-literal block -- don't highlight
self.in_parsed_literal += 1
self.body.append('\\begin{sphinxalltt}\n')
self.body.append('\\begin{sphinxalltt}' + CR)
else:
labels = self.hypertarget_to(node)
if isinstance(node.parent, captioned_literal_block):
labels += self.hypertarget_to(node.parent)
if labels and not self.in_footnote:
self.body.append('\n\\def\\sphinxLiteralBlockLabel{' + labels + '}')
self.body.append(CR + '\\def\\sphinxLiteralBlockLabel{' + labels + '}')
lang = node.get('language', 'default')
linenos = node.get('linenos', False)
@@ -1789,7 +1790,7 @@ class LaTeXTranslator(SphinxTranslator):
location=node, **highlight_args
)
if self.in_footnote:
self.body.append('\n\\sphinxSetupCodeBlockInFootnote')
self.body.append(CR + '\\sphinxSetupCodeBlockInFootnote')
hlcode = hlcode.replace('\\begin{Verbatim}',
'\\begin{sphinxVerbatim}')
# if in table raise verbatim flag to avoid "tabulary" environment
@@ -1811,14 +1812,14 @@ class LaTeXTranslator(SphinxTranslator):
hllines = str(highlight_args.get('hl_lines', []))[1:-1]
if hllines:
self.body.append('\n\\fvset{hllines={, %s,}}%%' % hllines)
self.body.append('\n' + hlcode + '\n')
self.body.append(CR + '\\fvset{hllines={, %s,}}%%' % hllines)
self.body.append(CR + hlcode + CR)
if hllines:
self.body.append('\\sphinxresetverbatimhllines\n')
self.body.append('\\sphinxresetverbatimhllines' + CR)
raise nodes.SkipNode
def depart_literal_block(self, node: Element) -> None:
self.body.append('\n\\end{sphinxalltt}\n')
self.body.append(CR + '\\end{sphinxalltt}' + CR)
self.in_parsed_literal -= 1
visit_doctest_block = visit_literal_block
depart_doctest_block = depart_literal_block
@@ -1827,19 +1828,19 @@ class LaTeXTranslator(SphinxTranslator):
self.body.append('\\item[] ')
def depart_line(self, node: Element) -> None:
self.body.append('\n')
self.body.append(CR)
def visit_line_block(self, node: Element) -> None:
if isinstance(node.parent, nodes.line_block):
self.body.append('\\item[]\n')
self.body.append('\\begin{DUlineblock}{\\DUlineblockindent}\n')
self.body.append('\\item[]' + CR)
self.body.append('\\begin{DUlineblock}{\\DUlineblockindent}' + CR)
else:
self.body.append('\n\\begin{DUlineblock}{0em}\n')
self.body.append(CR + '\\begin{DUlineblock}{0em}' + CR)
if self.table:
self.table.has_problematic = True
def depart_line_block(self, node: Element) -> None:
self.body.append('\\end{DUlineblock}\n')
self.body.append('\\end{DUlineblock}' + CR)
def visit_block_quote(self, node: Element) -> None:
# If the block quote contains a single object and that object
@@ -1852,7 +1853,7 @@ class LaTeXTranslator(SphinxTranslator):
isinstance(child, nodes.enumerated_list):
done = 1
if not done:
self.body.append('\\begin{quote}\n')
self.body.append('\\begin{quote}' + CR)
if self.table:
self.table.has_problematic = True
@@ -1864,7 +1865,7 @@ class LaTeXTranslator(SphinxTranslator):
isinstance(child, nodes.enumerated_list):
done = 1
if not done:
self.body.append('\\end{quote}\n')
self.body.append('\\end{quote}' + CR)
# option node handling copied from docutils' latex writer
@@ -1894,12 +1895,12 @@ class LaTeXTranslator(SphinxTranslator):
self.body.append('] ')
def visit_option_list(self, node: Element) -> None:
self.body.append('\\begin{optionlist}{3cm}\n')
self.body.append('\\begin{optionlist}{3cm}' + CR)
if self.table:
self.table.has_problematic = True
def depart_option_list(self, node: Element) -> None:
self.body.append('\\end{optionlist}\n')
self.body.append('\\end{optionlist}' + CR)
def visit_option_list_item(self, node: Element) -> None:
pass
@@ -1992,7 +1993,7 @@ class LaTeXTranslator(SphinxTranslator):
if self.literal_whitespace:
# Insert a blank before the newline, to avoid
# ! LaTeX Error: There's no line here to end.
text = text.replace('\n', '~\\\\\n').replace(' ', '~')
text = text.replace(CR, '~\\\\' + CR).replace(' ', '~')
return text
def encode_uri(self, text: str) -> str:
@@ -2022,7 +2023,7 @@ class LaTeXTranslator(SphinxTranslator):
pass
def depart_system_message(self, node: Element) -> None:
self.body.append('\n')
self.body.append(CR)
def visit_math(self, node: Element) -> None:
if self.in_title:

View File

@@ -15,14 +15,14 @@ import textwrap
import time
import wsgiref.handlers
from datetime import datetime
from queue import Queue
from typing import Dict
from unittest import mock
import pytest
import requests
from sphinx.builders.linkcheck import (CheckExternalLinksBuilder,
HyperlinkAvailabilityCheckWorker, RateLimit)
from sphinx.builders.linkcheck import HyperlinkAvailabilityCheckWorker, RateLimit
from sphinx.util.console import strip_colors
from .utils import CERT_FILE, http_server, https_server
@@ -536,10 +536,7 @@ class FakeResponse:
def test_limit_rate_default_sleep(app):
checker = CheckExternalLinksBuilder(app)
checker.init()
checker.rate_limits = {}
worker = HyperlinkAvailabilityCheckWorker(checker)
worker = HyperlinkAvailabilityCheckWorker(app.env, app.config, Queue(), Queue(), {})
with mock.patch('time.time', return_value=0.0):
next_check = worker.limit_rate(FakeResponse())
assert next_check == 60.0
@@ -547,40 +544,34 @@ def test_limit_rate_default_sleep(app):
def test_limit_rate_user_max_delay(app):
app.config.linkcheck_rate_limit_timeout = 0.0
checker = CheckExternalLinksBuilder(app)
checker.init()
checker.rate_limits = {}
worker = HyperlinkAvailabilityCheckWorker(checker)
worker = HyperlinkAvailabilityCheckWorker(app.env, app.config, Queue(), Queue(), {})
next_check = worker.limit_rate(FakeResponse())
assert next_check is None
def test_limit_rate_doubles_previous_wait_time(app):
checker = CheckExternalLinksBuilder(app)
checker.init()
checker.rate_limits = {"localhost": RateLimit(60.0, 0.0)}
worker = HyperlinkAvailabilityCheckWorker(checker)
rate_limits = {"localhost": RateLimit(60.0, 0.0)}
worker = HyperlinkAvailabilityCheckWorker(app.env, app.config, Queue(), Queue(),
rate_limits)
with mock.patch('time.time', return_value=0.0):
next_check = worker.limit_rate(FakeResponse())
assert next_check == 120.0
def test_limit_rate_clips_wait_time_to_max_time(app):
checker = CheckExternalLinksBuilder(app)
checker.init()
app.config.linkcheck_rate_limit_timeout = 90.0
checker.rate_limits = {"localhost": RateLimit(60.0, 0.0)}
worker = HyperlinkAvailabilityCheckWorker(checker)
rate_limits = {"localhost": RateLimit(60.0, 0.0)}
worker = HyperlinkAvailabilityCheckWorker(app.env, app.config, Queue(), Queue(),
rate_limits)
with mock.patch('time.time', return_value=0.0):
next_check = worker.limit_rate(FakeResponse())
assert next_check == 90.0
def test_limit_rate_bails_out_after_waiting_max_time(app):
checker = CheckExternalLinksBuilder(app)
checker.init()
app.config.linkcheck_rate_limit_timeout = 90.0
checker.rate_limits = {"localhost": RateLimit(90.0, 0.0)}
worker = HyperlinkAvailabilityCheckWorker(checker)
rate_limits = {"localhost": RateLimit(90.0, 0.0)}
worker = HyperlinkAvailabilityCheckWorker(app.env, app.config, Queue(), Queue(),
rate_limits)
next_check = worker.limit_rate(FakeResponse())
assert next_check is None