Commits

Georg Brandl  committed 680565b

#413: Added a way for intersphinx to refer to named labels in other
projects, and to specify the project you want to link to.

  • Participants
  • Parent commits 586ef03

Comments (0)

Files changed (16)

 
 * 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č.

File doc/ext/intersphinx.rst

 
 .. 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
 
 .. 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
 

File sphinx/builders/html.py

             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()

File sphinx/builders/htmlhelp.py

 from docutils import nodes
 
 from sphinx import addnodes
-from sphinx.locale import _
 from sphinx.builders.html import StandaloneHTMLBuilder
 
 

File sphinx/builders/qthelp.py

 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+))?\))$')
 

File sphinx/domains/__init__.py

         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

File sphinx/domains/c.py

 
     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)

File sphinx/domains/cpp.py

 
     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)

File sphinx/domains/javascript.py

     :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
 
     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

File sphinx/domains/python.py

 
     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)

File sphinx/domains/rst.py

 
     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

File sphinx/domains/std.py

     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('', '')
 
     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)

File sphinx/environment.py

 
 # 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([

File sphinx/ext/intersphinx.py

         else:
             type = 'py:' + type
             location += '#' + name
-        invdata.setdefault(type, {})[name] = (projname, version, location)
+        invdata.setdefault(type, {})[name] = (projname, version, location, '-')
     return invdata
 
 
         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
 
 
         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
         # 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)
     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):

File sphinx/search.py

         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:

File tests/test_intersphinx.py

 # 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 -
 ''')
 
 
     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():
 
     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'
     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')