mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge with 'stable'
This commit is contained in:
commit
bf1cc70996
6
CHANGES
6
CHANGES
@ -48,6 +48,12 @@ Bugs fixed
|
|||||||
* #2134: Fix figure caption with reference causes latex build error
|
* #2134: Fix figure caption with reference causes latex build error
|
||||||
* #2094: Fix rubric with reference not working in Latex
|
* #2094: Fix rubric with reference not working in Latex
|
||||||
* #2147: Fix literalinclude code in latex does not break in pages
|
* #2147: Fix literalinclude code in latex does not break in pages
|
||||||
|
* #1833: Fix email addresses is showed again if latex_show_urls is not None
|
||||||
|
* #2176: sphinx.ext.graphviz: use <object> instead of <img> to embed svg
|
||||||
|
* #967: Fix SVG inheritance diagram is not hyperlinked (clickable)
|
||||||
|
* #1237: Fix footnotes not working in definition list in LaTeX
|
||||||
|
* #2168: Fix raw directive does not work for text writer
|
||||||
|
* #2171: Fix cannot linkcheck url with unicode
|
||||||
|
|
||||||
|
|
||||||
Release 1.3.3 (released Dec 2, 2015)
|
Release 1.3.3 (released Dec 2, 2015)
|
||||||
|
@ -111,7 +111,18 @@ There are also these new config values:
|
|||||||
.. confval:: graphviz_output_format
|
.. confval:: graphviz_output_format
|
||||||
|
|
||||||
The output format for Graphviz when building HTML files. This must be either
|
The output format for Graphviz when building HTML files. This must be either
|
||||||
``'png'`` or ``'svg'``; the default is ``'png'``.
|
``'png'`` or ``'svg'``; the default is ``'png'``. If ``'svg'`` is used, in
|
||||||
|
order to make the URL links work properly, an appropriate ``target``
|
||||||
|
attribute must be set, such as ``"_top"`` and ``"_blank"``. For example, the
|
||||||
|
link in the following graph should work in the svg output: ::
|
||||||
|
|
||||||
|
.. graphviz::
|
||||||
|
|
||||||
|
digraph example {
|
||||||
|
a [label="sphinx", href="http://sphinx-doc.org", target="_top"];
|
||||||
|
b [label="other"];
|
||||||
|
a -> b;
|
||||||
|
}
|
||||||
|
|
||||||
.. versionadded:: 1.0
|
.. versionadded:: 1.0
|
||||||
Previously, output always was PNG.
|
Previously, output always was PNG.
|
||||||
|
@ -17,7 +17,7 @@ from os import path
|
|||||||
|
|
||||||
from six.moves import queue
|
from six.moves import queue
|
||||||
from six.moves.urllib.request import build_opener, Request, HTTPRedirectHandler
|
from six.moves.urllib.request import build_opener, Request, HTTPRedirectHandler
|
||||||
from six.moves.urllib.parse import unquote, urlsplit, quote
|
from six.moves.urllib.parse import unquote
|
||||||
from six.moves.urllib.error import HTTPError
|
from six.moves.urllib.error import HTTPError
|
||||||
from six.moves.html_parser import HTMLParser
|
from six.moves.html_parser import HTMLParser
|
||||||
from docutils import nodes
|
from docutils import nodes
|
||||||
@ -33,6 +33,7 @@ except ImportError:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
from sphinx.builders import Builder
|
from sphinx.builders import Builder
|
||||||
|
from sphinx.util import encode_uri
|
||||||
from sphinx.util.console import purple, red, darkgreen, darkgray, \
|
from sphinx.util.console import purple, red, darkgreen, darkgray, \
|
||||||
darkred, turquoise
|
darkred, turquoise
|
||||||
from sphinx.util.pycompat import TextIOWrapper
|
from sphinx.util.pycompat import TextIOWrapper
|
||||||
@ -153,15 +154,7 @@ class CheckExternalLinksBuilder(Builder):
|
|||||||
try:
|
try:
|
||||||
req_url.encode('ascii')
|
req_url.encode('ascii')
|
||||||
except UnicodeError:
|
except UnicodeError:
|
||||||
split = urlsplit(req_url)
|
req_url = encode_uri(req_url)
|
||||||
req_url = (split[0].encode() + '://' + # scheme
|
|
||||||
split[1].encode('idna') + # netloc
|
|
||||||
quote(split[2].encode('utf-8'))) # path
|
|
||||||
if split[3]: # query
|
|
||||||
req_url += '?' + quote(split[3].encode('utf-8'))
|
|
||||||
# go back to Unicode strings which is required by Python 3
|
|
||||||
# (but now all parts are pure ascii)
|
|
||||||
req_url = req_url.decode('ascii')
|
|
||||||
|
|
||||||
# need to actually check the URI
|
# need to actually check the URI
|
||||||
try:
|
try:
|
||||||
|
@ -223,7 +223,8 @@ def render_dot_html(self, node, code, options, prefix='graphviz',
|
|||||||
alt = node.get('alt', self.encode(code).strip())
|
alt = node.get('alt', self.encode(code).strip())
|
||||||
imgcss = imgcls and 'class="%s"' % imgcls or ''
|
imgcss = imgcls and 'class="%s"' % imgcls or ''
|
||||||
if format == 'svg':
|
if format == 'svg':
|
||||||
svgtag = '<img src="%s" alt="%s" %s/>\n' % (fname, alt, imgcss)
|
svgtag = '''<object data="%s" type="image/svg+xml">
|
||||||
|
<p class="warning">%s</p></object>\n''' % (fname, alt)
|
||||||
self.body.append(svgtag)
|
self.body.append(svgtag)
|
||||||
else:
|
else:
|
||||||
mapfile = open(outfn + '.map', 'rb')
|
mapfile = open(outfn + '.map', 'rb')
|
||||||
|
@ -264,6 +264,7 @@ class InheritanceGraph(object):
|
|||||||
this_node_attrs = n_attrs.copy()
|
this_node_attrs = n_attrs.copy()
|
||||||
if fullname in urls:
|
if fullname in urls:
|
||||||
this_node_attrs['URL'] = '"%s"' % urls[fullname]
|
this_node_attrs['URL'] = '"%s"' % urls[fullname]
|
||||||
|
this_node_attrs['target'] = '"_top"'
|
||||||
if tooltip:
|
if tooltip:
|
||||||
this_node_attrs['tooltip'] = tooltip
|
this_node_attrs['tooltip'] = tooltip
|
||||||
res.append(' "%s" [%s];\n' %
|
res.append(' "%s" [%s];\n' %
|
||||||
@ -348,11 +349,19 @@ def html_visit_inheritance_diagram(self, node):
|
|||||||
name = 'inheritance%s' % graph_hash
|
name = 'inheritance%s' % graph_hash
|
||||||
|
|
||||||
# Create a mapping from fully-qualified class names to URLs.
|
# Create a mapping from fully-qualified class names to URLs.
|
||||||
|
graphviz_output_format = self.builder.env.config.graphviz_output_format.upper()
|
||||||
|
current_filename = self.builder.current_docname + self.builder.out_suffix
|
||||||
urls = {}
|
urls = {}
|
||||||
for child in node:
|
for child in node:
|
||||||
if child.get('refuri') is not None:
|
if child.get('refuri') is not None:
|
||||||
|
if graphviz_output_format == 'SVG':
|
||||||
|
urls[child['reftitle']] = "../" + child.get('refuri')
|
||||||
|
else:
|
||||||
urls[child['reftitle']] = child.get('refuri')
|
urls[child['reftitle']] = child.get('refuri')
|
||||||
elif child.get('refid') is not None:
|
elif child.get('refid') is not None:
|
||||||
|
if graphviz_output_format == 'SVG':
|
||||||
|
urls[child['reftitle']] = '../' + current_filename + '#' + child.get('refid')
|
||||||
|
else:
|
||||||
urls[child['reftitle']] = '#' + child.get('refid')
|
urls[child['reftitle']] = '#' + child.get('refid')
|
||||||
|
|
||||||
dotcode = graph.generate_dot(name, urls, env=self.builder.env)
|
dotcode = graph.generate_dot(name, urls, env=self.builder.env)
|
||||||
|
@ -23,6 +23,7 @@ from collections import deque
|
|||||||
|
|
||||||
from six import iteritems, text_type, binary_type
|
from six import iteritems, text_type, binary_type
|
||||||
from six.moves import range
|
from six.moves import range
|
||||||
|
from six.moves.urllib.parse import urlsplit, urlunsplit, quote_plus, parse_qsl, urlencode
|
||||||
import docutils
|
import docutils
|
||||||
from docutils.utils import relative_path
|
from docutils.utils import relative_path
|
||||||
|
|
||||||
@ -523,3 +524,13 @@ def import_object(objname, source=None):
|
|||||||
raise ExtensionError('Could not find %s' % objname +
|
raise ExtensionError('Could not find %s' % objname +
|
||||||
(source and ' (needed for %s)' % source or ''),
|
(source and ' (needed for %s)' % source or ''),
|
||||||
err)
|
err)
|
||||||
|
|
||||||
|
|
||||||
|
def encode_uri(uri):
|
||||||
|
split = list(urlsplit(uri))
|
||||||
|
split[1] = split[1].encode('idna').decode('ascii')
|
||||||
|
split[2] = quote_plus(split[2].encode('utf-8'), '/').decode('ascii')
|
||||||
|
query = list((q, quote_plus(v.encode('utf-8')))
|
||||||
|
for (q, v) in parse_qsl(split[3]))
|
||||||
|
split[3] = urlencode(query).decode('ascii')
|
||||||
|
return urlunsplit(split)
|
||||||
|
@ -69,6 +69,8 @@ FOOTER = r'''
|
|||||||
\end{document}
|
\end{document}
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
URI_SCHEMES = ('mailto:', 'http:', 'https:', 'ftp:')
|
||||||
|
|
||||||
|
|
||||||
class collected_footnote(nodes.footnote):
|
class collected_footnote(nodes.footnote):
|
||||||
"""Footnotes that are collected are assigned this class."""
|
"""Footnotes that are collected are assigned this class."""
|
||||||
@ -98,6 +100,8 @@ class LaTeXWriter(writers.Writer):
|
|||||||
self.builder.translator_class or LaTeXTranslator)
|
self.builder.translator_class or LaTeXTranslator)
|
||||||
|
|
||||||
def translate(self):
|
def translate(self):
|
||||||
|
transform = ShowUrlsTransform(self.document)
|
||||||
|
transform.apply()
|
||||||
visitor = self.translator_class(self.document, self.builder)
|
visitor = self.translator_class(self.document, self.builder)
|
||||||
self.document.walkabout(visitor)
|
self.document.walkabout(visitor)
|
||||||
self.output = visitor.astext()
|
self.output = visitor.astext()
|
||||||
@ -126,6 +130,99 @@ if hasattr(Babel, '_ISO639_TO_BABEL'):
|
|||||||
Babel._ISO639_TO_BABEL['sl'] = 'slovene'
|
Babel._ISO639_TO_BABEL['sl'] = 'slovene'
|
||||||
|
|
||||||
|
|
||||||
|
class ShowUrlsTransform(object):
|
||||||
|
expanded = False
|
||||||
|
|
||||||
|
def __init__(self, document):
|
||||||
|
self.document = document
|
||||||
|
|
||||||
|
def apply(self):
|
||||||
|
# replace id_prefix temporarily
|
||||||
|
id_prefix = self.document.settings.id_prefix
|
||||||
|
self.document.settings.id_prefix = 'show_urls'
|
||||||
|
|
||||||
|
self.expand_show_urls()
|
||||||
|
if self.expanded:
|
||||||
|
self.renumber_footnotes()
|
||||||
|
|
||||||
|
# restore id_prefix
|
||||||
|
self.document.settings.id_prefix = id_prefix
|
||||||
|
|
||||||
|
def expand_show_urls(self):
|
||||||
|
show_urls = self.document.settings.env.config.latex_show_urls
|
||||||
|
if show_urls is False or show_urls == 'no':
|
||||||
|
return
|
||||||
|
|
||||||
|
for node in self.document.traverse(nodes.reference):
|
||||||
|
uri = node.get('refuri', '')
|
||||||
|
if uri.startswith(URI_SCHEMES):
|
||||||
|
if uri.startswith('mailto:'):
|
||||||
|
uri = uri[7:]
|
||||||
|
if node.astext() != uri:
|
||||||
|
index = node.parent.index(node)
|
||||||
|
if show_urls == 'footnote':
|
||||||
|
footnote_nodes = self.create_footnote(uri)
|
||||||
|
for i, fn in enumerate(footnote_nodes):
|
||||||
|
node.parent.insert(index + i + 1, fn)
|
||||||
|
|
||||||
|
self.expanded = True
|
||||||
|
else: # all other true values (b/w compat)
|
||||||
|
textnode = nodes.Text(" (%s)" % uri)
|
||||||
|
node.parent.insert(index + 1, textnode)
|
||||||
|
|
||||||
|
def create_footnote(self, uri):
|
||||||
|
label = nodes.label('', '#')
|
||||||
|
para = nodes.paragraph()
|
||||||
|
para.append(nodes.Text(uri))
|
||||||
|
footnote = nodes.footnote(uri, label, para, auto=1)
|
||||||
|
footnote['names'].append('#')
|
||||||
|
self.document.note_autofootnote(footnote)
|
||||||
|
|
||||||
|
label = nodes.Text('#')
|
||||||
|
footnote_ref = nodes.footnote_reference('[#]_', label, auto=1,
|
||||||
|
refid=footnote['ids'][0])
|
||||||
|
self.document.note_autofootnote_ref(footnote_ref)
|
||||||
|
footnote.add_backref(footnote_ref['ids'][0])
|
||||||
|
|
||||||
|
return [footnote, footnote_ref]
|
||||||
|
|
||||||
|
def renumber_footnotes(self):
|
||||||
|
def is_used_number(number):
|
||||||
|
for node in self.document.traverse(nodes.footnote):
|
||||||
|
if not node.get('auto') and number in node['names']:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_auto_footnote(node):
|
||||||
|
return isinstance(node, nodes.footnote) and node.get('auto')
|
||||||
|
|
||||||
|
def footnote_ref_by(ids):
|
||||||
|
def is_footnote_ref(node):
|
||||||
|
return isinstance(node, nodes.footnote_reference) and ids[0] == node['refid']
|
||||||
|
|
||||||
|
return is_footnote_ref
|
||||||
|
|
||||||
|
startnum = 1
|
||||||
|
for footnote in self.document.traverse(is_auto_footnote):
|
||||||
|
while True:
|
||||||
|
label = str(startnum)
|
||||||
|
startnum += 1
|
||||||
|
if not is_used_number(label):
|
||||||
|
break
|
||||||
|
|
||||||
|
old_label = footnote[0].astext()
|
||||||
|
footnote.remove(footnote[0])
|
||||||
|
footnote.insert(0, nodes.label('', label))
|
||||||
|
if old_label in footnote['names']:
|
||||||
|
footnote['names'].remove(old_label)
|
||||||
|
footnote['names'].append(label)
|
||||||
|
|
||||||
|
for footnote_ref in self.document.traverse(footnote_ref_by(footnote['ids'])):
|
||||||
|
footnote_ref.remove(footnote_ref[0])
|
||||||
|
footnote_ref += nodes.Text(label)
|
||||||
|
|
||||||
|
|
||||||
class Table(object):
|
class Table(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.col = 0
|
self.col = 0
|
||||||
@ -278,6 +375,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
sys.maxsize]]
|
sys.maxsize]]
|
||||||
self.bodystack = []
|
self.bodystack = []
|
||||||
self.footnotestack = []
|
self.footnotestack = []
|
||||||
|
self.termfootnotestack = []
|
||||||
self.curfilestack = []
|
self.curfilestack = []
|
||||||
self.handled_abbrs = set()
|
self.handled_abbrs = set()
|
||||||
if document.settings.docclass == 'howto':
|
if document.settings.docclass == 'howto':
|
||||||
@ -297,6 +395,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
self.in_footnote = 0
|
self.in_footnote = 0
|
||||||
self.in_caption = 0
|
self.in_caption = 0
|
||||||
self.in_container_literal_block = 0
|
self.in_container_literal_block = 0
|
||||||
|
self.in_term = 0
|
||||||
self.first_document = 1
|
self.first_document = 1
|
||||||
self.this_is_the_title = 1
|
self.this_is_the_title = 1
|
||||||
self.literal_whitespace = 0
|
self.literal_whitespace = 0
|
||||||
@ -761,7 +860,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
|
|
||||||
def visit_collected_footnote(self, node):
|
def visit_collected_footnote(self, node):
|
||||||
self.in_footnote += 1
|
self.in_footnote += 1
|
||||||
if 'in_table' in node:
|
if 'footnotetext' in node:
|
||||||
self.body.append('\\footnotetext[%s]{' % node['number'])
|
self.body.append('\\footnotetext[%s]{' % node['number'])
|
||||||
else:
|
else:
|
||||||
self.body.append('\\footnote[%s]{' % node['number'])
|
self.body.append('\\footnote[%s]{' % node['number'])
|
||||||
@ -864,7 +963,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
self.body.append('\\end{threeparttable}\n\n')
|
self.body.append('\\end{threeparttable}\n\n')
|
||||||
if self.table.footnotes:
|
if self.table.footnotes:
|
||||||
for footnode in self.table.footnotes:
|
for footnode in self.table.footnotes:
|
||||||
footnode['in_table'] = True
|
footnode['footnotetext'] = True
|
||||||
footnode.walkabout(self)
|
footnode.walkabout(self)
|
||||||
self.table = None
|
self.table = None
|
||||||
self.tablebody = None
|
self.tablebody = None
|
||||||
@ -1031,14 +1130,22 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def visit_term(self, node):
|
def visit_term(self, node):
|
||||||
|
self.in_term += 1
|
||||||
ctx = '}] \\leavevmode'
|
ctx = '}] \\leavevmode'
|
||||||
if node.get('ids'):
|
if node.get('ids'):
|
||||||
ctx += self.hypertarget(node['ids'][0])
|
ctx += self.hypertarget(node['ids'][0])
|
||||||
self.body.append('\\item[{')
|
self.body.append('\\item[{')
|
||||||
|
self.termfootnotestack.append([])
|
||||||
self.context.append(ctx)
|
self.context.append(ctx)
|
||||||
|
|
||||||
def depart_term(self, node):
|
def depart_term(self, node):
|
||||||
self.body.append(self.context.pop())
|
self.body.append(self.context.pop())
|
||||||
|
footnotes = self.termfootnotestack.pop()
|
||||||
|
for footnode in footnotes:
|
||||||
|
footnode['footnotetext'] = True
|
||||||
|
footnode.walkabout(self)
|
||||||
|
|
||||||
|
self.in_term -= 1
|
||||||
|
|
||||||
def visit_termsep(self, node):
|
def visit_termsep(self, node):
|
||||||
self.body.append(', ')
|
self.body.append(', ')
|
||||||
@ -1399,22 +1506,8 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
uri = '%' + self.curfilestack[-1] + '#' + node['refid']
|
uri = '%' + self.curfilestack[-1] + '#' + node['refid']
|
||||||
if self.in_title or not uri:
|
if self.in_title or not uri:
|
||||||
self.context.append('')
|
self.context.append('')
|
||||||
elif uri.startswith('mailto:') or uri.startswith('http:') or \
|
elif uri.startswith(URI_SCHEMES):
|
||||||
uri.startswith('https:') or uri.startswith('ftp:'):
|
|
||||||
self.body.append('\\href{%s}{' % self.encode_uri(uri))
|
self.body.append('\\href{%s}{' % self.encode_uri(uri))
|
||||||
# if configured, put the URL after the link
|
|
||||||
show_urls = self.builder.config.latex_show_urls
|
|
||||||
if node.astext() != uri and show_urls and show_urls != 'no':
|
|
||||||
if uri.startswith('mailto:'):
|
|
||||||
uri = uri[7:]
|
|
||||||
if show_urls == 'footnote' and not \
|
|
||||||
(self.in_footnote or self.in_caption):
|
|
||||||
# obviously, footnotes in footnotes are not going to work
|
|
||||||
self.context.append(
|
|
||||||
r'}\footnote{%s}' % self.encode_uri(uri))
|
|
||||||
else: # all other true values (b/w compat)
|
|
||||||
self.context.append('} (%s)' % self.encode_uri(uri))
|
|
||||||
else:
|
|
||||||
self.context.append('}')
|
self.context.append('}')
|
||||||
elif uri.startswith('#'):
|
elif uri.startswith('#'):
|
||||||
# references to labels in the same document
|
# references to labels in the same document
|
||||||
@ -1566,7 +1659,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
# if a footnote has been inserted once, it shouldn't be repeated
|
# if a footnote has been inserted once, it shouldn't be repeated
|
||||||
# by the next reference
|
# by the next reference
|
||||||
if used:
|
if used:
|
||||||
if self.table:
|
if self.table or self.in_term:
|
||||||
self.body.append('\\protect\\footnotemark[%s]' % num)
|
self.body.append('\\protect\\footnotemark[%s]' % num)
|
||||||
else:
|
else:
|
||||||
self.body.append('\\footnotemark[%s]' % num)
|
self.body.append('\\footnotemark[%s]' % num)
|
||||||
@ -1574,6 +1667,9 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
self.footnotestack[-1][num][1] = True
|
self.footnotestack[-1][num][1] = True
|
||||||
self.body.append('\\protect\\footnotemark[%s]' % num)
|
self.body.append('\\protect\\footnotemark[%s]' % num)
|
||||||
self.table.footnotes.append(footnode)
|
self.table.footnotes.append(footnode)
|
||||||
|
elif self.in_term:
|
||||||
|
self.body.append('\\footnotemark[%s]' % num)
|
||||||
|
self.termfootnotestack[-1].append(footnode)
|
||||||
else:
|
else:
|
||||||
if self.in_caption:
|
if self.in_caption:
|
||||||
raise UnsupportedError('%s:%s: footnotes in float captions '
|
raise UnsupportedError('%s:%s: footnotes in float captions '
|
||||||
|
@ -940,7 +940,9 @@ class TextTranslator(nodes.NodeVisitor):
|
|||||||
|
|
||||||
def visit_raw(self, node):
|
def visit_raw(self, node):
|
||||||
if 'text' in node.get('format', '').split():
|
if 'text' in node.get('format', '').split():
|
||||||
self.body.append(node.astext())
|
self.new_state(0)
|
||||||
|
self.add_text(node.astext())
|
||||||
|
self.end_state(wrap = False)
|
||||||
raise nodes.SkipNode
|
raise nodes.SkipNode
|
||||||
|
|
||||||
def visit_math(self, node):
|
def visit_math(self, node):
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import shutil
|
import shutil
|
||||||
from codecs import open
|
from io import open
|
||||||
|
|
||||||
from six import PY2, text_type
|
from six import PY2, text_type
|
||||||
|
|
||||||
@ -126,35 +126,31 @@ class path(text_type):
|
|||||||
def utime(self, arg):
|
def utime(self, arg):
|
||||||
os.utime(self, arg)
|
os.utime(self, arg)
|
||||||
|
|
||||||
def write_text(self, text, **kwargs):
|
def open(self, mode='r', **kwargs):
|
||||||
|
return open(self, mode, **kwargs)
|
||||||
|
|
||||||
|
def write_text(self, text, encoding='utf-8', **kwargs):
|
||||||
"""
|
"""
|
||||||
Writes the given `text` to the file.
|
Writes the given `text` to the file.
|
||||||
"""
|
"""
|
||||||
f = open(self, 'w', **kwargs)
|
if isinstance(text, bytes):
|
||||||
try:
|
text = text.decode(encoding)
|
||||||
|
with open(self, 'w', encoding=encoding, **kwargs) as f:
|
||||||
f.write(text)
|
f.write(text)
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
def text(self, **kwargs):
|
def text(self, encoding='utf-8', **kwargs):
|
||||||
"""
|
"""
|
||||||
Returns the text in the file.
|
Returns the text in the file.
|
||||||
"""
|
"""
|
||||||
f = open(self, mode='U', **kwargs)
|
with open(self, mode='U', encoding=encoding, **kwargs) as f:
|
||||||
try:
|
|
||||||
return f.read()
|
return f.read()
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
def bytes(self):
|
def bytes(self):
|
||||||
"""
|
"""
|
||||||
Returns the bytes in the file.
|
Returns the bytes in the file.
|
||||||
"""
|
"""
|
||||||
f = open(self, mode='rb')
|
with open(self, mode='rb') as f:
|
||||||
try:
|
|
||||||
return f.read()
|
return f.read()
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
def write_bytes(self, bytes, append=False):
|
def write_bytes(self, bytes, append=False):
|
||||||
"""
|
"""
|
||||||
@ -167,11 +163,8 @@ class path(text_type):
|
|||||||
mode = 'ab'
|
mode = 'ab'
|
||||||
else:
|
else:
|
||||||
mode = 'wb'
|
mode = 'wb'
|
||||||
f = open(self, mode=mode)
|
with open(self, mode=mode) as f:
|
||||||
try:
|
|
||||||
f.write(bytes)
|
f.write(bytes)
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
def exists(self):
|
def exists(self):
|
||||||
"""
|
"""
|
||||||
|
45
tests/roots/test-footnotes/index.rst
Normal file
45
tests/roots/test-footnotes/index.rst
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
===============
|
||||||
|
test-footenotes
|
||||||
|
===============
|
||||||
|
|
||||||
|
The section with a reference to [AuthorYear]_
|
||||||
|
=============================================
|
||||||
|
|
||||||
|
.. figure:: rimg.png
|
||||||
|
|
||||||
|
This is the figure caption with a reference to [AuthorYear]_.
|
||||||
|
|
||||||
|
.. list-table:: The table title with a reference to [AuthorYear]_
|
||||||
|
:header-rows: 1
|
||||||
|
|
||||||
|
* - Header1
|
||||||
|
- Header2
|
||||||
|
* - Content
|
||||||
|
- Content
|
||||||
|
|
||||||
|
.. rubric:: The rubric title with a reference to [AuthorYear]_
|
||||||
|
|
||||||
|
.. [#] First
|
||||||
|
|
||||||
|
* First footnote: [#]_
|
||||||
|
* Second footnote: [1]_
|
||||||
|
* `Sphinx <http://sphinx-doc.org/>`_
|
||||||
|
* Third footnote: [#]_
|
||||||
|
* `URL including tilde <http://sphinx-doc.org/~test/>`_
|
||||||
|
* GitHub Page: `https://github.com/sphinx-doc/sphinx <https://github.com/sphinx-doc/sphinx>`_
|
||||||
|
* Mailing list: `sphinx-dev@googlegroups.com <mailto:sphinx-dev@googlegroups.com>`_
|
||||||
|
|
||||||
|
.. [AuthorYear] Author, Title, Year
|
||||||
|
.. [1] Second
|
||||||
|
.. [#] Third
|
||||||
|
|
||||||
|
`URL in term <http://sphinx-doc.org/>`_
|
||||||
|
Description Description Description ...
|
||||||
|
|
||||||
|
Footnote in term [#]_
|
||||||
|
Description Description Description ...
|
||||||
|
|
||||||
|
`Term in deflist <http://sphinx-doc.org/>`_
|
||||||
|
Description2
|
||||||
|
|
||||||
|
.. [#] Footnote in term
|
Before Width: | Height: | Size: 218 B After Width: | Height: | Size: 218 B |
@ -1,22 +0,0 @@
|
|||||||
==============
|
|
||||||
test-reference
|
|
||||||
==============
|
|
||||||
|
|
||||||
The section with a reference to [AuthorYear]_
|
|
||||||
=============================================
|
|
||||||
|
|
||||||
.. figure:: rimg.png
|
|
||||||
|
|
||||||
This is the figure caption with a reference to [AuthorYear]_.
|
|
||||||
|
|
||||||
.. list-table:: The table title with a reference to [AuthorYear]_
|
|
||||||
:header-rows: 1
|
|
||||||
|
|
||||||
* - Header1
|
|
||||||
- Header2
|
|
||||||
* - Content
|
|
||||||
- Content
|
|
||||||
|
|
||||||
.. rubric:: The rubric title with a reference to [AuthorYear]_
|
|
||||||
|
|
||||||
.. [AuthorYear] Author, Title, Year
|
|
@ -58,12 +58,12 @@ def test_build_all():
|
|||||||
"""))
|
"""))
|
||||||
|
|
||||||
master_doc = srcdir / 'contents.txt'
|
master_doc = srcdir / 'contents.txt'
|
||||||
master_doc.write_bytes((master_doc.text() + dedent("""
|
master_doc.write_text(master_doc.text() + dedent(u"""
|
||||||
.. toctree::
|
.. toctree::
|
||||||
|
|
||||||
%(test_name)s/%(test_name)s
|
%(test_name)s/%(test_name)s
|
||||||
""" % {'test_name': test_name})
|
""" % {'test_name': test_name})
|
||||||
).encode('utf-8'))
|
)
|
||||||
|
|
||||||
# note: no 'html' - if it's ok with dirhtml it's ok with html
|
# note: no 'html' - if it's ok with dirhtml it's ok with html
|
||||||
for buildername in ['dirhtml', 'singlehtml', 'latex', 'texinfo', 'pickle',
|
for buildername in ['dirhtml', 'singlehtml', 'latex', 'texinfo', 'pickle',
|
||||||
|
@ -376,11 +376,8 @@ def test_html_output(app, status, warning):
|
|||||||
for fname, paths in iteritems(HTML_XPATH):
|
for fname, paths in iteritems(HTML_XPATH):
|
||||||
parser = NslessParser()
|
parser = NslessParser()
|
||||||
parser.entity.update(html_entities.entitydefs)
|
parser.entity.update(html_entities.entitydefs)
|
||||||
fp = open(os.path.join(app.outdir, fname), 'rb')
|
with (app.outdir / fname).open('rb') as fp:
|
||||||
try:
|
|
||||||
etree = ET.parse(fp, parser)
|
etree = ET.parse(fp, parser)
|
||||||
finally:
|
|
||||||
fp.close()
|
|
||||||
for path, check in paths:
|
for path, check in paths:
|
||||||
yield check_xpath, etree, fname, path, check
|
yield check_xpath, etree, fname, path, check
|
||||||
|
|
||||||
@ -429,11 +426,8 @@ def test_tocdepth(app, status, warning):
|
|||||||
for fname, paths in iteritems(expects):
|
for fname, paths in iteritems(expects):
|
||||||
parser = NslessParser()
|
parser = NslessParser()
|
||||||
parser.entity.update(html_entities.entitydefs)
|
parser.entity.update(html_entities.entitydefs)
|
||||||
fp = open(os.path.join(app.outdir, fname), 'rb')
|
with (app.outdir / fname).open('rb') as fp:
|
||||||
try:
|
|
||||||
etree = ET.parse(fp, parser)
|
etree = ET.parse(fp, parser)
|
||||||
finally:
|
|
||||||
fp.close()
|
|
||||||
|
|
||||||
for xpath, check, be_found in paths:
|
for xpath, check, be_found in paths:
|
||||||
yield check_xpath, etree, fname, xpath, check, be_found
|
yield check_xpath, etree, fname, xpath, check, be_found
|
||||||
@ -474,11 +468,8 @@ def test_tocdepth_singlehtml(app, status, warning):
|
|||||||
for fname, paths in iteritems(expects):
|
for fname, paths in iteritems(expects):
|
||||||
parser = NslessParser()
|
parser = NslessParser()
|
||||||
parser.entity.update(html_entities.entitydefs)
|
parser.entity.update(html_entities.entitydefs)
|
||||||
fp = open(os.path.join(app.outdir, fname), 'rb')
|
with (app.outdir / fname).open('rb') as fp:
|
||||||
try:
|
|
||||||
etree = ET.parse(fp, parser)
|
etree = ET.parse(fp, parser)
|
||||||
finally:
|
|
||||||
fp.close()
|
|
||||||
|
|
||||||
for xpath, check, be_found in paths:
|
for xpath, check, be_found in paths:
|
||||||
yield check_xpath, etree, fname, xpath, check, be_found
|
yield check_xpath, etree, fname, xpath, check, be_found
|
||||||
@ -531,11 +522,8 @@ def test_numfig_disabled(app, status, warning):
|
|||||||
for fname, paths in iteritems(expects):
|
for fname, paths in iteritems(expects):
|
||||||
parser = NslessParser()
|
parser = NslessParser()
|
||||||
parser.entity.update(html_entities.entitydefs)
|
parser.entity.update(html_entities.entitydefs)
|
||||||
fp = open(os.path.join(app.outdir, fname), 'rb')
|
with (app.outdir / fname).open('rb') as fp:
|
||||||
try:
|
|
||||||
etree = ET.parse(fp, parser)
|
etree = ET.parse(fp, parser)
|
||||||
finally:
|
|
||||||
fp.close()
|
|
||||||
|
|
||||||
for xpath, check, be_found in paths:
|
for xpath, check, be_found in paths:
|
||||||
yield check_xpath, etree, fname, xpath, check, be_found
|
yield check_xpath, etree, fname, xpath, check, be_found
|
||||||
@ -633,11 +621,8 @@ def test_numfig_without_numbered_toctree(app, status, warning):
|
|||||||
for fname, paths in iteritems(expects):
|
for fname, paths in iteritems(expects):
|
||||||
parser = NslessParser()
|
parser = NslessParser()
|
||||||
parser.entity.update(html_entities.entitydefs)
|
parser.entity.update(html_entities.entitydefs)
|
||||||
fp = open(os.path.join(app.outdir, fname), 'rb')
|
with (app.outdir / fname).open('rb') as fp:
|
||||||
try:
|
|
||||||
etree = ET.parse(fp, parser)
|
etree = ET.parse(fp, parser)
|
||||||
finally:
|
|
||||||
fp.close()
|
|
||||||
|
|
||||||
for xpath, check, be_found in paths:
|
for xpath, check, be_found in paths:
|
||||||
yield check_xpath, etree, fname, xpath, check, be_found
|
yield check_xpath, etree, fname, xpath, check, be_found
|
||||||
@ -731,11 +716,8 @@ def test_numfig_with_numbered_toctree(app, status, warning):
|
|||||||
for fname, paths in iteritems(expects):
|
for fname, paths in iteritems(expects):
|
||||||
parser = NslessParser()
|
parser = NslessParser()
|
||||||
parser.entity.update(html_entities.entitydefs)
|
parser.entity.update(html_entities.entitydefs)
|
||||||
fp = open(os.path.join(app.outdir, fname), 'rb')
|
with (app.outdir / fname).open('rb') as fp:
|
||||||
try:
|
|
||||||
etree = ET.parse(fp, parser)
|
etree = ET.parse(fp, parser)
|
||||||
finally:
|
|
||||||
fp.close()
|
|
||||||
|
|
||||||
for xpath, check, be_found in paths:
|
for xpath, check, be_found in paths:
|
||||||
yield check_xpath, etree, fname, xpath, check, be_found
|
yield check_xpath, etree, fname, xpath, check, be_found
|
||||||
@ -832,11 +814,8 @@ def test_numfig_with_prefix(app, status, warning):
|
|||||||
for fname, paths in iteritems(expects):
|
for fname, paths in iteritems(expects):
|
||||||
parser = NslessParser()
|
parser = NslessParser()
|
||||||
parser.entity.update(html_entities.entitydefs)
|
parser.entity.update(html_entities.entitydefs)
|
||||||
fp = open(os.path.join(app.outdir, fname), 'rb')
|
with (app.outdir / fname).open('rb') as fp:
|
||||||
try:
|
|
||||||
etree = ET.parse(fp, parser)
|
etree = ET.parse(fp, parser)
|
||||||
finally:
|
|
||||||
fp.close()
|
|
||||||
|
|
||||||
for xpath, check, be_found in paths:
|
for xpath, check, be_found in paths:
|
||||||
yield check_xpath, etree, fname, xpath, check, be_found
|
yield check_xpath, etree, fname, xpath, check, be_found
|
||||||
@ -930,11 +909,8 @@ def test_numfig_with_secnum_depth(app, status, warning):
|
|||||||
for fname, paths in iteritems(expects):
|
for fname, paths in iteritems(expects):
|
||||||
parser = NslessParser()
|
parser = NslessParser()
|
||||||
parser.entity.update(html_entities.entitydefs)
|
parser.entity.update(html_entities.entitydefs)
|
||||||
fp = open(os.path.join(app.outdir, fname), 'rb')
|
with (app.outdir / fname).open('rb') as fp:
|
||||||
try:
|
|
||||||
etree = ET.parse(fp, parser)
|
etree = ET.parse(fp, parser)
|
||||||
finally:
|
|
||||||
fp.close()
|
|
||||||
|
|
||||||
for xpath, check, be_found in paths:
|
for xpath, check, be_found in paths:
|
||||||
yield check_xpath, etree, fname, xpath, check, be_found
|
yield check_xpath, etree, fname, xpath, check, be_found
|
||||||
|
@ -320,7 +320,7 @@ def test_footnote(app, status, warning):
|
|||||||
'\\footnotetext[5]{\nfootnotes in table\n}' in result)
|
'\\footnotetext[5]{\nfootnotes in table\n}' in result)
|
||||||
|
|
||||||
|
|
||||||
@with_app(buildername='latex', testroot='references-in-caption')
|
@with_app(buildername='latex', testroot='footnotes')
|
||||||
def test_reference_in_caption(app, status, warning):
|
def test_reference_in_caption(app, status, warning):
|
||||||
app.builder.build_all()
|
app.builder.build_all()
|
||||||
result = (app.outdir / 'Python.tex').text(encoding='utf8')
|
result = (app.outdir / 'Python.tex').text(encoding='utf8')
|
||||||
@ -332,3 +332,81 @@ def test_reference_in_caption(app, status, warning):
|
|||||||
assert '\\chapter{The section with a reference to {[}AuthorYear{]}}' in result
|
assert '\\chapter{The section with a reference to {[}AuthorYear{]}}' in result
|
||||||
assert '\\caption{The table title with a reference to {[}AuthorYear{]}}' in result
|
assert '\\caption{The table title with a reference to {[}AuthorYear{]}}' in result
|
||||||
assert '\\paragraph{The rubric title with a reference to {[}AuthorYear{]}}' in result
|
assert '\\paragraph{The rubric title with a reference to {[}AuthorYear{]}}' in result
|
||||||
|
|
||||||
|
|
||||||
|
@with_app(buildername='latex', testroot='footnotes',
|
||||||
|
confoverrides={'latex_show_urls': 'inline'})
|
||||||
|
def test_latex_show_urls_is_inline(app, status, warning):
|
||||||
|
app.builder.build_all()
|
||||||
|
result = (app.outdir / 'Python.tex').text(encoding='utf8')
|
||||||
|
print(result)
|
||||||
|
print(status.getvalue())
|
||||||
|
print(warning.getvalue())
|
||||||
|
assert 'First footnote: \\footnote[2]{\nFirst\n}' in result
|
||||||
|
assert 'Second footnote: \\footnote[1]{\nSecond\n}' in result
|
||||||
|
assert '\\href{http://sphinx-doc.org/}{Sphinx} (http://sphinx-doc.org/)' in result
|
||||||
|
assert 'Third footnote: \\footnote[3]{\nThird\n}' in result
|
||||||
|
assert ('\\href{http://sphinx-doc.org/~test/}{URL including tilde} '
|
||||||
|
'(http://sphinx-doc.org/\\textasciitilde{}test/)' in result)
|
||||||
|
assert ('\\item[{\\href{http://sphinx-doc.org/}{URL in term} (http://sphinx-doc.org/)}] '
|
||||||
|
'\\leavevmode\nDescription' in result)
|
||||||
|
assert ('\\item[{Footnote in term \\footnotemark[4]}] '
|
||||||
|
'\\leavevmode\\footnotetext[4]{\nFootnote in term\n}\nDescription' in result)
|
||||||
|
assert ('\\item[{\\href{http://sphinx-doc.org/}{Term in deflist} '
|
||||||
|
'(http://sphinx-doc.org/)}] \\leavevmode\nDescription' in result)
|
||||||
|
assert ('\\href{https://github.com/sphinx-doc/sphinx}'
|
||||||
|
'{https://github.com/sphinx-doc/sphinx}\n' in result)
|
||||||
|
assert ('\\href{mailto:sphinx-dev@googlegroups.com}'
|
||||||
|
'{sphinx-dev@googlegroups.com}' in result)
|
||||||
|
|
||||||
|
|
||||||
|
@with_app(buildername='latex', testroot='footnotes',
|
||||||
|
confoverrides={'latex_show_urls': 'footnote'})
|
||||||
|
def test_latex_show_urls_is_footnote(app, status, warning):
|
||||||
|
app.builder.build_all()
|
||||||
|
result = (app.outdir / 'Python.tex').text(encoding='utf8')
|
||||||
|
print(result)
|
||||||
|
print(status.getvalue())
|
||||||
|
print(warning.getvalue())
|
||||||
|
assert 'First footnote: \\footnote[2]{\nFirst\n}' in result
|
||||||
|
assert 'Second footnote: \\footnote[1]{\nSecond\n}' in result
|
||||||
|
assert ('\\href{http://sphinx-doc.org/}{Sphinx}'
|
||||||
|
'\\footnote[3]{\nhttp://sphinx-doc.org/\n}' in result)
|
||||||
|
assert 'Third footnote: \\footnote[5]{\nThird\n}' in result
|
||||||
|
assert ('\\href{http://sphinx-doc.org/~test/}{URL including tilde}'
|
||||||
|
'\\footnote[4]{\nhttp://sphinx-doc.org/\\textasciitilde{}test/\n}' in result)
|
||||||
|
assert ('\\item[{\\href{http://sphinx-doc.org/}{URL in term}\\footnotemark[6]}] '
|
||||||
|
'\\leavevmode\\footnotetext[6]{\nhttp://sphinx-doc.org/\n}\nDescription' in result)
|
||||||
|
assert ('\\item[{Footnote in term \\footnotemark[8]}] '
|
||||||
|
'\\leavevmode\\footnotetext[8]{\nFootnote in term\n}\nDescription' in result)
|
||||||
|
assert ('\\item[{\\href{http://sphinx-doc.org/}{Term in deflist}\\footnotemark[7]}] '
|
||||||
|
'\\leavevmode\\footnotetext[7]{\nhttp://sphinx-doc.org/\n}\nDescription' in result)
|
||||||
|
assert ('\\href{https://github.com/sphinx-doc/sphinx}'
|
||||||
|
'{https://github.com/sphinx-doc/sphinx}\n' in result)
|
||||||
|
assert ('\\href{mailto:sphinx-dev@googlegroups.com}'
|
||||||
|
'{sphinx-dev@googlegroups.com}\n' in result)
|
||||||
|
|
||||||
|
|
||||||
|
@with_app(buildername='latex', testroot='footnotes',
|
||||||
|
confoverrides={'latex_show_urls': 'no'})
|
||||||
|
def test_latex_show_urls_is_no(app, status, warning):
|
||||||
|
app.builder.build_all()
|
||||||
|
result = (app.outdir / 'Python.tex').text(encoding='utf8')
|
||||||
|
print(result)
|
||||||
|
print(status.getvalue())
|
||||||
|
print(warning.getvalue())
|
||||||
|
assert 'First footnote: \\footnote[2]{\nFirst\n}' in result
|
||||||
|
assert 'Second footnote: \\footnote[1]{\nSecond\n}' in result
|
||||||
|
assert '\\href{http://sphinx-doc.org/}{Sphinx}' in result
|
||||||
|
assert 'Third footnote: \\footnote[3]{\nThird\n}' in result
|
||||||
|
assert '\\href{http://sphinx-doc.org/~test/}{URL including tilde}' in result
|
||||||
|
assert ('\\item[{\\href{http://sphinx-doc.org/}{URL in term}}] '
|
||||||
|
'\\leavevmode\nDescription' in result)
|
||||||
|
assert ('\\item[{Footnote in term \\footnotemark[4]}] '
|
||||||
|
'\\leavevmode\\footnotetext[4]{\nFootnote in term\n}\nDescription' in result)
|
||||||
|
assert ('\\item[{\\href{http://sphinx-doc.org/}{Term in deflist}}] '
|
||||||
|
'\\leavevmode\nDescription' in result)
|
||||||
|
assert ('\\href{https://github.com/sphinx-doc/sphinx}'
|
||||||
|
'{https://github.com/sphinx-doc/sphinx}\n' in result)
|
||||||
|
assert ('\\href{mailto:sphinx-dev@googlegroups.com}'
|
||||||
|
'{sphinx-dev@googlegroups.com}\n' in result)
|
||||||
|
25
tests/test_util.py
Normal file
25
tests/test_util.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
test_util
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests util functions.
|
||||||
|
|
||||||
|
:copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS.
|
||||||
|
:license: BSD, see LICENSE for details.
|
||||||
|
"""
|
||||||
|
from sphinx.util import encode_uri
|
||||||
|
|
||||||
|
|
||||||
|
def test_encode_uri():
|
||||||
|
expected = (u'https://ru.wikipedia.org/wiki/%D0%A1%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0_'
|
||||||
|
u'%D1%83%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F_'
|
||||||
|
u'%D0%B1%D0%B0%D0%B7%D0%B0%D0%BC%D0%B8_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85')
|
||||||
|
uri = u'https://ru.wikipedia.org/wiki/Система_управления_базами_данных'
|
||||||
|
assert expected, encode_uri(uri)
|
||||||
|
|
||||||
|
expected = (u'https://github.com/search?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+is%3A'
|
||||||
|
u'sprint-friendly+user%3Ajupyter&type=Issues&ref=searchresults')
|
||||||
|
uri = (u'https://github.com/search?utf8=✓&q=is%3Aissue+is%3Aopen+is%3A'
|
||||||
|
u'sprint-friendly+user%3Ajupyter&type=Issues&ref=searchresults')
|
||||||
|
assert expected, encode_uri(uri)
|
Loading…
Reference in New Issue
Block a user