mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch '3.x'
This commit is contained in:
8
CHANGES
8
CHANGES
@@ -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
|
||||
--------
|
||||
|
@@ -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(',')]
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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)):
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user