diff options
author | Georg Brandl <georg@python.org> | 2010-01-13 23:35:13 +0000 |
---|---|---|
committer | Georg Brandl <georg@python.org> | 2010-01-13 23:35:13 +0000 |
commit | 07f295df7ce486b36d950614080b1267dc709dfa (patch) | |
tree | fa4d59c28c0c92ffa42b18f94438f9312f89b1f9 | |
parent | a52885d078a9dcc921b113f65b7d3fde76709ded (diff) | |
parent | edb89b1940326e9e5e7488db91f46355e3871346 (diff) | |
download | sphinx-git-07f295df7ce486b36d950614080b1267dc709dfa.tar.gz |
merge
-rw-r--r-- | CHANGES | 4 | ||||
-rw-r--r-- | doc/conf.py | 2 | ||||
-rw-r--r-- | doc/ext/appapi.rst | 10 | ||||
-rw-r--r-- | doc/ext/viewcode.rst | 19 | ||||
-rw-r--r-- | doc/extensions.rst | 1 | ||||
-rw-r--r-- | sphinx/application.py | 1 | ||||
-rw-r--r-- | sphinx/builders/html.py | 6 | ||||
-rw-r--r-- | sphinx/directives/__init__.py | 8 | ||||
-rw-r--r-- | sphinx/directives/other.py | 3 | ||||
-rw-r--r-- | sphinx/domains/c.py | 2 | ||||
-rw-r--r-- | sphinx/domains/python.py | 74 | ||||
-rw-r--r-- | sphinx/domains/std.py | 10 | ||||
-rw-r--r-- | sphinx/environment.py | 4 | ||||
-rw-r--r-- | sphinx/errors.py | 8 | ||||
-rw-r--r-- | sphinx/ext/autodoc.py | 20 | ||||
-rw-r--r-- | sphinx/ext/autosummary/__init__.py | 2 | ||||
-rw-r--r-- | sphinx/ext/inheritance_diagram.py | 2 | ||||
-rw-r--r-- | sphinx/ext/viewcode.py | 162 | ||||
-rw-r--r-- | sphinx/pycode/__init__.py | 45 | ||||
-rw-r--r-- | sphinx/themes/basic/static/basic.css | 16 | ||||
-rw-r--r-- | sphinx/util/__init__.py | 32 | ||||
-rw-r--r-- | tests/test_autodoc.py | 24 |
22 files changed, 351 insertions, 104 deletions
@@ -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') |