summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPauli Virtanen <pav@iki.fi>2012-03-11 17:48:51 +0100
committerPauli Virtanen <pav@iki.fi>2012-03-11 17:48:51 +0100
commite85f2467852ec4d42e4f4ed147bd64ef2907517d (patch)
tree5c2fc418a1845dd24722b1e5917a35b45ea52bb0
parent3137157ebafefaedad02cfc780957b3adb59e826 (diff)
downloadsphinx-e85f2467852ec4d42e4f4ed147bd64ef2907517d.tar.gz
ENH: add a linkcode extension
-rw-r--r--doc/ext/linkcode.rst47
-rw-r--r--doc/extensions.rst1
-rw-r--r--sphinx/ext/linkcode.py72
-rw-r--r--tests/root/conf.py22
-rw-r--r--tests/test_linkcode.py28
5 files changed, 170 insertions, 0 deletions
diff --git a/doc/ext/linkcode.rst b/doc/ext/linkcode.rst
new file mode 100644
index 00000000..9ae24756
--- /dev/null
+++ b/doc/ext/linkcode.rst
@@ -0,0 +1,47 @@
+:mod:`sphinx.ext.linkcode` -- Add external links to source code
+===============================================================
+
+.. module:: sphinx.ext.linkcode
+ :synopsis: Add external links to source code.
+.. moduleauthor:: Pauli Virtanen
+
+.. versionadded:: 1.1
+
+This extension looks at your Python object descriptions
+(``.. class::``, ``.. function::`` etc.) and adds external links to
+code hosted somewhere on the web. The intent is similar to the
+``sphinx.ext.viewcode`` extension, but assumes the source code can be
+found somewhere on the Internet.
+
+In your configuration, you need to specify a :confval:`linkcode_resolve`
+function that returns an URL based on the name of a module and
+the full name of the object.
+
+.. confval:: linkcode_resolve
+
+ This is a function ``linkcode_resolve(domain, info)``,
+ which should return the URL to source code corresponding to
+ the object in given domain with given information.
+
+ The function should return ``None`` if no link is to be added.
+
+ The argument ``domain`` specifies the language domain the object is
+ in. ``info`` is a dictionary with the following keys guaranteed to
+ be present (dependent on the domain):
+
+ - ``c``: ``names`` (list of names for the object)
+ - ``cpp``: ``names`` (list of names for the object)
+ - ``py``: ``module`` (name of the module), ``fullname`` (name of the object)
+ - ``javascript``: ``object`` (name of the object), ``fullname`` (name of the item)
+
+ Example:
+
+ .. code-block:: python
+
+ def linkcode_resolve(domain, info):
+ if domain != 'py':
+ return None
+ if not info['module']:
+ return None
+ filename = info['module'].replace('.', '/')
+ return "http://somesite/sourcerepo/%s.py" % filename
diff --git a/doc/extensions.rst b/doc/extensions.rst
index b9397448..07bc7fe4 100644
--- a/doc/extensions.rst
+++ b/doc/extensions.rst
@@ -53,6 +53,7 @@ These extensions are built in and can be activated by respective entries in the
ext/todo
ext/extlinks
ext/viewcode
+ ext/linkcode
ext/oldcmarkup
diff --git a/sphinx/ext/linkcode.py b/sphinx/ext/linkcode.py
new file mode 100644
index 00000000..ffe5b9c0
--- /dev/null
+++ b/sphinx/ext/linkcode.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+"""
+ sphinx.ext.linkcode
+ ~~~~~~~~~~~~~~~~~~~
+
+ Add external links to module code in Python object descriptions.
+
+ :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+from docutils import nodes
+
+from sphinx import addnodes
+from sphinx.locale import _
+from sphinx.errors import SphinxError
+
+class LinkcodeError(SphinxError):
+ category = "linkcode error"
+
+def doctree_read(app, doctree):
+ env = app.builder.env
+
+ resolve_target = getattr(env.config, 'linkcode_resolve', None)
+ if not callable(env.config.linkcode_resolve):
+ raise LinkcodeError(
+ "Function `linkcode_resolve` is not given in conf.py")
+
+ domain_keys = dict(
+ py=['module', 'fullname'],
+ c=['names'],
+ cpp=['names'],
+ js=['object', 'fullname'],
+ )
+
+ for objnode in doctree.traverse(addnodes.desc):
+ domain = objnode.get('domain')
+ uris = set()
+ for signode in objnode:
+ if not isinstance(signode, addnodes.desc_signature):
+ continue
+
+ # Convert signode to a specified format
+ info = {}
+ for key in domain_keys.get(domain, []):
+ value = signode.get(key)
+ if not value:
+ value = ''
+ info[key] = value
+ if not info:
+ continue
+
+ # Call user code to resolve the link
+ uri = resolve_target(domain, info)
+ if not uri:
+ # no source
+ continue
+
+ if uri in uris or not uri:
+ # only one link per name, please
+ continue
+ uris.add(uri)
+
+ onlynode = addnodes.only(expr='html')
+ onlynode += nodes.reference('', '', internal=False, refuri=uri)
+ onlynode[0] += nodes.inline('', _('[source]'),
+ classes=['viewcode-link'])
+ signode += onlynode
+
+def setup(app):
+ app.connect('doctree-read', doctree_read)
+ app.add_config_value('linkcode_resolve', None, 'env')
diff --git a/tests/root/conf.py b/tests/root/conf.py
index b97ddfcc..37d5e91a 100644
--- a/tests/root/conf.py
+++ b/tests/root/conf.py
@@ -67,6 +67,28 @@ extlinks = {'issue': ('http://bugs.python.org/issue%s', 'issue '),
# modify tags from conf.py
tags.add('confpytag')
+# -- linkcode
+
+if 'test_linkcode' in tags:
+ import glob
+
+ extensions.remove('sphinx.ext.viewcode')
+ extensions.append('sphinx.ext.linkcode')
+
+ exclude_patterns.extend(glob.glob('*.txt') + glob.glob('*/*.txt'))
+ exclude_patterns.remove('contents.txt')
+ exclude_patterns.remove('objects.txt')
+
+ def linkcode_resolve(domain, info):
+ if domain == 'py':
+ fn = info['module'].replace('.', '/')
+ return "http://foobar/source/%s.py" % fn
+ elif domain == "js":
+ return "http://foobar/js/" + info['fullname']
+ elif domain in ("c", "cpp"):
+ return "http://foobar/%s/%s" % (domain, "".join(info['names']))
+ else:
+ raise AssertionError()
# -- extension API
diff --git a/tests/test_linkcode.py b/tests/test_linkcode.py
new file mode 100644
index 00000000..b4b33332
--- /dev/null
+++ b/tests/test_linkcode.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+"""
+ test_linkcode
+ ~~~~~~~~~~~~~
+
+ Test the sphinx.ext.linkcode extension.
+
+ :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import os
+from util import *
+
+@with_app(srcdir='(temp)', buildername='html', tags=['test_linkcode'])
+def test_html(app):
+ app.builder.build_all()
+
+ fp = open(os.path.join(app.outdir, 'objects.html'), 'rb')
+ try:
+ stuff = fp.read()
+ finally:
+ fp.close()
+
+ assert 'http://foobar/source/foolib.py' in stuff
+ assert 'http://foobar/js/' in stuff
+ assert 'http://foobar/c/' in stuff
+ assert 'http://foobar/cpp/' in stuff