mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch 'stable'
This commit is contained in:
commit
1cfde018d2
3
CHANGES
3
CHANGES
@ -129,6 +129,9 @@ Bugs fixed
|
|||||||
* #2775: Fix failing linkcheck with servers not supporting identidy encoding
|
* #2775: Fix failing linkcheck with servers not supporting identidy encoding
|
||||||
* #2833: Fix formatting instance annotations in ext.autodoc.
|
* #2833: Fix formatting instance annotations in ext.autodoc.
|
||||||
* #1911: ``-D`` option of ``sphinx-build`` does not override the ``extensions`` variable
|
* #1911: ``-D`` option of ``sphinx-build`` does not override the ``extensions`` variable
|
||||||
|
* #2789: `sphinx.ext.intersphinx` generates wrong hyperlinks if the inventory is given
|
||||||
|
* parsing errors for caption of code-blocks are displayed in document (ref: #2845)
|
||||||
|
* #2846: ``singlehtml`` builder does not include figure numbers
|
||||||
|
|
||||||
Release 1.4.5 (released Jul 13, 2016)
|
Release 1.4.5 (released Jul 13, 2016)
|
||||||
=====================================
|
=====================================
|
||||||
|
@ -978,6 +978,26 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder):
|
|||||||
|
|
||||||
return {self.config.master_doc: new_secnumbers}
|
return {self.config.master_doc: new_secnumbers}
|
||||||
|
|
||||||
|
def assemble_toc_fignumbers(self):
|
||||||
|
# Assemble toc_fignumbers to resolve figure numbers on SingleHTML.
|
||||||
|
# Merge all fignumbers to single fignumber.
|
||||||
|
#
|
||||||
|
# Note: current Sphinx has refid confliction in singlehtml mode.
|
||||||
|
# To avoid the problem, it replaces key of secnumbers to
|
||||||
|
# tuple of docname and refid.
|
||||||
|
#
|
||||||
|
# There are related codes in inline_all_toctres() and
|
||||||
|
# HTMLTranslter#add_fignumber().
|
||||||
|
new_fignumbers = {}
|
||||||
|
# {u'foo': {'figure': {'id2': (2,), 'id1': (1,)}}, u'bar': {'figure': {'id1': (3,)}}}
|
||||||
|
for docname, fignumlist in iteritems(self.env.toc_fignumbers):
|
||||||
|
for figtype, fignums in iteritems(fignumlist):
|
||||||
|
new_fignumbers.setdefault((docname, figtype), {})
|
||||||
|
for id, fignum in iteritems(fignums):
|
||||||
|
new_fignumbers[(docname, figtype)][id] = fignum
|
||||||
|
|
||||||
|
return {self.config.master_doc: new_fignumbers}
|
||||||
|
|
||||||
def get_doc_context(self, docname, body, metatags):
|
def get_doc_context(self, docname, body, metatags):
|
||||||
# no relation links...
|
# no relation links...
|
||||||
toc = self.env.get_toctree_for(self.config.master_doc, self, False)
|
toc = self.env.get_toctree_for(self.config.master_doc, self, False)
|
||||||
@ -1014,6 +1034,7 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder):
|
|||||||
self.info(bold('assembling single document... '), nonl=True)
|
self.info(bold('assembling single document... '), nonl=True)
|
||||||
doctree = self.assemble_doctree()
|
doctree = self.assemble_doctree()
|
||||||
self.env.toc_secnumbers = self.assemble_toc_secnumbers()
|
self.env.toc_secnumbers = self.assemble_toc_secnumbers()
|
||||||
|
self.env.toc_fignumbers = self.assemble_toc_fignumbers()
|
||||||
self.info()
|
self.info()
|
||||||
self.info(bold('writing... '), nonl=True)
|
self.info(bold('writing... '), nonl=True)
|
||||||
self.write_doc_serialized(self.config.master_doc, doctree)
|
self.write_doc_serialized(self.config.master_doc, doctree)
|
||||||
@ -1066,6 +1087,7 @@ class SerializingHTMLBuilder(StandaloneHTMLBuilder):
|
|||||||
self.config_hash = ''
|
self.config_hash = ''
|
||||||
self.tags_hash = ''
|
self.tags_hash = ''
|
||||||
self.imagedir = '_images'
|
self.imagedir = '_images'
|
||||||
|
self.current_docname = None
|
||||||
self.theme = None # no theme necessary
|
self.theme = None # no theme necessary
|
||||||
self.templates = None # no template bridge necessary
|
self.templates = None # no template bridge necessary
|
||||||
self.init_translator_class()
|
self.init_translator_class()
|
||||||
|
@ -18,6 +18,7 @@ from docutils.statemachine import ViewList
|
|||||||
from six import string_types
|
from six import string_types
|
||||||
|
|
||||||
from sphinx import addnodes
|
from sphinx import addnodes
|
||||||
|
from sphinx.locale import _
|
||||||
from sphinx.util import parselinenos
|
from sphinx.util import parselinenos
|
||||||
from sphinx.util.nodes import set_source_info
|
from sphinx.util.nodes import set_source_info
|
||||||
|
|
||||||
@ -68,6 +69,8 @@ def container_wrapper(directive, literal_node, caption):
|
|||||||
parsed = nodes.Element()
|
parsed = nodes.Element()
|
||||||
directive.state.nested_parse(ViewList([caption], source=''),
|
directive.state.nested_parse(ViewList([caption], source=''),
|
||||||
directive.content_offset, parsed)
|
directive.content_offset, parsed)
|
||||||
|
if isinstance(parsed[0], nodes.system_message):
|
||||||
|
raise ValueError(parsed[0])
|
||||||
caption_node = nodes.caption(parsed[0].rawsource, '',
|
caption_node = nodes.caption(parsed[0].rawsource, '',
|
||||||
*parsed[0].children)
|
*parsed[0].children)
|
||||||
caption_node.source = parsed[0].source
|
caption_node.source = parsed[0].source
|
||||||
@ -131,7 +134,12 @@ class CodeBlock(Directive):
|
|||||||
caption = self.options.get('caption')
|
caption = self.options.get('caption')
|
||||||
if caption:
|
if caption:
|
||||||
self.options.setdefault('name', nodes.fully_normalize_name(caption))
|
self.options.setdefault('name', nodes.fully_normalize_name(caption))
|
||||||
literal = container_wrapper(self, literal, caption)
|
try:
|
||||||
|
literal = container_wrapper(self, literal, caption)
|
||||||
|
except ValueError as exc:
|
||||||
|
document = self.state.document
|
||||||
|
errmsg = _('Invalid caption: %s' % exc[0][0].astext())
|
||||||
|
return [document.reporter.warning(errmsg, line=self.lineno)]
|
||||||
|
|
||||||
# literal will be note_implicit_target that is linked from caption and numref.
|
# literal will be note_implicit_target that is linked from caption and numref.
|
||||||
# when options['name'] is provided, it should be primary ID.
|
# when options['name'] is provided, it should be primary ID.
|
||||||
@ -333,7 +341,12 @@ class LiteralInclude(Directive):
|
|||||||
if not caption:
|
if not caption:
|
||||||
caption = self.arguments[0]
|
caption = self.arguments[0]
|
||||||
self.options.setdefault('name', nodes.fully_normalize_name(caption))
|
self.options.setdefault('name', nodes.fully_normalize_name(caption))
|
||||||
retnode = container_wrapper(self, retnode, caption)
|
try:
|
||||||
|
retnode = container_wrapper(self, retnode, caption)
|
||||||
|
except ValueError as exc:
|
||||||
|
document = self.state.document
|
||||||
|
errmsg = _('Invalid caption: %s' % exc[0][0].astext())
|
||||||
|
return [document.reporter.warning(errmsg, line=self.lineno)]
|
||||||
|
|
||||||
# retnode will be note_implicit_target that is linked from caption and numref.
|
# retnode will be note_implicit_target that is linked from caption and numref.
|
||||||
# when options['name'] is provided, it should be primary ID.
|
# when options['name'] is provided, it should be primary ID.
|
||||||
|
@ -239,12 +239,12 @@ def fetch_inventory(app, uri, inv):
|
|||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
if hasattr(f, 'geturl'):
|
if hasattr(f, 'geturl'):
|
||||||
newuri = f.geturl()
|
newinv = f.geturl()
|
||||||
if newuri.endswith("/" + INVENTORY_FILENAME):
|
if inv != newinv:
|
||||||
newuri = newuri[:-len(INVENTORY_FILENAME) - 1]
|
app.info('intersphinx inventory has moved: %s -> %s' % (inv, newinv))
|
||||||
if uri != newuri and uri != newuri + "/":
|
|
||||||
app.info('intersphinx inventory has moved: %s -> %s' % (uri, newuri))
|
if uri in (inv, path.dirname(inv), path.dirname(inv) + '/'):
|
||||||
uri = newuri
|
uri = path.dirname(newinv)
|
||||||
line = f.readline().rstrip().decode('utf-8')
|
line = f.readline().rstrip().decode('utf-8')
|
||||||
try:
|
try:
|
||||||
if line == '# Sphinx inventory version 1':
|
if line == '# Sphinx inventory version 1':
|
||||||
|
@ -69,6 +69,7 @@ class HTMLTranslator(BaseTranslator):
|
|||||||
builder.config.highlight_language
|
builder.config.highlight_language
|
||||||
self.highlightopts = builder.config.highlight_options
|
self.highlightopts = builder.config.highlight_options
|
||||||
self.highlightlinenothreshold = sys.maxsize
|
self.highlightlinenothreshold = sys.maxsize
|
||||||
|
self.docnames = [builder.current_docname] # for singlehtml builder
|
||||||
self.protect_literal_text = 0
|
self.protect_literal_text = 0
|
||||||
self.permalink_text = builder.config.html_add_permalinks
|
self.permalink_text = builder.config.html_add_permalinks
|
||||||
# support backwards-compatible setting to a bool
|
# support backwards-compatible setting to a bool
|
||||||
@ -82,10 +83,11 @@ class HTMLTranslator(BaseTranslator):
|
|||||||
|
|
||||||
def visit_start_of_file(self, node):
|
def visit_start_of_file(self, node):
|
||||||
# only occurs in the single-file builder
|
# only occurs in the single-file builder
|
||||||
|
self.docnames.append(node['docname'])
|
||||||
self.body.append('<span id="document-%s"></span>' % node['docname'])
|
self.body.append('<span id="document-%s"></span>' % node['docname'])
|
||||||
|
|
||||||
def depart_start_of_file(self, node):
|
def depart_start_of_file(self, node):
|
||||||
pass
|
self.docnames.pop()
|
||||||
|
|
||||||
def visit_desc(self, node):
|
def visit_desc(self, node):
|
||||||
self.body.append(self.starttag(node, 'dl', CLASS=node['objtype']))
|
self.body.append(self.starttag(node, 'dl', CLASS=node['objtype']))
|
||||||
@ -247,7 +249,7 @@ class HTMLTranslator(BaseTranslator):
|
|||||||
self.secnumber_suffix)
|
self.secnumber_suffix)
|
||||||
elif isinstance(node.parent, nodes.section):
|
elif isinstance(node.parent, nodes.section):
|
||||||
if self.builder.name == 'singlehtml':
|
if self.builder.name == 'singlehtml':
|
||||||
docname = node.parent.get('docname')
|
docname = self.docnames[-1]
|
||||||
anchorname = '#' + node.parent['ids'][0]
|
anchorname = '#' + node.parent['ids'][0]
|
||||||
if (docname, anchorname) not in self.builder.secnumbers:
|
if (docname, anchorname) not in self.builder.secnumbers:
|
||||||
anchorname = (docname, '') # try first heading which has no anchor
|
anchorname = (docname, '') # try first heading which has no anchor
|
||||||
@ -264,14 +266,19 @@ class HTMLTranslator(BaseTranslator):
|
|||||||
|
|
||||||
def add_fignumber(self, node):
|
def add_fignumber(self, node):
|
||||||
def append_fignumber(figtype, figure_id):
|
def append_fignumber(figtype, figure_id):
|
||||||
if figure_id in self.builder.fignumbers.get(figtype, {}):
|
if self.builder.name == 'singlehtml':
|
||||||
|
key = (self.docnames[-1], figtype)
|
||||||
|
else:
|
||||||
|
key = figtype
|
||||||
|
|
||||||
|
if figure_id in self.builder.fignumbers.get(key, {}):
|
||||||
self.body.append('<span class="caption-number">')
|
self.body.append('<span class="caption-number">')
|
||||||
prefix = self.builder.config.numfig_format.get(figtype)
|
prefix = self.builder.config.numfig_format.get(figtype)
|
||||||
if prefix is None:
|
if prefix is None:
|
||||||
msg = 'numfig_format is not defined for %s' % figtype
|
msg = 'numfig_format is not defined for %s' % figtype
|
||||||
self.builder.warn(msg)
|
self.builder.warn(msg)
|
||||||
else:
|
else:
|
||||||
numbers = self.builder.fignumbers[figtype][figure_id]
|
numbers = self.builder.fignumbers[key][figure_id]
|
||||||
self.body.append(prefix % '.'.join(map(str, numbers)) + ' ')
|
self.body.append(prefix % '.'.join(map(str, numbers)) + ' ')
|
||||||
self.body.append('</span>')
|
self.body.append('</span>')
|
||||||
|
|
||||||
|
@ -548,7 +548,7 @@ def test_numfig_disabled(app, status, warning):
|
|||||||
yield check_xpath, etree, fname, xpath, check, be_found
|
yield check_xpath, etree, fname, xpath, check, be_found
|
||||||
|
|
||||||
|
|
||||||
@gen_with_app(buildername='html', testroot='numfig',
|
@gen_with_app(buildername='html', testroot='numfig', freshenv=True,
|
||||||
confoverrides={'numfig': True})
|
confoverrides={'numfig': True})
|
||||||
def test_numfig_without_numbered_toctree(app, status, warning):
|
def test_numfig_without_numbered_toctree(app, status, warning):
|
||||||
# remove :numbered: option
|
# remove :numbered: option
|
||||||
@ -935,6 +935,92 @@ def test_numfig_with_secnum_depth(app, status, warning):
|
|||||||
yield check_xpath, etree, fname, xpath, check, be_found
|
yield check_xpath, etree, fname, xpath, check, be_found
|
||||||
|
|
||||||
|
|
||||||
|
@gen_with_app(buildername='singlehtml', testroot='numfig',
|
||||||
|
confoverrides={'numfig': True})
|
||||||
|
def test_numfig_with_singlehtml(app, status, warning):
|
||||||
|
app.builder.build_all()
|
||||||
|
|
||||||
|
expects = {
|
||||||
|
'index.html': [
|
||||||
|
(".//div[@class='figure']/p[@class='caption']/"
|
||||||
|
"span[@class='caption-number']", '^Fig. 1 $', True),
|
||||||
|
(".//div[@class='figure']/p[@class='caption']/"
|
||||||
|
"span[@class='caption-number']", '^Fig. 2 $', True),
|
||||||
|
(".//table/caption/span[@class='caption-number']",
|
||||||
|
'^Table 1 $', True),
|
||||||
|
(".//table/caption/span[@class='caption-number']",
|
||||||
|
'^Table 2 $', True),
|
||||||
|
(".//div[@class='code-block-caption']/"
|
||||||
|
"span[@class='caption-number']", '^Listing 1 $', True),
|
||||||
|
(".//div[@class='code-block-caption']/"
|
||||||
|
"span[@class='caption-number']", '^Listing 2 $', True),
|
||||||
|
(".//li/a/span", '^Fig. 1$', True),
|
||||||
|
(".//li/a/span", '^Figure2.2$', True),
|
||||||
|
(".//li/a/span", '^Table 1$', True),
|
||||||
|
(".//li/a/span", '^Table:2.2$', True),
|
||||||
|
(".//li/a/span", '^Listing 1$', True),
|
||||||
|
(".//li/a/span", '^Code-2.2$', True),
|
||||||
|
(".//div[@class='figure']/p[@class='caption']/"
|
||||||
|
"span[@class='caption-number']", '^Fig. 1.1 $', True),
|
||||||
|
(".//div[@class='figure']/p[@class='caption']/"
|
||||||
|
"span[@class='caption-number']", '^Fig. 1.2 $', True),
|
||||||
|
(".//div[@class='figure']/p[@class='caption']/"
|
||||||
|
"span[@class='caption-number']", '^Fig. 1.3 $', True),
|
||||||
|
(".//div[@class='figure']/p[@class='caption']/"
|
||||||
|
"span[@class='caption-number']", '^Fig. 1.4 $', True),
|
||||||
|
(".//table/caption/span[@class='caption-number']",
|
||||||
|
'^Table 1.1 $', True),
|
||||||
|
(".//table/caption/span[@class='caption-number']",
|
||||||
|
'^Table 1.2 $', True),
|
||||||
|
(".//table/caption/span[@class='caption-number']",
|
||||||
|
'^Table 1.3 $', True),
|
||||||
|
(".//table/caption/span[@class='caption-number']",
|
||||||
|
'^Table 1.4 $', True),
|
||||||
|
(".//div[@class='code-block-caption']/"
|
||||||
|
"span[@class='caption-number']", '^Listing 1.1 $', True),
|
||||||
|
(".//div[@class='code-block-caption']/"
|
||||||
|
"span[@class='caption-number']", '^Listing 1.2 $', True),
|
||||||
|
(".//div[@class='code-block-caption']/"
|
||||||
|
"span[@class='caption-number']", '^Listing 1.3 $', True),
|
||||||
|
(".//div[@class='code-block-caption']/"
|
||||||
|
"span[@class='caption-number']", '^Listing 1.4 $', True),
|
||||||
|
(".//div[@class='figure']/p[@class='caption']/"
|
||||||
|
"span[@class='caption-number']", '^Fig. 2.1 $', True),
|
||||||
|
(".//div[@class='figure']/p[@class='caption']/"
|
||||||
|
"span[@class='caption-number']", '^Fig. 2.3 $', True),
|
||||||
|
(".//div[@class='figure']/p[@class='caption']/"
|
||||||
|
"span[@class='caption-number']", '^Fig. 2.4 $', True),
|
||||||
|
(".//table/caption/span[@class='caption-number']",
|
||||||
|
'^Table 2.1 $', True),
|
||||||
|
(".//table/caption/span[@class='caption-number']",
|
||||||
|
'^Table 2.3 $', True),
|
||||||
|
(".//table/caption/span[@class='caption-number']",
|
||||||
|
'^Table 2.4 $', True),
|
||||||
|
(".//div[@class='code-block-caption']/"
|
||||||
|
"span[@class='caption-number']", '^Listing 2.1 $', True),
|
||||||
|
(".//div[@class='code-block-caption']/"
|
||||||
|
"span[@class='caption-number']", '^Listing 2.3 $', True),
|
||||||
|
(".//div[@class='code-block-caption']/"
|
||||||
|
"span[@class='caption-number']", '^Listing 2.4 $', True),
|
||||||
|
(".//div[@class='figure']/p[@class='caption']/"
|
||||||
|
"span[@class='caption-number']", '^Fig. 2.2 $', True),
|
||||||
|
(".//table/caption/span[@class='caption-number']",
|
||||||
|
'^Table 2.2 $', True),
|
||||||
|
(".//div[@class='code-block-caption']/"
|
||||||
|
"span[@class='caption-number']", '^Listing 2.2 $', True),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
for fname, paths in iteritems(expects):
|
||||||
|
parser = NslessParser()
|
||||||
|
parser.entity.update(html_entities.entitydefs)
|
||||||
|
with (app.outdir / fname).open('rb') as fp:
|
||||||
|
etree = ET.parse(fp, parser)
|
||||||
|
|
||||||
|
for xpath, check, be_found in paths:
|
||||||
|
yield check_xpath, etree, fname, xpath, check, be_found
|
||||||
|
|
||||||
|
|
||||||
@gen_with_app(buildername='html', testroot='add_enumerable_node')
|
@gen_with_app(buildername='html', testroot='add_enumerable_node')
|
||||||
def test_enumerable_node(app, status, warning):
|
def test_enumerable_node(app, status, warning):
|
||||||
app.builder.build_all()
|
app.builder.build_all()
|
||||||
|
@ -19,7 +19,7 @@ from docutils import nodes
|
|||||||
from sphinx import addnodes
|
from sphinx import addnodes
|
||||||
from sphinx.ext.intersphinx import read_inventory_v1, read_inventory_v2, \
|
from sphinx.ext.intersphinx import read_inventory_v1, read_inventory_v2, \
|
||||||
load_mappings, missing_reference, _strip_basic_auth, _read_from_url, \
|
load_mappings, missing_reference, _strip_basic_auth, _read_from_url, \
|
||||||
_get_safe_url
|
_get_safe_url, fetch_inventory, INVENTORY_FILENAME
|
||||||
|
|
||||||
from util import with_app, with_tempdir, mock
|
from util import with_app, with_tempdir, mock
|
||||||
|
|
||||||
@ -83,6 +83,50 @@ def test_read_inventory_v2():
|
|||||||
'/util/glossary.html#term-a-term-including-colon'
|
'/util/glossary.html#term-a-term-including-colon'
|
||||||
|
|
||||||
|
|
||||||
|
@with_app()
|
||||||
|
@mock.patch('sphinx.ext.intersphinx.read_inventory_v2')
|
||||||
|
@mock.patch('sphinx.ext.intersphinx._read_from_url')
|
||||||
|
def test_fetch_inventory_redirection(app, status, warning, _read_from_url, read_inventory_v2):
|
||||||
|
_read_from_url().readline.return_value = '# Sphinx inventory version 2'.encode('utf-8')
|
||||||
|
|
||||||
|
# same uri and inv, not redirected
|
||||||
|
_read_from_url().geturl.return_value = 'http://hostname/' + INVENTORY_FILENAME
|
||||||
|
fetch_inventory(app, 'http://hostname/', 'http://hostname/' + INVENTORY_FILENAME)
|
||||||
|
assert 'intersphinx inventory has moved' not in status.getvalue()
|
||||||
|
assert read_inventory_v2.call_args[0][1] == 'http://hostname/'
|
||||||
|
|
||||||
|
# same uri and inv, redirected
|
||||||
|
status.seek(0)
|
||||||
|
status.truncate(0)
|
||||||
|
_read_from_url().geturl.return_value = 'http://hostname/new/' + INVENTORY_FILENAME
|
||||||
|
|
||||||
|
fetch_inventory(app, 'http://hostname/', 'http://hostname/' + INVENTORY_FILENAME)
|
||||||
|
assert status.getvalue() == ('intersphinx inventory has moved: '
|
||||||
|
'http://hostname/%s -> http://hostname/new/%s\n' %
|
||||||
|
(INVENTORY_FILENAME, INVENTORY_FILENAME))
|
||||||
|
assert read_inventory_v2.call_args[0][1] == 'http://hostname/new'
|
||||||
|
|
||||||
|
# different uri and inv, not redirected
|
||||||
|
status.seek(0)
|
||||||
|
status.truncate(0)
|
||||||
|
_read_from_url().geturl.return_value = 'http://hostname/new/' + INVENTORY_FILENAME
|
||||||
|
|
||||||
|
fetch_inventory(app, 'http://hostname/', 'http://hostname/new/' + INVENTORY_FILENAME)
|
||||||
|
assert 'intersphinx inventory has moved' not in status.getvalue()
|
||||||
|
assert read_inventory_v2.call_args[0][1] == 'http://hostname/'
|
||||||
|
|
||||||
|
# different uri and inv, redirected
|
||||||
|
status.seek(0)
|
||||||
|
status.truncate(0)
|
||||||
|
_read_from_url().geturl.return_value = 'http://hostname/other/' + INVENTORY_FILENAME
|
||||||
|
|
||||||
|
fetch_inventory(app, 'http://hostname/', 'http://hostname/new/' + INVENTORY_FILENAME)
|
||||||
|
assert status.getvalue() == ('intersphinx inventory has moved: '
|
||||||
|
'http://hostname/new/%s -> http://hostname/other/%s\n' %
|
||||||
|
(INVENTORY_FILENAME, INVENTORY_FILENAME))
|
||||||
|
assert read_inventory_v2.call_args[0][1] == 'http://hostname/'
|
||||||
|
|
||||||
|
|
||||||
@with_app()
|
@with_app()
|
||||||
@with_tempdir
|
@with_tempdir
|
||||||
def test_missing_reference(tempdir, app, status, warning):
|
def test_missing_reference(tempdir, app, status, warning):
|
||||||
|
Loading…
Reference in New Issue
Block a user