diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py index dbaa9ad55..c905c72dd 100644 --- a/sphinx/directives/__init__.py +++ b/sphinx/directives/__init__.py @@ -145,7 +145,7 @@ class ObjectDescription(Directive): if typ in self.doc_fields_with_linked_arg: # XXX currmodule/currclass node = addnodes.pending_xref( - obj, reftype='obj', refcaption=False, + obj, reftype='obj', refexplicit=False, reftarget=obj) #, modname=self.env.currmodule #, classname=self.env.currclass diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py index e7933d49b..563fd2aa0 100644 --- a/sphinx/domains/__init__.py +++ b/sphinx/domains/__init__.py @@ -82,8 +82,8 @@ class Domain(object): self._role2type = {} for name, obj in self.object_types.iteritems(): for rolename in obj.roles: - self._role2type[rolename] = name - self.object_type = self._role2type.get # XXX necessary? + self._role2type.setdefault(rolename, []).append(name) + self.objtypes_for_role = self._role2type.get def role(self, name): """ diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py index f5d08ee44..78a6966b0 100644 --- a/sphinx/ext/intersphinx.py +++ b/sphinx/ext/intersphinx.py @@ -42,7 +42,7 @@ if hasattr(urllib2, 'HTTPSHandler'): urllib2.install_opener(urllib2.build_opener(*handlers)) -def fetch_inventory_v1(f, uri, join): +def read_inventory_v1(f, uri, join): invdata = {} line = f.next() projname = line.rstrip()[11:].decode('utf-8') @@ -56,12 +56,13 @@ def fetch_inventory_v1(f, uri, join): type = 'py:module' location += '#module-' + name else: + type = 'py:' + type location += '#' + name invdata.setdefault(type, {})[name] = (projname, version, location) return invdata -def fetch_inventory_v2(f, uri, join): +def read_inventory_v2(f, uri, join, bufsize=16*1024): invdata = {} line = f.readline() projname = line.rstrip()[11:].decode('utf-8') @@ -73,7 +74,7 @@ def fetch_inventory_v2(f, uri, join): def read_chunks(): decompressor = zlib.decompressobj() - for chunk in iter(lambda: f.read(16 * 1024), ''): + for chunk in iter(lambda: f.read(bufsize), ''): yield decompressor.decompress(chunk) yield decompressor.flush() @@ -116,9 +117,9 @@ def fetch_inventory(app, uri, inv): line = f.readline().rstrip() try: if line == '# Sphinx inventory version 1': - invdata = fetch_inventory_v1(f, uri, join) + invdata = read_inventory_v1(f, uri, join) elif line == '# Sphinx inventory version 2': - invdata = fetch_inventory_v2(f, uri, join) + invdata = read_inventory_v2(f, uri, join) else: raise ValueError f.close() @@ -163,33 +164,24 @@ def load_mappings(app): def missing_reference(app, env, node, contnode): """Attempt to resolve a missing reference via intersphinx references.""" - type = node['reftype'] - domain = node['refdomain'] - fulltype = domain + ':' + type + domain = node.get('refdomain') + if not domain: + # only objects in domains are in the inventory + return target = node['reftarget'] - if type not in env.intersphinx_inventory: + objtypes = env.domains[domain].objtypes_for_role(node['reftype']) + if not objtypes: return - if target not in env.intersphinx_inventory[type]: + 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[type][target] - - # XXX - 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 + proj, version, uri = env.intersphinx_inventory[fulltype][target] newnode = nodes.reference('', '') - newnode['refuri'] = uri + '#' + target + newnode['refuri'] = uri newnode['reftitle'] = '(in %s v%s)' % (proj, version) newnode['class'] = 'external-xref' newnode.append(contnode) diff --git a/tests/test_intersphinx.py b/tests/test_intersphinx.py new file mode 100644 index 000000000..2d5219145 --- /dev/null +++ b/tests/test_intersphinx.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +""" + test_intersphinx + ~~~~~~~~~~~~~~~~ + + Test the intersphinx extension. + + :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import zlib +import posixpath +from cStringIO import StringIO + +from docutils import nodes + +from sphinx import addnodes +from sphinx.ext.intersphinx import read_inventory_v1, read_inventory_v2, \ + fetch_inventory, load_mappings, missing_reference + +from util import * + + +inventory_v1 = '''\ +# Sphinx inventory version 1 +# Project: foo +# Version: 1.0 +module mod foo.html +module.cls class foo.html +''' + +inventory_v2 = '''\ +# Sphinx inventory version 2 +# Project: foo +# 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 +''') + + +def test_read_inventory_v1(): + f = StringIO(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') + assert invdata['py:class']['module.cls'] == \ + ('foo', '1.0', '/util/foo.html#module.cls') + + +def test_read_inventory_v2(): + f = StringIO(inventory_v2) + f.readline() + invdata1 = read_inventory_v2(f, '/util', posixpath.join) + + # try again with a small buffer size to test the chunking algorithm + f = StringIO(inventory_v2) + f.readline() + invdata2 = read_inventory_v2(f, '/util', posixpath.join, bufsize=5) + + assert invdata1 == invdata2 + + assert len(invdata1['py:module']) == 2 + assert invdata1['py:module']['module1'] == \ + ('foo', '2.0', '/util/foo.html#module-module1') + assert invdata1['py: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' + + +@with_app(confoverrides={'extensions': 'sphinx.ext.intersphinx'}) +@with_tempdir +def test_missing_reference(tempdir, app): + inv_file = tempdir / 'inventory' + write_file(inv_file, inventory_v2) + app.config.intersphinx_mapping = {'http://docs.python.org/': inv_file} + app.config.intersphinx_cache_limit = 0 + + # load the inventory and check if it's done correctly + load_mappings(app) + inv = app.env.intersphinx_inventory + + assert inv['py:module']['module2'] == \ + ('foo', '2.0', 'http://docs.python.org/foo.html#module-module2') + + # create fake nodes and check referencing + contnode = nodes.emphasis('foo') + refnode = addnodes.pending_xref('') + refnode['reftarget'] = 'module1.func' + refnode['reftype'] = 'func' + refnode['refdomain'] = 'py' + + rn = missing_reference(app, app.env, refnode, contnode) + assert isinstance(rn, nodes.reference) + assert rn['refuri'] == 'http://docs.python.org/sub/foo.html#module1.func' + assert rn['reftitle'] == '(in foo v2.0)' + assert rn[0] is contnode + + # create unresolvable nodes and check None return value + refnode['reftype'] = 'foo' + assert missing_reference(app, app.env, refnode, contnode) is None + + refnode['reftype'] = 'function' + refnode['reftarget'] = 'foo.func' + assert missing_reference(app, app.env, refnode, contnode) is None