mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
merge
This commit is contained in:
commit
07f295df7c
4
CHANGES
4
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
|
||||
|
@ -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']
|
||||
|
@ -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.
|
||||
|
19
doc/ext/viewcode.rst
Normal file
19
doc/ext/viewcode.rst
Normal file
@ -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.
|
@ -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
|
||||
|
@ -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',
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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('-')):
|
||||
|
@ -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."""
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)]
|
||||
|
162
sphinx/ext/viewcode.py
Normal file
162
sphinx/ext/viewcode.py
Normal file
@ -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')
|
@ -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()
|
||||
|
@ -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 {
|
||||
|
@ -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:
|
||||
|
@ -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')
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user