mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
#413: Added a way for intersphinx to refer to named labels in other
projects, and to specify the project you want to link to.
This commit is contained in:
parent
0dd83d9ee4
commit
5b7e794e2b
5
CHANGES
5
CHANGES
@ -8,6 +8,11 @@ Release 1.0 (in development)
|
||||
|
||||
* Added a JavaScript domain.
|
||||
|
||||
* Added a reStructuredText domain.
|
||||
|
||||
* Added a way for intersphinx to refer to named labels in other
|
||||
projects, and to specify the project you want to link to.
|
||||
|
||||
* Support for docutils 0.4 has been removed.
|
||||
|
||||
* Added Croatian translation, thanks to Bojan Mihelač.
|
||||
|
@ -8,17 +8,16 @@
|
||||
|
||||
.. versionadded:: 0.5
|
||||
|
||||
This extension can generate automatic links to the documentation of Python
|
||||
objects in other projects. This works as follows:
|
||||
This extension can generate automatic links to the documentation of objects in
|
||||
other projects. This works as follows:
|
||||
|
||||
* Each Sphinx HTML build creates a file named :file:`objects.inv` that
|
||||
contains a mapping from Python identifiers to URIs relative to the HTML set's
|
||||
root.
|
||||
* Each Sphinx HTML build creates a file named :file:`objects.inv` that contains
|
||||
a mapping from object names to URIs relative to the HTML set's root.
|
||||
|
||||
* Projects using the Intersphinx extension can specify the location of such
|
||||
mapping files in the :confval:`intersphinx_mapping` config value. The mapping
|
||||
will then be used to resolve otherwise missing references to Python objects
|
||||
into links to the other documentation.
|
||||
will then be used to resolve otherwise missing references to objects into
|
||||
links to the other documentation.
|
||||
|
||||
* By default, the mapping file is assumed to be at the same location as the rest
|
||||
of the documentation; however, the location of the mapping file can also be
|
||||
@ -31,37 +30,63 @@ linking:
|
||||
|
||||
.. confval:: intersphinx_mapping
|
||||
|
||||
This config value contains the locations and names of other projects that
|
||||
should be linked to in this documentation.
|
||||
|
||||
Relative local paths for target locations are taken as relative to the base
|
||||
of the built documentation, while relative local paths for inventory
|
||||
locations are taken as relative to the source directory.
|
||||
|
||||
When fetching remote inventory files, proxy settings will be read from
|
||||
the ``$HTTP_PROXY`` environment variable.
|
||||
|
||||
**Old format for this config value**
|
||||
|
||||
This is the format used before Sphinx 1.0. It is still recognized.
|
||||
|
||||
A dictionary mapping URIs to either ``None`` or an URI. The keys are the
|
||||
base URI of the foreign Sphinx documentation sets and can be local paths or
|
||||
HTTP URIs. The values indicate where the inventory file can be found: they
|
||||
can be ``None`` (at the same location as the base URI) or another local or
|
||||
HTTP URI.
|
||||
|
||||
Relative local paths in the keys are taken as relative to the base of the
|
||||
built documentation, while relative local paths in the values are taken as
|
||||
relative to the source directory.
|
||||
**New format for this config value**
|
||||
|
||||
An example, to add links to modules and objects in the Python standard
|
||||
library documentation::
|
||||
.. versionadded:: 1.0
|
||||
|
||||
intersphinx_mapping = {'http://docs.python.org/': None}
|
||||
A dictionary mapping unique identifiers to a tuple ``(target, inventory)``.
|
||||
Each ``target`` is the base URI of a foreign Sphinx documentation set and can
|
||||
be a local path or an HTTP URI. The ``inventory`` indicates where the
|
||||
inventory file can be found: it can be ``None`` (at the same location as
|
||||
the base URI) or another local or HTTP URI.
|
||||
|
||||
The unique identifier can be used to prefix cross-reference targets, so that
|
||||
it is clear which intersphinx set the target belongs to. A link like
|
||||
``:ref:`comparison manual <python:comparisons>``` will link to the label
|
||||
"comparisons" in the doc set "python", if it exists.
|
||||
|
||||
**Example**
|
||||
|
||||
To add links to modules and objects in the Python standard library
|
||||
documentation, use::
|
||||
|
||||
intersphinx_mapping = {'python': ('http://docs.python.org/', None)}
|
||||
|
||||
This will download the corresponding :file:`objects.inv` file from the
|
||||
Internet and generate links to the pages under the given URI. The downloaded
|
||||
inventory is cached in the Sphinx environment, so it must be redownloaded
|
||||
whenever you do a full rebuild.
|
||||
|
||||
A second example, showing the meaning of a non-``None`` value::
|
||||
A second example, showing the meaning of a non-``None`` value of the second
|
||||
tuple item::
|
||||
|
||||
intersphinx_mapping = {'http://docs.python.org/': 'python-inv.txt'}
|
||||
intersphinx_mapping = {'python': ('http://docs.python.org/',
|
||||
'python-inv.txt')}
|
||||
|
||||
This will read the inventory from :file:`python-inv.txt` in the source
|
||||
directory, but still generate links to the pages under
|
||||
``http://docs.python.org/``. It is up to you to update the inventory file
|
||||
as new objects are added to the Python documentation.
|
||||
|
||||
When fetching remote inventory files, proxy settings will be read from
|
||||
the ``$HTTP_PROXY`` environment variable.
|
||||
``http://docs.python.org/``. It is up to you to update the inventory file as
|
||||
new objects are added to the Python documentation.
|
||||
|
||||
.. confval:: intersphinx_cache_limit
|
||||
|
||||
|
@ -742,13 +742,17 @@ class StandaloneHTMLBuilder(Builder):
|
||||
f.write('# The remainder of this file is compressed using zlib.\n')
|
||||
compressor = zlib.compressobj(9)
|
||||
for domainname, domain in self.env.domains.iteritems():
|
||||
for name, type, docname, anchor, prio in domain.get_objects():
|
||||
for name, dispname, type, docname, anchor, prio in \
|
||||
domain.get_objects():
|
||||
if anchor.endswith(name):
|
||||
# this can shorten the inventory by as much as 25%
|
||||
anchor = anchor[:-len(name)] + '$'
|
||||
uri = self.get_target_uri(docname) + '#' + anchor
|
||||
if dispname == name:
|
||||
dispname = '-'
|
||||
f.write(compressor.compress(
|
||||
'%s %s:%s %s %s\n' % (name, domainname, type, prio,
|
||||
self.get_target_uri(docname) + '#' + anchor)))
|
||||
'%s %s:%s %s %s %s\n' % (name, domainname, type, prio,
|
||||
uri, dispname)))
|
||||
f.write(compressor.flush())
|
||||
finally:
|
||||
f.close()
|
||||
|
@ -18,7 +18,6 @@ from os import path
|
||||
from docutils import nodes
|
||||
|
||||
from sphinx import addnodes
|
||||
from sphinx.locale import _
|
||||
from sphinx.builders.html import StandaloneHTMLBuilder
|
||||
|
||||
|
||||
|
@ -18,9 +18,9 @@ from os import path
|
||||
from docutils import nodes
|
||||
|
||||
from sphinx import addnodes
|
||||
from sphinx.locale import _
|
||||
from sphinx.builders.html import StandaloneHTMLBuilder
|
||||
|
||||
|
||||
_idpattern = re.compile(
|
||||
r'(?P<title>.+) (\((?P<id>[\w\.]+)( (?P<descr>\w+))?\))$')
|
||||
|
||||
|
@ -229,6 +229,7 @@ class Domain(object):
|
||||
five items:
|
||||
|
||||
* `name` -- fully qualified name
|
||||
* `dispname` -- name to display when searching/linking
|
||||
* `type` -- object type, a key in ``self.object_types``
|
||||
* `docname` -- the document where it is to be found
|
||||
* `anchor` -- the anchor name for the object
|
||||
|
@ -210,4 +210,4 @@ class CDomain(Domain):
|
||||
|
||||
def get_objects(self):
|
||||
for refname, (docname, type) in self.data['objects'].iteritems():
|
||||
yield (refname, type, docname, refname, 1)
|
||||
yield (refname, refname, type, docname, refname, 1)
|
||||
|
@ -1094,4 +1094,4 @@ class CPPDomain(Domain):
|
||||
|
||||
def get_objects(self):
|
||||
for refname, (docname, type) in self.data['objects'].iteritems():
|
||||
yield (refname, type, docname, refname, 1)
|
||||
yield (refname, refname, type, docname, refname, 1)
|
||||
|
@ -8,7 +8,6 @@
|
||||
:copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
import re
|
||||
|
||||
from sphinx import addnodes
|
||||
from sphinx.domains import Domain, ObjType
|
||||
@ -215,4 +214,4 @@ class JavaScriptDomain(Domain):
|
||||
|
||||
def get_objects(self):
|
||||
for refname, (docname, type) in self.data['objects'].iteritems():
|
||||
yield refname, type, docname, refname, 1
|
||||
yield refname, refname, type, docname, refname, 1
|
||||
|
@ -617,6 +617,6 @@ class PythonDomain(Domain):
|
||||
|
||||
def get_objects(self):
|
||||
for modname, info in self.data['modules'].iteritems():
|
||||
yield (modname, 'module', info[0], 'module-' + modname, 0)
|
||||
yield (modname, modname, 'module', info[0], 'module-' + modname, 0)
|
||||
for refname, (docname, type) in self.data['objects'].iteritems():
|
||||
yield (refname, type, docname, refname, 1)
|
||||
yield (refname, refname, type, docname, refname, 1)
|
||||
|
@ -131,4 +131,4 @@ class ReSTDomain(Domain):
|
||||
|
||||
def get_objects(self):
|
||||
for (typ, name), docname in self.data['objects'].iteritems():
|
||||
yield name, typ, docname, name, 1
|
||||
yield name, name, typ, docname, name, 1
|
||||
|
@ -418,25 +418,27 @@ class StandardDomain(Domain):
|
||||
def resolve_xref(self, env, fromdocname, builder,
|
||||
typ, target, node, contnode):
|
||||
if typ == 'ref':
|
||||
refdoc = node.get('refdoc', fromdocname)
|
||||
#refdoc = node.get('refdoc', fromdocname)
|
||||
if node['refexplicit']:
|
||||
# reference to anonymous label; the reference uses
|
||||
# the supplied link caption
|
||||
docname, labelid = self.data['anonlabels'].get(target, ('',''))
|
||||
sectname = node.astext()
|
||||
if not docname:
|
||||
env.warn(refdoc, 'undefined label: %s' %
|
||||
target, node.line)
|
||||
# XXX warn somehow if not resolved by intersphinx
|
||||
#if not docname:
|
||||
# env.warn(refdoc, 'undefined label: %s' %
|
||||
# target, node.line)
|
||||
else:
|
||||
# reference to named label; the final node will
|
||||
# contain the section name after the label
|
||||
docname, labelid, sectname = self.data['labels'].get(target,
|
||||
('','',''))
|
||||
if not docname:
|
||||
env.warn(refdoc,
|
||||
'undefined label: %s' % target + ' -- if you '
|
||||
'don\'t give a link caption the label must '
|
||||
'precede a section header.', node.line)
|
||||
# XXX warn somehow if not resolved by intersphinx
|
||||
#if not docname:
|
||||
# env.warn(refdoc,
|
||||
# 'undefined label: %s' % target + ' -- if you '
|
||||
# 'don\'t give a link caption the label must '
|
||||
# 'precede a section header.', node.line)
|
||||
if not docname:
|
||||
return None
|
||||
newnode = nodes.reference('', '')
|
||||
@ -487,9 +489,9 @@ class StandardDomain(Domain):
|
||||
|
||||
def get_objects(self):
|
||||
for (prog, option), info in self.data['progoptions'].iteritems():
|
||||
yield (option, 'option', info[0], info[1], 1)
|
||||
yield (option, option, 'option', info[0], info[1], 1)
|
||||
for (type, name), info in self.data['objects'].iteritems():
|
||||
yield (name, type, info[0], info[1],
|
||||
yield (name, name, type, info[0], info[1],
|
||||
self.object_types[type].attrs['searchprio'])
|
||||
for name, info in self.data['labels'].iteritems():
|
||||
yield (name, 'label', info[0], info[1], -1)
|
||||
yield (name, info[2], 'label', info[0], info[1], -1)
|
||||
|
@ -61,7 +61,7 @@ default_settings = {
|
||||
|
||||
# This is increased every time an environment attribute is added
|
||||
# or changed to properly invalidate pickle files.
|
||||
ENV_VERSION = 35
|
||||
ENV_VERSION = 36
|
||||
|
||||
|
||||
default_substitutions = set([
|
||||
|
@ -58,7 +58,7 @@ def read_inventory_v1(f, uri, join):
|
||||
else:
|
||||
type = 'py:' + type
|
||||
location += '#' + name
|
||||
invdata.setdefault(type, {})[name] = (projname, version, location)
|
||||
invdata.setdefault(type, {})[name] = (projname, version, location, '-')
|
||||
return invdata
|
||||
|
||||
|
||||
@ -90,11 +90,12 @@ def read_inventory_v2(f, uri, join, bufsize=16*1024):
|
||||
assert not buf
|
||||
|
||||
for line in split_lines(read_chunks()):
|
||||
name, type, prio, location = line.rstrip().split(None, 3)
|
||||
name, type, prio, location, dispname = line.rstrip().split(None, 4)
|
||||
if location.endswith('$'):
|
||||
location = location[:-1] + name
|
||||
location = join(uri, location)
|
||||
invdata.setdefault(type, {})[name] = (projname, version, location)
|
||||
invdata.setdefault(type, {})[name] = (projname, version,
|
||||
location, dispname)
|
||||
return invdata
|
||||
|
||||
|
||||
@ -142,7 +143,15 @@ def load_mappings(app):
|
||||
env.intersphinx_cache = {}
|
||||
cache = env.intersphinx_cache
|
||||
update = False
|
||||
for uri, inv in app.config.intersphinx_mapping.iteritems():
|
||||
for key, value in app.config.intersphinx_mapping.iteritems():
|
||||
if isinstance(value, tuple):
|
||||
# new format
|
||||
name, (uri, inv) = key, value
|
||||
if not name.isalnum():
|
||||
env.warn('intersphinx identifier %r is not alphanumeric' % name)
|
||||
else:
|
||||
# old format, no name
|
||||
name, uri, inv = None, key, value
|
||||
# we can safely assume that the uri<->inv mapping is not changed
|
||||
# during partial rebuilds since a changed intersphinx_mapping
|
||||
# setting will cause a full environment reread
|
||||
@ -152,12 +161,19 @@ def load_mappings(app):
|
||||
# files; remote ones only if the cache time is expired
|
||||
if '://' not in inv or uri not in cache \
|
||||
or cache[uri][0] < cache_time:
|
||||
app.info('loading intersphinx inventory from %s...' % inv)
|
||||
invdata = fetch_inventory(app, uri, inv)
|
||||
cache[uri] = (now, invdata)
|
||||
if invdata:
|
||||
cache[uri] = (name, now, invdata)
|
||||
else:
|
||||
cache.pop(uri, None)
|
||||
update = True
|
||||
if update:
|
||||
env.intersphinx_inventory = {}
|
||||
for _, invdata in cache.itervalues():
|
||||
env.intersphinx_named_inventory = {}
|
||||
for name, _, invdata in cache.itervalues():
|
||||
if name:
|
||||
env.intersphinx_named_inventory[name] = invdata
|
||||
for type, objects in invdata.iteritems():
|
||||
env.intersphinx_inventory.setdefault(
|
||||
type, {}).update(objects)
|
||||
@ -173,20 +189,27 @@ def missing_reference(app, env, node, contnode):
|
||||
objtypes = env.domains[domain].objtypes_for_role(node['reftype'])
|
||||
if not objtypes:
|
||||
return
|
||||
for objtype in objtypes:
|
||||
fulltype = '%s:%s' % (domain, objtype)
|
||||
if fulltype in env.intersphinx_inventory and \
|
||||
target in env.intersphinx_inventory[fulltype]:
|
||||
break
|
||||
else:
|
||||
return
|
||||
proj, version, uri = env.intersphinx_inventory[fulltype][target]
|
||||
newnode = nodes.reference('', '')
|
||||
newnode['refuri'] = uri
|
||||
newnode['reftitle'] = '(in %s v%s)' % (proj, version)
|
||||
newnode['class'] = 'external-xref'
|
||||
newnode.append(contnode)
|
||||
return newnode
|
||||
objtypes = ['%s:%s' % (domain, objtype) for objtype in objtypes]
|
||||
to_try = [(env.intersphinx_inventory, target)]
|
||||
if ':' in target:
|
||||
# first part may be the foreign doc set name
|
||||
setname, newtarget = target.split(':', 1)
|
||||
if setname in env.intersphinx_named_inventory:
|
||||
to_try.append((env.intersphinx_named_inventory[setname], newtarget))
|
||||
for inventory, target in to_try:
|
||||
for objtype in objtypes:
|
||||
if objtype not in inventory or target not in inventory[objtype]:
|
||||
continue
|
||||
proj, version, uri, dispname = inventory[objtype][target]
|
||||
newnode = nodes.reference('', '')
|
||||
newnode['refuri'] = uri
|
||||
newnode['reftitle'] = '(in %s v%s)' % (proj, version)
|
||||
newnode['class'] = 'external-xref'
|
||||
if dispname == '-':
|
||||
newnode.append(contnode)
|
||||
else:
|
||||
newnode.append(contnode.__class__(dispname, dispname))
|
||||
return newnode
|
||||
|
||||
|
||||
def setup(app):
|
||||
|
@ -153,7 +153,9 @@ class IndexBuilder(object):
|
||||
otypes = self._objtypes
|
||||
onames = self._objnames
|
||||
for domainname, domain in self.env.domains.iteritems():
|
||||
for fullname, type, docname, anchor, prio in domain.get_objects():
|
||||
for fullname, dispname, type, docname, anchor, prio in \
|
||||
domain.get_objects():
|
||||
# XXX use dispname?
|
||||
if docname not in fn2index:
|
||||
continue
|
||||
if prio < 0:
|
||||
|
@ -36,10 +36,10 @@ inventory_v2 = '''\
|
||||
# Version: 2.0
|
||||
# The remainder of this file is compressed with zlib.
|
||||
''' + zlib.compress('''\
|
||||
module1 py:module 0 foo.html#module-module1
|
||||
module2 py:module 0 foo.html#module-$
|
||||
module1.func py:function 1 sub/foo.html#$
|
||||
CFunc c:function 2 cfunc.html#CFunc
|
||||
module1 py:module 0 foo.html#module-module1 Long Module desc
|
||||
module2 py:module 0 foo.html#module-$ -
|
||||
module1.func py:function 1 sub/foo.html#$ -
|
||||
CFunc c:function 2 cfunc.html#CFunc -
|
||||
''')
|
||||
|
||||
|
||||
@ -48,9 +48,9 @@ def test_read_inventory_v1():
|
||||
f.readline()
|
||||
invdata = read_inventory_v1(f, '/util', posixpath.join)
|
||||
assert invdata['py:module']['module'] == \
|
||||
('foo', '1.0', '/util/foo.html#module-module')
|
||||
('foo', '1.0', '/util/foo.html#module-module', '-')
|
||||
assert invdata['py:class']['module.cls'] == \
|
||||
('foo', '1.0', '/util/foo.html#module.cls')
|
||||
('foo', '1.0', '/util/foo.html#module.cls', '-')
|
||||
|
||||
|
||||
def test_read_inventory_v2():
|
||||
@ -67,9 +67,9 @@ def test_read_inventory_v2():
|
||||
|
||||
assert len(invdata1['py:module']) == 2
|
||||
assert invdata1['py:module']['module1'] == \
|
||||
('foo', '2.0', '/util/foo.html#module-module1')
|
||||
('foo', '2.0', '/util/foo.html#module-module1', 'Long Module desc')
|
||||
assert invdata1['py:module']['module2'] == \
|
||||
('foo', '2.0', '/util/foo.html#module-module2')
|
||||
('foo', '2.0', '/util/foo.html#module-module2', '-')
|
||||
assert invdata1['py:function']['module1.func'][2] == \
|
||||
'/util/sub/foo.html#module1.func'
|
||||
assert invdata1['c:function']['CFunc'][2] == '/util/cfunc.html#CFunc'
|
||||
@ -88,7 +88,7 @@ def test_missing_reference(tempdir, app):
|
||||
inv = app.env.intersphinx_inventory
|
||||
|
||||
assert inv['py:module']['module2'] == \
|
||||
('foo', '2.0', 'http://docs.python.org/foo.html#module-module2')
|
||||
('foo', '2.0', 'http://docs.python.org/foo.html#module-module2', '-')
|
||||
|
||||
# create fake nodes and check referencing
|
||||
contnode = nodes.emphasis('foo')
|
||||
|
Loading…
Reference in New Issue
Block a user