1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
# -*- coding: utf-8 -*-
"""
sphinx.ext.intersphinx
~~~~~~~~~~~~~~~~~~~~~~
Insert links to Python objects documented in remote Sphinx documentation.
This works as follows:
* Each Sphinx HTML build creates a file named "objects.inv" that contains
a mapping from Python identifiers to URIs relative to the HTML set's root.
* Projects using the Intersphinx extension can specify links to such mapping
files in the `intersphinx_mapping` config value. The mapping will then be
used to resolve otherwise missing references to Python 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 specified individually, e.g. if the docs should be buildable
without Internet access.
:copyright: 2008 by Georg Brandl.
:license: BSD.
"""
import time
import urllib
import posixpath
from os import path
from docutils import nodes
from sphinx.builders.html import INVENTORY_FILENAME
def fetch_inventory(app, uri, inv):
"""Fetch, parse and return an intersphinx inventory file."""
invdata = {}
# both *uri* (base URI of the links to generate) and *inv* (actual
# location of the inventory file) can be local or remote URIs
localuri = uri.find('://') == -1
try:
if inv.find('://') != -1:
f = urllib.urlopen(inv)
else:
f = open(path.join(app.srcdir, inv))
except Exception, err:
app.warn('intersphinx inventory %r not fetchable due to '
'%s: %s' % (inv, err.__class__, err))
return
try:
line = f.next()
if line.rstrip() != '# Sphinx inventory version 1':
raise ValueError('unknown or unsupported inventory version')
line = f.next()
projname = line.rstrip()[11:].decode('utf-8')
line = f.next()
version = line.rstrip()[11:]
for line in f:
name, type, location = line.rstrip().split(None, 2)
if localuri:
location = path.join(uri, location)
else:
location = posixpath.join(uri, location)
invdata[name] = (type, projname, version, location)
f.close()
except Exception, err:
app.warn('intersphinx inventory %r not readable due to '
'%s: %s' % (inv, err.__class__, err))
else:
return invdata
def load_mappings(app):
"""Load all intersphinx mappings into the environment."""
now = int(time.time())
cache_time = now - app.config.intersphinx_cache_limit * 86400
env = app.builder.env
if not hasattr(env, 'intersphinx_cache'):
env.intersphinx_cache = {}
cache = env.intersphinx_cache
update = False
for uri, inv in app.config.intersphinx_mapping.iteritems():
# 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
if not inv:
inv = posixpath.join(uri, INVENTORY_FILENAME)
# decide whether the inventory must be read: always read local
# 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:
invdata = fetch_inventory(app, uri, inv)
cache[uri] = (now, invdata)
update = True
if update:
env.intersphinx_inventory = {}
for _, invdata in cache.itervalues():
if invdata:
env.intersphinx_inventory.update(invdata)
def missing_reference(app, env, node, contnode):
"""Attempt to resolve a missing reference via intersphinx references."""
type = node['reftype']
target = node['reftarget']
if type == 'mod':
type, proj, version, uri = env.intersphinx_inventory.get(target,
('','','',''))
if type != 'mod':
return None
target = 'module-' + target # for link anchor
else:
if target[-2:] == '()':
target = target[:-2]
target = target.rstrip(' *')
# special case: exceptions and object methods
if type == 'exc' and '.' not in target and \
'exceptions.' + target in env.intersphinx_inventory:
target = 'exceptions.' + target
elif type in ('func', 'meth') and '.' not in target and \
'object.' + target in env.intersphinx_inventory:
target = 'object.' + target
if target not in env.intersphinx_inventory:
return None
type, proj, version, uri = env.intersphinx_inventory[target]
print "Intersphinx hit:", target, uri
newnode = nodes.reference('', '')
newnode['refuri'] = uri + '#' + target
newnode['reftitle'] = '(in %s v%s)' % (proj, version)
newnode['class'] = 'external-xref'
newnode.append(contnode)
return newnode
def setup(app):
app.add_config_value('intersphinx_mapping', {}, True)
app.add_config_value('intersphinx_cache_limit', 5, False)
app.connect('missing-reference', missing_reference)
app.connect('builder-inited', load_mappings)
|