summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorg Brandl <georg@python.org>2010-01-13 23:35:13 +0000
committerGeorg Brandl <georg@python.org>2010-01-13 23:35:13 +0000
commit07f295df7ce486b36d950614080b1267dc709dfa (patch)
treefa4d59c28c0c92ffa42b18f94438f9312f89b1f9
parenta52885d078a9dcc921b113f65b7d3fde76709ded (diff)
parentedb89b1940326e9e5e7488db91f46355e3871346 (diff)
downloadsphinx-git-07f295df7ce486b36d950614080b1267dc709dfa.tar.gz
merge
-rw-r--r--CHANGES4
-rw-r--r--doc/conf.py2
-rw-r--r--doc/ext/appapi.rst10
-rw-r--r--doc/ext/viewcode.rst19
-rw-r--r--doc/extensions.rst1
-rw-r--r--sphinx/application.py1
-rw-r--r--sphinx/builders/html.py6
-rw-r--r--sphinx/directives/__init__.py8
-rw-r--r--sphinx/directives/other.py3
-rw-r--r--sphinx/domains/c.py2
-rw-r--r--sphinx/domains/python.py74
-rw-r--r--sphinx/domains/std.py10
-rw-r--r--sphinx/environment.py4
-rw-r--r--sphinx/errors.py8
-rw-r--r--sphinx/ext/autodoc.py20
-rw-r--r--sphinx/ext/autosummary/__init__.py2
-rw-r--r--sphinx/ext/inheritance_diagram.py2
-rw-r--r--sphinx/ext/viewcode.py162
-rw-r--r--sphinx/pycode/__init__.py45
-rw-r--r--sphinx/themes/basic/static/basic.css16
-rw-r--r--sphinx/util/__init__.py32
-rw-r--r--tests/test_autodoc.py24
22 files changed, 351 insertions, 104 deletions
diff --git a/CHANGES b/CHANGES
index 2ddd8c7ed..8b605fa33 100644
--- a/CHANGES
+++ b/CHANGES
@@ -7,6 +7,10 @@ Release 1.0 (in development)
* Support for docutils 0.4 has been removed.
+* Added the ``viewcode`` extension.
+
+* Added ``html-collect-pages`` event.
+
* Added ``tab-width`` option to ``literalinclude`` directive.
* The ``html_sidebars`` config value can now contain patterns as
diff --git a/doc/conf.py b/doc/conf.py
index afa330681..3ef3ff719 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -7,7 +7,7 @@ import sys, os, re
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.addons.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo',
- 'sphinx.ext.autosummary']
+ 'sphinx.ext.autosummary', 'sphinx.ext.viewcode']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
diff --git a/doc/ext/appapi.rst b/doc/ext/appapi.rst
index 7ed3d3d49..f3a4a0613 100644
--- a/doc/ext/appapi.rst
+++ b/doc/ext/appapi.rst
@@ -360,7 +360,15 @@ registered event handlers.
.. versionadded:: 0.5
-.. event:: page-context (app, pagename, templatename, context, doctree)
+.. event:: html-collect-pages (app)
+
+ Emitted when the HTML builder is starting to write non-document pages. You
+ can add pages to write by returning an iterable from this event consisting of
+ ``(pagename, context, templatename)``.
+
+ .. versionadded:: 1.0
+
+.. event:: html-page-context (app, pagename, templatename, context, doctree)
Emitted when the HTML builder has created a context dictionary to render a
template with -- this can be used to add custom elements to the context.
diff --git a/doc/ext/viewcode.rst b/doc/ext/viewcode.rst
new file mode 100644
index 000000000..ba6c8f864
--- /dev/null
+++ b/doc/ext/viewcode.rst
@@ -0,0 +1,19 @@
+:mod:`sphinx.ext.viewcode` -- Add links to highlighted source code
+==================================================================
+
+.. module:: sphinx.ext.viewcode
+ :synopsis: Add links to a highlighted version of the source code.
+.. moduleauthor:: Georg Brandl
+
+.. versionadded:: 1.0
+
+
+This extension looks at your Python object descriptions (``.. class::``,
+``.. function::`` etc.) and tries to find the source files where the objects are
+contained. When found, a separate HTML page will be output for each module with
+a highlighted version of the source code, and a link will be added to all object
+descriptions that leads to the source code of the described object. A link back
+from the source to the description will also be inserted.
+
+There are currently no configuration values for this extension; you just need to
+add ``'sphinx.ext.viewcode'`` to your :confval:`extensions` value for it to work.
diff --git a/doc/extensions.rst b/doc/extensions.rst
index 29c40ccdb..389275202 100644
--- a/doc/extensions.rst
+++ b/doc/extensions.rst
@@ -52,6 +52,7 @@ These extensions are built in and can be activated by respective entries in the
ext/coverage
ext/todo
ext/extlinks
+ ext/viewcode
Third-party extensions
diff --git a/sphinx/application.py b/sphinx/application.py
index 99753b73a..61c3b05a9 100644
--- a/sphinx/application.py
+++ b/sphinx/application.py
@@ -47,6 +47,7 @@ events = {
'missing-reference': 'env, node, contnode',
'doctree-resolved': 'doctree, docname',
'env-updated': 'env',
+ 'html-collect-pages': 'builder',
'html-page-context': 'pagename, context, doctree or None',
'build-finished': 'exception',
}
diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py
index 2ef2e61fb..cb800556c 100644
--- a/sphinx/builders/html.py
+++ b/sphinx/builders/html.py
@@ -381,8 +381,12 @@ class StandaloneHTMLBuilder(Builder):
def finish(self):
self.info(bold('writing additional files...'), nonl=1)
- # the global general index
+ # pages from extensions
+ for pagelist in self.app.emit('html-collect-pages'):
+ for pagename, context, template in pagelist:
+ self.handle_page(pagename, context, template)
+ # the global general index
if self.config.html_use_index:
# the total count of lines for each index letter, used to distribute
# the entries into two columns
diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py
index 5ffce16bd..80f622023 100644
--- a/sphinx/directives/__init__.py
+++ b/sphinx/directives/__init__.py
@@ -190,7 +190,7 @@ class ObjectDescription(Directive):
return [strip_backslash_re.sub('', sig.strip())
for sig in self.arguments[0].split('\n')]
- def parse_signature(self, sig, signode):
+ def handle_signature(self, sig, signode):
"""
Parse the signature *sig* into individual nodes and append them to
*signode*. If ValueError is raised, parsing is aborted and the whole
@@ -242,8 +242,10 @@ class ObjectDescription(Directive):
signode['first'] = False
node.append(signode)
try:
- # name can also be a tuple, e.g. (classname, objname)
- name = self.parse_signature(sig, signode)
+ # name can also be a tuple, e.g. (classname, objname);
+ # this is strictly domain-specific (i.e. no assumptions may
+ # be made in this base class)
+ name = self.handle_signature(sig, signode)
except ValueError, err:
# signature parsing failed
signode.clear()
diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py
index a347970c9..1ba6e3bcc 100644
--- a/sphinx/directives/other.py
+++ b/sphinx/directives/other.py
@@ -215,7 +215,8 @@ class VersionChange(Directive):
env = self.state.document.settings.env
env.versionchanges.setdefault(node['version'], []).append(
(node['type'], env.doc_read_data['docname'], self.lineno,
- env.doc_read_data.get('py_module'),
+ # XXX: python domain specific
+ env.doc_read_data.get('py:module'),
env.doc_read_data.get('object'),
node.astext()))
return ret
diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py
index 7a29ff68f..fc5644091 100644
--- a/sphinx/domains/c.py
+++ b/sphinx/domains/c.py
@@ -66,7 +66,7 @@ class CObject(ObjectDescription):
else:
node += tnode
- def parse_signature(self, sig, signode):
+ def handle_signature(self, sig, signode):
"""Transform a C (or C++) signature into RST nodes."""
# first try the function pointer signature regex, it's more specific
m = c_funcptr_sig_re.match(sig)
diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py
index 46b938981..3a1424ba6 100644
--- a/sphinx/domains/python.py
+++ b/sphinx/domains/python.py
@@ -53,7 +53,7 @@ class PyObject(ObjectDescription):
"""
return False
- def parse_signature(self, sig, signode):
+ def handle_signature(self, sig, signode):
"""
Transform a Python signature into RST nodes.
Returns (fully qualified name of the thing, classname if any).
@@ -65,37 +65,49 @@ class PyObject(ObjectDescription):
m = py_sig_re.match(sig)
if m is None:
raise ValueError
- classname, name, arglist, retann = m.groups()
+ name_prefix, name, arglist, retann = m.groups()
- currclass = self.env.doc_read_data.get('py_class')
- if currclass:
+ # determine module and class name (if applicable), as well as full name
+ modname = self.options.get(
+ 'module', self.env.doc_read_data.get('py:module'))
+ classname = self.env.doc_read_data.get('py:class')
+ if classname:
add_module = False
- if classname and classname.startswith(currclass):
- fullname = classname + name
+ if name_prefix and name_prefix.startswith(classname):
+ fullname = name_prefix + name
# class name is given again in the signature
- classname = classname[len(currclass):].lstrip('.')
- elif classname:
+ name_prefix = name_prefix[len(classname):].lstrip('.')
+ elif name_prefix:
# class name is given in the signature, but different
# (shouldn't happen)
- fullname = currclass + '.' + classname + name
+ fullname = classname + '.' + name_prefix + name
else:
# class name is not given in the signature
- fullname = currclass + '.' + name
+ fullname = classname + '.' + name
else:
add_module = True
- fullname = classname and classname + name or name
+ if name_prefix:
+ classname = name_prefix.rstrip('.')
+ fullname = name_prefix + name
+ else:
+ classname = ''
+ fullname = name
- prefix = self.get_signature_prefix(sig)
- if prefix:
- signode += addnodes.desc_annotation(prefix, prefix)
+ signode['module'] = modname
+ signode['class'] = classname
+ signode['fullname'] = fullname
- if classname:
- signode += addnodes.desc_addname(classname, classname)
+ sig_prefix = self.get_signature_prefix(sig)
+ if sig_prefix:
+ signode += addnodes.desc_annotation(sig_prefix, sig_prefix)
+
+ if name_prefix:
+ signode += addnodes.desc_addname(name_prefix, name_prefix)
# exceptions are a special case, since they are documented in the
# 'exceptions' module.
elif add_module and self.env.config.add_module_names:
modname = self.options.get(
- 'module', self.env.doc_read_data.get('py_module'))
+ 'module', self.env.doc_read_data.get('py:module'))
if modname and modname != 'exceptions':
nodetext = modname + '.'
signode += addnodes.desc_addname(nodetext, nodetext)
@@ -107,7 +119,7 @@ class PyObject(ObjectDescription):
signode += addnodes.desc_parameterlist()
if retann:
signode += addnodes.desc_returns(retann, retann)
- return fullname, classname
+ return fullname, name_prefix
signode += addnodes.desc_parameterlist()
stack = [signode[-1]]
@@ -130,7 +142,7 @@ class PyObject(ObjectDescription):
raise ValueError
if retann:
signode += addnodes.desc_returns(retann, retann)
- return fullname, classname
+ return fullname, name_prefix
def get_index_text(self, modname, name):
"""
@@ -140,7 +152,7 @@ class PyObject(ObjectDescription):
def add_target_and_index(self, name_cls, sig, signode):
modname = self.options.get(
- 'module', self.env.doc_read_data.get('py_module'))
+ 'module', self.env.doc_read_data.get('py:module'))
fullname = (modname and modname + '.' or '') + name_cls[0]
# note target
if fullname not in self.state.document.ids:
@@ -169,7 +181,7 @@ class PyObject(ObjectDescription):
def after_content(self):
if self.clsname_set:
- self.env.doc_read_data['py_class'] = None
+ self.env.doc_read_data['py:class'] = None
class PyModulelevel(PyObject):
@@ -214,7 +226,7 @@ class PyClasslike(PyObject):
def before_content(self):
PyObject.before_content(self)
if self.names:
- self.env.doc_read_data['py_class'] = self.names[0][0]
+ self.env.doc_read_data['py:class'] = self.names[0][0]
self.clsname_set = True
@@ -292,8 +304,8 @@ class PyClassmember(PyObject):
def before_content(self):
PyObject.before_content(self)
lastname = self.names and self.names[-1][1]
- if lastname and not self.env.doc_read_data.get('py_class'):
- self.env.doc_read_data['py_class'] = lastname.strip('.')
+ if lastname and not self.env.doc_read_data.get('py:class'):
+ self.env.doc_read_data['py:class'] = lastname.strip('.')
self.clsname_set = True
@@ -317,7 +329,7 @@ class PyModule(Directive):
env = self.state.document.settings.env
modname = self.arguments[0].strip()
noindex = 'noindex' in self.options
- env.doc_read_data['py_module'] = modname
+ env.doc_read_data['py:module'] = modname
env.domaindata['py']['modules'][modname] = \
(env.docname, self.options.get('synopsis', ''),
self.options.get('platform', ''), 'deprecated' in self.options)
@@ -361,16 +373,16 @@ class PyCurrentModule(Directive):
env = self.state.document.settings.env
modname = self.arguments[0].strip()
if modname == 'None':
- env.doc_read_data['py_module'] = None
+ env.doc_read_data['py:module'] = None
else:
- env.doc_read_data['py_module'] = modname
+ env.doc_read_data['py:module'] = modname
return []
class PyXRefRole(XRefRole):
def process_link(self, env, refnode, has_explicit_title, title, target):
- refnode['py_module'] = env.doc_read_data.get('py_module')
- refnode['py_class'] = env.doc_read_data.get('py_class')
+ refnode['py:module'] = env.doc_read_data.get('py:module')
+ refnode['py:class'] = env.doc_read_data.get('py:class')
if not has_explicit_title:
title = title.lstrip('.') # only has a meaning for the target
target = target.lstrip('~') # only has a meaning for the title
@@ -497,8 +509,8 @@ class PythonDomain(Domain):
return make_refnode(builder, fromdocname, docname,
'module-' + target, contnode, title)
else:
- modname = node.get('py_module')
- clsname = node.get('py_class')
+ modname = node.get('py:module')
+ clsname = node.get('py:class')
searchorder = node.hasattr('refspecific') and 1 or 0
name, obj = self.find_obj(env, modname, clsname,
target, typ, searchorder)
diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py
index b291be20b..49394172e 100644
--- a/sphinx/domains/std.py
+++ b/sphinx/domains/std.py
@@ -35,7 +35,7 @@ class GenericObject(ObjectDescription):
indextemplate = ''
parse_node = None
- def parse_signature(self, sig, signode):
+ def handle_signature(self, sig, signode):
if self.parse_node:
name = self.parse_node(self.env, sig, signode)
else:
@@ -146,7 +146,7 @@ class Cmdoption(ObjectDescription):
def add_target_and_index(self, name, sig, signode):
targetname = name.replace('/', '-')
- currprogram = self.env.doc_read_data.get('std_program')
+ currprogram = self.env.doc_read_data.get('std:program')
if currprogram:
targetname = '-' + currprogram + targetname
targetname = 'cmdoption' + targetname
@@ -175,9 +175,9 @@ class Program(Directive):
env = self.state.document.settings.env
program = ws_re.sub('-', self.arguments[0].strip())
if program == 'None':
- env.doc_read_data['std_program'] = None
+ env.doc_read_data['std:program'] = None
else:
- env.doc_read_data['std_program'] = program
+ env.doc_read_data['std:program'] = program
return []
@@ -185,7 +185,7 @@ class OptionXRefRole(XRefRole):
innernodeclass = addnodes.literal_emphasis
def process_link(self, env, refnode, has_explicit_title, title, target):
- program = env.doc_read_data.get('std_program')
+ program = env.doc_read_data.get('std:program')
if not has_explicit_title:
if ' ' in title and not (title.startswith('/') or
title.startswith('-')):
diff --git a/sphinx/environment.py b/sphinx/environment.py
index 9626924f6..2ce3cc983 100644
--- a/sphinx/environment.py
+++ b/sphinx/environment.py
@@ -713,12 +713,12 @@ class BuildEnvironment:
@property
def currmodule(self):
"""Backwards compatible alias."""
- return self.doc_read_data.get('py_module')
+ return self.doc_read_data.get('py:module')
@property
def currclass(self):
"""Backwards compatible alias."""
- return self.doc_read_data.get('py_class')
+ return self.doc_read_data.get('py:class')
def new_serialno(self, category=''):
"""Return a serial number, e.g. for index entry targets."""
diff --git a/sphinx/errors.py b/sphinx/errors.py
index 684101c68..ca70fe4b2 100644
--- a/sphinx/errors.py
+++ b/sphinx/errors.py
@@ -46,3 +46,11 @@ class ExtensionError(SphinxError):
class ThemeError(SphinxError):
category = 'Theme error'
+
+
+class PycodeError(Exception):
+ def __str__(self):
+ res = self.args[0]
+ if len(self.args) > 1:
+ res += ' (exception was: %r)' % self.args[1]
+ return res
diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py
index b74b8f9be..53625dde8 100644
--- a/sphinx/ext/autodoc.py
+++ b/sphinx/ext/autodoc.py
@@ -562,9 +562,9 @@ class Documenter(object):
do all members, else those given by *self.options.members*.
"""
# set current namespace for finding members
- self.env.doc_read_data['autodoc_module'] = self.modname
+ self.env.doc_read_data['autodoc:module'] = self.modname
if self.objpath:
- self.env.doc_read_data['autodoc_class'] = self.objpath[0]
+ self.env.doc_read_data['autodoc:class'] = self.objpath[0]
want_all = all_members or self.options.inherited_members or \
self.options.members is ALL
@@ -605,8 +605,8 @@ class Documenter(object):
check_module=members_check_module)
# reset current objects
- self.env.doc_read_data['autodoc_module'] = None
- self.env.doc_read_data['autodoc_class'] = None
+ self.env.doc_read_data['autodoc:module'] = None
+ self.env.doc_read_data['autodoc:class'] = None
def generate(self, more_content=None, real_modname=None,
check_module=False, all_members=False):
@@ -764,10 +764,10 @@ class ModuleLevelDocumenter(Documenter):
else:
# if documenting a toplevel object without explicit module,
# it can be contained in another auto directive ...
- modname = self.env.doc_read_data.get('autodoc_module')
+ modname = self.env.doc_read_data.get('autodoc:module')
# ... or in the scope of a module directive
if not modname:
- modname = self.env.doc_read_data.get('py_module')
+ modname = self.env.doc_read_data.get('py:module')
# ... else, it stays None, which means invalid
return modname, parents + [base]
@@ -786,10 +786,10 @@ class ClassLevelDocumenter(Documenter):
# if documenting a class-level object without path,
# there must be a current class, either from a parent
# auto directive ...
- mod_cls = self.env.doc_read_data.get('autodoc_class')
+ mod_cls = self.env.doc_read_data.get('autodoc:class')
# ... or from a class directive
if mod_cls is None:
- mod_cls = self.env.doc_read_data.get('py_class')
+ mod_cls = self.env.doc_read_data.get('py:class')
# ... if still None, there's no way to know
if mod_cls is None:
return None, []
@@ -797,9 +797,9 @@ class ClassLevelDocumenter(Documenter):
parents = [cls]
# if the module name is still missing, get it like above
if not modname:
- modname = self.env.doc_read_data.get('autodoc_module')
+ modname = self.env.doc_read_data.get('autodoc:module')
if not modname:
- modname = self.env.doc_read_data.get('py_module')
+ modname = self.env.doc_read_data.get('py:module')
# ... else, it stays None, which means invalid
return modname, parents + [base]
diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py
index 0e3bdf16e..99d895870 100644
--- a/sphinx/ext/autosummary/__init__.py
+++ b/sphinx/ext/autosummary/__init__.py
@@ -228,7 +228,7 @@ class Autosummary(Directive):
env = self.state.document.settings.env
prefixes = ['']
- currmodule = env.doc_read_data.get('py_module')
+ currmodule = env.doc_read_data.get('py:module')
if currmodule:
prefixes.insert(0, currmodule)
diff --git a/sphinx/ext/inheritance_diagram.py b/sphinx/ext/inheritance_diagram.py
index 236a79335..1fb8b0b2f 100644
--- a/sphinx/ext/inheritance_diagram.py
+++ b/sphinx/ext/inheritance_diagram.py
@@ -284,7 +284,7 @@ class InheritanceDiagram(Directive):
# Create a graph starting with the list of classes
try:
graph = InheritanceGraph(class_names,
- env.doc_read_data.get('py_module'))
+ env.doc_read_data.get('py:module'))
except InheritanceException, err:
return [node.document.reporter.warning(err.args[0],
line=self.lineno)]
diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py
new file mode 100644
index 000000000..6be03a468
--- /dev/null
+++ b/sphinx/ext/viewcode.py
@@ -0,0 +1,162 @@
+# -*- coding: utf-8 -*-
+"""
+ sphinx.ext.viewcode
+ ~~~~~~~~~~~~~~~~~~~
+
+ Add links to module code in Python object descriptions.
+
+ :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+from docutils import nodes
+
+from sphinx import addnodes
+from sphinx.util import make_refnode
+from sphinx.pycode import ModuleAnalyzer
+
+
+def doctree_read(app, doctree):
+ env = app.builder.env
+ if not hasattr(env, '_viewcode_modules'):
+ env._viewcode_modules = {}
+
+ def has_tag(modname, fullname, docname):
+ entry = env._viewcode_modules.get(modname, None)
+ if entry is None:
+ try:
+ analyzer = ModuleAnalyzer.for_module(modname)
+ except Exception:
+ env._viewcode_modules[modname] = False
+ return
+ analyzer.find_tags()
+ entry = analyzer.code, analyzer.tags, {}
+ env._viewcode_modules[modname] = entry
+ elif entry is False:
+ return
+ code, tags, used = entry
+ if fullname in tags:
+ used[fullname] = docname
+ return True
+
+ for objnode in doctree.traverse(addnodes.desc):
+ if objnode['domain'] != 'py':
+ continue
+ names = set()
+ for signode in objnode.traverse(addnodes.desc_signature):
+ modname = signode['module']
+ if not modname:
+ continue
+ fullname = signode['fullname']
+ if not has_tag(modname, fullname, env.docname):
+ continue
+ if fullname in names:
+ # only one link per name, please
+ continue
+ names.add(fullname)
+ pagename = '_modules/' + modname.replace('.', '/')
+ onlynode = addnodes.only(expr='html')
+ onlynode += addnodes.pending_xref(
+ '', reftype='viewcode', refdomain='std', refexplicit=False,
+ reftarget=pagename, refid=fullname,
+ refdoc=env.docname)
+ onlynode[0] += nodes.inline('', _('[source]'),
+ classes=['viewcode-link'])
+ signode += onlynode
+
+
+def missing_reference(app, env, node, contnode):
+ # resolve our "viewcode" reference nodes -- they need special treatment
+ if node['reftype'] == 'viewcode':
+ return make_refnode(app.builder, node['refdoc'], node['reftarget'],
+ node['refid'], contnode)
+
+
+def collect_pages(app):
+ env = app.builder.env
+ if not hasattr(env, '_viewcode_modules'):
+ return
+ highlighter = app.builder.highlighter
+ urito = app.builder.get_relative_uri
+
+ modnames = set(env._viewcode_modules)
+
+ for modname, (code, tags, used) in env._viewcode_modules.iteritems():
+ # construct a page name for the highlighted source
+ pagename = '_modules/' + modname.replace('.', '/')
+ # highlight the source using the builder's highlighter
+ highlighted = highlighter.highlight_block(code, 'python', False)
+ # split the code into lines
+ lines = highlighted.splitlines()
+ # split off wrap markup from the first line of the actual code
+ before, after = lines[0].split('<pre>')
+ lines[0:1] = [before + '<pre>', after]
+ # nothing to do for the last line; it always starts with </pre> anyway
+ # now that we have code lines (starting at index 1), insert anchors for
+ # the collected tags (HACK: this only works if the tag boundaries are
+ # properly nested!)
+ for name, docname in used.iteritems():
+ type, start, end = tags[name]
+ backlink = urito(pagename, docname) + '#' + modname + '.' + name
+ lines[start] = (
+ '<div class="viewcode-block" id="%s"><a class="viewcode-back" '
+ 'href="%s">%s</a>' % (name, backlink, _('[docs]')) + lines[start])
+ lines[end - 1] += '</div>'
+ # try to find parents (for submodules)
+ parents = []
+ parent = modname
+ while '.' in parent:
+ parent = parent.rsplit('.', 1)[0]
+ if parent in modnames:
+ parents.append({
+ 'link': urito(pagename, '_modules/' +
+ parent.replace('.', '/')),
+ 'title': parent})
+ parents.append({'link': urito(pagename, '_modules/index'),
+ 'title': _('Module code')})
+ parents.reverse()
+ # putting it all together
+ context = {
+ 'parents': parents,
+ 'title': modname,
+ 'body': _('<h2>Source code for %s</h2>') % modname + '\n'.join(lines)
+ }
+ app.builder.info(' '+pagename, nonl=1)
+ yield (pagename, context, 'page.html')
+
+ if not modnames:
+ return
+
+ app.builder.info(' _modules/index')
+ html = ['\n']
+ # the stack logic is needed for using nested lists for submodules
+ stack = ['']
+ for modname in sorted(modnames):
+ if modname.startswith(stack[-1]):
+ stack.append(modname + '.')
+ html.append('<ul>')
+ else:
+ stack.pop()
+ while not modname.startswith(stack[-1]):
+ stack.pop()
+ html.append('</ul>')
+ stack.append(modname + '.')
+ html.append('<li><a href="%s">%s</a></li>\n' % (
+ urito('_modules/index', '_modules/' + modname.replace('.', '/')),
+ modname))
+ html.append('</ul>' * (len(stack) - 1))
+ context = {
+ 'title': _('Overview: module code'),
+ 'body': _('<h2>All modules for which code is available</h2>') + \
+ ''.join(html),
+ }
+
+ yield ('_modules/index', context, 'page.html')
+
+
+def setup(app):
+ app.connect('doctree-read', doctree_read)
+ app.connect('html-collect-pages', collect_pages)
+ app.connect('missing-reference', missing_reference)
+ #app.add_config_value('viewcode_include_modules', [], 'env')
+ #app.add_config_value('viewcode_exclude_modules', [], 'env')
diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py
index 19fd09e13..836cba802 100644
--- a/sphinx/pycode/__init__.py
+++ b/sphinx/pycode/__init__.py
@@ -14,8 +14,10 @@ import sys
from os import path
from cStringIO import StringIO
+from sphinx.errors import PycodeError
from sphinx.pycode import nodes
from sphinx.pycode.pgen2 import driver, token, tokenize, parse, literals
+from sphinx.util import get_module_source
from sphinx.util.docstrings import prepare_docstring, prepare_commentdoc
@@ -136,14 +138,6 @@ class AttrDocVisitor(nodes.NodeVisitor):
self.collected[namespace, name] = docstring
-class PycodeError(Exception):
- def __str__(self):
- res = self.args[0]
- if len(self.args) > 1:
- res += ' (exception was: %r)' % self.args[1]
- return res
-
-
class ModuleAnalyzer(object):
# cache for analyzer objects -- caches both by module and file name
cache = {}
@@ -173,33 +167,11 @@ class ModuleAnalyzer(object):
return entry
try:
- if modname not in sys.modules:
- try:
- __import__(modname)
- except ImportError, err:
- raise PycodeError('error importing %r' % modname, err)
- mod = sys.modules[modname]
- if hasattr(mod, '__loader__'):
- try:
- source = mod.__loader__.get_source(modname)
- except Exception, err:
- raise PycodeError('error getting source for %r' % modname,
- err)
+ type, source = get_module_source(modname)
+ if type == 'string':
obj = cls.for_string(source, modname)
- cls.cache['module', modname] = obj
- return obj
- filename = getattr(mod, '__file__', None)
- if filename is None:
- raise PycodeError('no source found for module %r' % modname)
- filename = path.normpath(path.abspath(filename))
- lfilename = filename.lower()
- if lfilename.endswith('.pyo') or lfilename.endswith('.pyc'):
- filename = filename[:-1]
- elif not lfilename.endswith('.py'):
- raise PycodeError('source is not a .py file: %r' % filename)
- if not path.isfile(filename):
- raise PycodeError('source file is not present: %r' % filename)
- obj = cls.for_file(filename, modname)
+ else:
+ obj = cls.for_file(source, modname)
except PycodeError, err:
cls.cache['module', modname] = err
raise
@@ -214,6 +186,11 @@ class ModuleAnalyzer(object):
# file-like object yielding source lines
self.source = source
+ # cache the source code as well
+ pos = self.source.tell()
+ self.code = self.source.read()
+ self.source.seek(pos)
+
# will be filled by tokenize()
self.tokens = None
# will be filled by parse()
diff --git a/sphinx/themes/basic/static/basic.css b/sphinx/themes/basic/static/basic.css
index bd0a85446..f04023bf9 100644
--- a/sphinx/themes/basic/static/basic.css
+++ b/sphinx/themes/basic/static/basic.css
@@ -368,6 +368,22 @@ dl.glossary dt {
margin-left: 1.5em;
}
+.viewcode-link {
+ float: right;
+}
+
+.viewcode-back {
+ float: right;
+ font-family: sans-serif;
+}
+
+div.viewcode-block:target {
+ background-color: #f4debf;
+ border: 1px solid #D5BB73;
+ margin: -1px -10px;
+ padding: 0 10px;
+}
+
/* -- code displays --------------------------------------------------------- */
pre {
diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py
index 012ca2ba3..7381d5ead 100644
--- a/sphinx/util/__init__.py
+++ b/sphinx/util/__init__.py
@@ -508,6 +508,38 @@ def make_refnode(builder, fromdocname, todocname, targetid, child, title=None):
return node
+def get_module_source(modname):
+ """Try to find the source code for a module.
+
+ Can return ('file', 'filename') in which case the source is in the given
+ file, or ('string', 'source') which which case the source is the string.
+ """
+ if modname not in sys.modules:
+ try:
+ __import__(modname)
+ except Exception, err:
+ raise PycodeError('error importing %r' % modname, err)
+ mod = sys.modules[modname]
+ if hasattr(mod, '__loader__'):
+ try:
+ source = mod.__loader__.get_source(modname)
+ except Exception, err:
+ raise PycodeError('error getting source for %r' % modname, err)
+ return 'string', source
+ filename = getattr(mod, '__file__', None)
+ if filename is None:
+ raise PycodeError('no source found for module %r' % modname)
+ filename = path.normpath(path.abspath(filename))
+ lfilename = filename.lower()
+ if lfilename.endswith('.pyo') or lfilename.endswith('.pyc'):
+ filename = filename[:-1]
+ elif not lfilename.endswith('.py'):
+ raise PycodeError('source is not a .py file: %r' % filename)
+ if not path.isfile(filename):
+ raise PycodeError('source file is not present: %r' % filename)
+ return 'file', filename
+
+
try:
any = any
except NameError:
diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py
index 2d311cac3..297239ae9 100644
--- a/tests/test_autodoc.py
+++ b/tests/test_autodoc.py
@@ -97,28 +97,28 @@ def test_parse_name():
verify('function', 'util.raises', ('util', ['raises'], None, None))
verify('function', 'util.raises(exc) -> None',
('util', ['raises'], 'exc', 'None'))
- directive.env.doc_read_data['autodoc_module'] = 'util'
+ directive.env.doc_read_data['autodoc:module'] = 'util'
verify('function', 'raises', ('util', ['raises'], None, None))
- del directive.env.doc_read_data['autodoc_module']
- directive.env.doc_read_data['py_module'] = 'util'
+ del directive.env.doc_read_data['autodoc:module']
+ directive.env.doc_read_data['py:module'] = 'util'
verify('function', 'raises', ('util', ['raises'], None, None))
verify('class', 'TestApp', ('util', ['TestApp'], None, None))
# for members
- directive.env.doc_read_data['py_module'] = 'foo'
+ directive.env.doc_read_data['py:module'] = 'foo'
verify('method', 'util.TestApp.cleanup',
('util', ['TestApp', 'cleanup'], None, None))
- directive.env.doc_read_data['py_module'] = 'util'
- directive.env.doc_read_data['py_class'] = 'Foo'
- directive.env.doc_read_data['autodoc_class'] = 'TestApp'
+ directive.env.doc_read_data['py:module'] = 'util'
+ directive.env.doc_read_data['py:class'] = 'Foo'
+ directive.env.doc_read_data['autodoc:class'] = 'TestApp'
verify('method', 'cleanup', ('util', ['TestApp', 'cleanup'], None, None))
verify('method', 'TestApp.cleanup',
('util', ['TestApp', 'cleanup'], None, None))
# and clean up
- del directive.env.doc_read_data['py_module']
- del directive.env.doc_read_data['py_class']
- del directive.env.doc_read_data['autodoc_class']
+ del directive.env.doc_read_data['py:module']
+ del directive.env.doc_read_data['py:class']
+ del directive.env.doc_read_data['autodoc:class']
def test_format_signature():
@@ -353,7 +353,7 @@ def test_generate():
'function', 'util.foobar', more_content=None)
# test auto and given content mixing
- directive.env.doc_read_data['py_module'] = 'test_autodoc'
+ directive.env.doc_read_data['py:module'] = 'test_autodoc'
assert_result_contains(' Function.', 'method', 'Class.meth')
add_content = ViewList()
add_content.append('Content.', '', 0)
@@ -437,7 +437,7 @@ def test_generate():
'attribute', 'test_autodoc.Class.descr')
# test generation for C modules (which have no source file)
- directive.env.doc_read_data['py_module'] = 'time'
+ directive.env.doc_read_data['py:module'] = 'time'
assert_processes([('function', 'time.asctime')], 'function', 'asctime')
assert_processes([('function', 'time.asctime')], 'function', 'asctime')