diff options
| author | gbrandl <devnull@localhost> | 2009-03-14 21:14:48 +0100 |
|---|---|---|
| committer | gbrandl <devnull@localhost> | 2009-03-14 21:14:48 +0100 |
| commit | ab97ffb4d30111cd2a8af3eb0bbff5f503347a31 (patch) | |
| tree | e168143c124212589c8ef4121971cbe7a5ef9b1e | |
| parent | 68a77dd93ffb50c4712bbd8aeb85977b67f63859 (diff) | |
| parent | 45deb642412529c9f00ae41ad39fd5a9c6a9c762 (diff) | |
| download | sphinx-ab97ffb4d30111cd2a8af3eb0bbff5f503347a31.tar.gz | |
merge with autosummary branch
| -rw-r--r-- | setup.py | 3 | ||||
| -rwxr-xr-x | sphinx-autogen.py | 15 | ||||
| -rw-r--r-- | sphinx/cmdline.py | 16 | ||||
| -rw-r--r-- | sphinx/ext/autosummary/LICENSE.txt | 31 | ||||
| -rw-r--r-- | sphinx/ext/autosummary/__init__.py | 318 | ||||
| -rw-r--r-- | sphinx/ext/autosummary/generate.py | 215 | ||||
| -rw-r--r-- | sphinx/ext/autosummary/templates/module | 39 |
7 files changed, 635 insertions, 2 deletions
@@ -178,7 +178,8 @@ setup( entry_points={ 'console_scripts': [ 'sphinx-build = sphinx:main', - 'sphinx-quickstart = sphinx.quickstart:main' + 'sphinx-quickstart = sphinx.quickstart:main', + 'sphinx-autogen = sphinx.ext.autosummary.generate:main', ], 'distutils.commands': [ 'build_sphinx = sphinx.setup_command:BuildDoc', diff --git a/sphinx-autogen.py b/sphinx-autogen.py new file mode 100755 index 00000000..494f4d85 --- /dev/null +++ b/sphinx-autogen.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + Sphinx - Python documentation toolchain + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: 2007-2009 by Georg Brandl. + :license: BSD. +""" + +import sys + +if __name__ == '__main__': + from sphinx.ext.autosummary.generate import main + sys.exit(main(sys.argv)) diff --git a/sphinx/cmdline.py b/sphinx/cmdline.py index 497c118e..79bd9751 100644 --- a/sphinx/cmdline.py +++ b/sphinx/cmdline.py @@ -43,6 +43,8 @@ new and changed files -C -- use no config file at all, only -D options -D <setting=value> -- override a setting in configuration -A <name=value> -- pass a value into the templates, for HTML builder + -g <path> -- auto-generate docs with sphinx.ext.autosummary + for autosummary directives in sources found in path -N -- do not do colored output -q -- no output on stdout, just warnings on stderr -Q -- no output at all, not even warnings @@ -61,7 +63,7 @@ def main(argv): nocolor() try: - opts, args = getopt.getopt(argv[1:], 'ab:t:d:c:CD:A:NEqQWw:P') + opts, args = getopt.getopt(argv[1:], 'ab:t:d:c:CD:A:g:NEqQWw:P') allopts = set(opt[0] for opt in opts) srcdir = confdir = path.abspath(args[0]) if not path.isdir(srcdir): @@ -143,6 +145,18 @@ def main(argv): except ValueError: pass htmlcontext[key] = val + elif opt == '-g': + # XXX XXX XXX + source_filenames = [path.join(srcdir, f) + for f in os.listdir(srcdir) if f.endswith('.rst')] + if val is None: + print >>sys.stderr, \ + 'Error: you must provide a destination directory ' \ + 'for autodoc generation.' + return 1 + p = path.abspath(val) + from sphinx.ext.autosummary.generate import generate_autosummary_docs + generate_autosummary_docs(source_filenames, p) elif opt == '-N': nocolor() elif opt == '-E': diff --git a/sphinx/ext/autosummary/LICENSE.txt b/sphinx/ext/autosummary/LICENSE.txt new file mode 100644 index 00000000..aa1bf333 --- /dev/null +++ b/sphinx/ext/autosummary/LICENSE.txt @@ -0,0 +1,31 @@ + The files + - __init__.py + - generate.py + - templates/module.html + + have the following license: + +Copyright (C) 2008 Stefan van der Walt <stefan@mentat.za.net>, Pauli Virtanen <pav@iki.fi>, Christopher Perkins <chris@percious.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py new file mode 100644 index 00000000..8775fe86 --- /dev/null +++ b/sphinx/ext/autosummary/__init__.py @@ -0,0 +1,318 @@ +# -*- coding: utf-8 -*- +""" + sphinx.ext.autosummary + ~~~~~~~~~~~~~~~~~~~~~~ + + Sphinx extension that adds an autosummary:: directive, which can be + used to generate function/method/attribute/etc. summary lists, similar + to those output eg. by Epydoc and other API doc generation tools. + + An :autolink: role is also provided. + + autosummary directive + --------------------- + + The autosummary directive has the form:: + + .. autosummary:: + :nosignatures: + :toctree: generated/ + + module.function_1 + module.function_2 + ... + + and it generates an output table (containing signatures, optionally) + + ======================== ============================================= + module.function_1(args) Summary line from the docstring of function_1 + module.function_2(args) Summary line from the docstring + ... + ======================== ============================================= + + If the :toctree: option is specified, files matching the function names + are inserted to the toctree with the given prefix: + + generated/module.function_1 + generated/module.function_2 + ... + + Note: The file names contain the module:: or currentmodule:: prefixes. + + .. seealso:: autosummary_generate.py + + + autolink role + ------------- + + The autolink role functions as ``:obj:`` when the name referred can be + resolved to a Python object, and otherwise it becomes simple emphasis. + This can be used as the default role to make links 'smart'. + + :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import os +import re +import sys +import inspect +import posixpath + +from docutils.parsers.rst import directives +from docutils.statemachine import ViewList +from docutils import nodes + +from sphinx import addnodes, roles +from sphinx.util import patfilter + + +# -- autosummary_toc node ------------------------------------------------------ + +class autosummary_toc(nodes.comment): + pass + +def process_autosummary_toc(app, doctree): + """ + Insert items described in autosummary:: to the TOC tree, but do + not generate the toctree:: list. + """ + env = app.builder.env + crawled = {} + def crawl_toc(node, depth=1): + crawled[node] = True + for j, subnode in enumerate(node): + try: + if (isinstance(subnode, autosummary_toc) + and isinstance(subnode[0], addnodes.toctree)): + env.note_toctree(env.docname, subnode[0]) + continue + except IndexError: + continue + if not isinstance(subnode, nodes.section): + continue + if subnode not in crawled: + crawl_toc(subnode, depth+1) + crawl_toc(doctree) + +def autosummary_toc_visit_html(self, node): + """Hide autosummary toctree list in HTML output.""" + raise nodes.SkipNode + +def autosummary_toc_visit_latex(self, node): + """Show autosummary toctree (= put the referenced pages here) in Latex.""" + pass + +def autosummary_toc_depart_noop(self, node): + pass + + +# -- .. autosummary:: ---------------------------------------------------------- + +def autosummary_directive(dirname, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + """ + Pretty table containing short signatures and summaries of functions etc. + + autosummary also generates a (hidden) toctree:: node. + + """ + + names = [] + names += [x.strip() for x in content if x.strip()] + + table, warnings, real_names = get_autosummary(names, state, + 'nosignatures' in options) + node = table + + env = state.document.settings.env + suffix = env.config.source_suffix + all_docnames = env.found_docs.copy() + dirname = posixpath.dirname(env.docname) + + if 'toctree' in options: + tree_prefix = options['toctree'].strip() + docnames = [] + for name in names: + name = real_names.get(name, name) + + docname = tree_prefix + name + if docname.endswith(suffix): + docname = docname[:-len(suffix)] + docname = posixpath.normpath(posixpath.join(dirname, docname)) + if docname not in env.found_docs: + warnings.append(state.document.reporter.warning( + 'toctree references unknown document %r' % docname, + line=lineno)) + docnames.append(docname) + + tocnode = addnodes.toctree() + tocnode['includefiles'] = docnames + tocnode['maxdepth'] = -1 + tocnode['glob'] = None + + tocnode = autosummary_toc('', '', tocnode) + return warnings + [node] + [tocnode] + else: + return warnings + [node] + + +def get_autosummary(names, state, no_signatures=False): + """ + Generate a proper table node for autosummary:: directive. + + Parameters + ---------- + names : list of str + Names of Python objects to be imported and added to the table. + document : document + Docutils document object + + """ + document = state.document + + real_names = {} + warnings = [] + + prefixes = [''] + prefixes.insert(0, document.settings.env.currmodule) + + table = nodes.table('') + group = nodes.tgroup('', cols=2) + table.append(group) + group.append(nodes.colspec('', colwidth=30)) + group.append(nodes.colspec('', colwidth=70)) + body = nodes.tbody('') + group.append(body) + + def append_row(*column_texts): + row = nodes.row('') + for text in column_texts: + node = nodes.paragraph('') + vl = ViewList() + vl.append(text, '<autosummary>') + state.nested_parse(vl, 0, node) + row.append(nodes.entry('', node)) + body.append(row) + + for name in names: + try: + obj, real_name = import_by_name(name, prefixes=prefixes) + except ImportError: + warnings.append(document.reporter.warning( + 'failed to import %s' % name)) + append_row(':obj:`%s`' % name, '') + continue + + real_names[name] = real_name + + title = '' + qualifier = 'obj' + col1 = ':'+qualifier+':`%s <%s>`' % (name, real_name) + col2 = title + append_row(col1, col2) + + return table, warnings, real_names + +def import_by_name(name, prefixes=[None]): + """ + Import a Python object that has the given name, under one of the prefixes. + + Parameters + ---------- + name : str + Name of a Python object, eg. 'numpy.ndarray.view' + prefixes : list of (str or None), optional + Prefixes to prepend to the name (None implies no prefix). + The first prefixed name that results to successful import is used. + + Returns + ------- + obj + The imported object + name + Name of the imported object (useful if `prefixes` was used) + + """ + for prefix in prefixes: + try: + if prefix: + prefixed_name = '.'.join([prefix, name]) + else: + prefixed_name = name + return _import_by_name(prefixed_name), prefixed_name + except ImportError: + pass + raise ImportError + +def _import_by_name(name): + """Import a Python object given its full name.""" + try: + # try first interpret `name` as MODNAME.OBJ + name_parts = name.split('.') + try: + modname = '.'.join(name_parts[:-1]) + __import__(modname) + return getattr(sys.modules[modname], name_parts[-1]) + except (ImportError, IndexError, AttributeError): + pass + + # ... then as MODNAME, MODNAME.OBJ1, MODNAME.OBJ1.OBJ2, ... + last_j = 0 + modname = None + for j in reversed(range(1, len(name_parts)+1)): + last_j = j + modname = '.'.join(name_parts[:j]) + try: + __import__(modname) + except ImportError: + continue + if modname in sys.modules: + break + + if last_j < len(name_parts): + obj = sys.modules[modname] + for obj_name in name_parts[last_j:]: + obj = getattr(obj, obj_name) + return obj + else: + return sys.modules[modname] + except (ValueError, ImportError, AttributeError, KeyError), e: + raise ImportError(e) + + +# -- :autolink: (smart default role) ------------------------------------------- + +def autolink_role(typ, rawtext, etext, lineno, inliner, + options={}, content=[]): + """ + Smart linking role. + + Expands to ':obj:`text`' if `text` is an object that can be imported; + otherwise expands to '*text*'. + """ + r = roles.xfileref_role('obj', rawtext, etext, lineno, inliner, + options, content) + pnode = r[0][0] + + prefixes = [None] + #prefixes.insert(0, inliner.document.settings.env.currmodule) + try: + obj, name = import_by_name(pnode['reftarget'], prefixes) + except ImportError: + content = pnode[0] + r[0][0] = nodes.emphasis(rawtext, content[0].astext(), + classes=content['classes']) + return r + + +def setup(app): + app.add_directive('autosummary', autosummary_directive, True, (0, 0, False), + toctree=directives.unchanged, + nosignatures=directives.flag) + app.add_role('autolink', autolink_role) + + app.add_node(autosummary_toc, + html=(autosummary_toc_visit_html, autosummary_toc_depart_noop), + latex=(autosummary_toc_visit_latex, autosummary_toc_depart_noop)) + app.connect('doctree-read', process_autosummary_toc) diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py new file mode 100644 index 00000000..178a2870 --- /dev/null +++ b/sphinx/ext/autosummary/generate.py @@ -0,0 +1,215 @@ +# -*- coding: utf-8 -*- +""" + sphinx.ext.autosummary.generate + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Usable as a library or script to generate automatic RST source files for + items referred to in autosummary:: directives. + + Each generated RST file contains a single auto*:: directive which + extracts the docstring of the referred item. + + Example Makefile rule:: + + generate: + sphinx-autogen source/*.rst source/generated + + :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" +import os +import re +import sys +import glob +import inspect + +from jinja2 import Environment, PackageLoader + +from sphinx.ext.autosummary import import_by_name +from sphinx.util import ensuredir + +# create our own templating environment, for module template only +env = Environment(loader=PackageLoader('sphinx.ext.autosummary', 'templates')) + + +def generate_autosummary_docs(sources, output_dir=None): + # read + names = {} + for name, loc in get_documented(sources).items(): + for (filename, sec_title, keyword, toctree) in loc: + if toctree is not None: + path = os.path.join(os.path.dirname(filename), toctree) + names[name] = os.path.abspath(path) + + # write + for name, path in sorted(names.items()): + if output_dir is not None: + path = output_dir + + ensuredir(path) + + try: + obj, name = import_by_name(name) + except ImportError, e: + print >>sys.stderr, 'Failed to import %r: %s' % (name, e) + continue + + fn = os.path.join(path, '%s.rst' % name) + # skip it if it exists + if os.path.isfile(fn): + continue + + f = open(fn, 'w') + + try: + if inspect.ismodule(obj): + tmpl = env.get_template('module') + functions = [getattr(obj, item).__name__ + for item in dir(obj) + if inspect.isfunction(getattr(obj, item))] + classes = [getattr(obj, item).__name__ + for item in dir(obj) + if inspect.isclass(getattr(obj, item)) + and not issubclass(getattr(obj, item), Exception)] + exceptions = [getattr(obj, item).__name__ + for item in dir(obj) + if inspect.isclass(getattr(obj, item)) + and issubclass(getattr(obj, item), Exception)] + rendered = tmpl.render(name=name, + functions=functions, + classes=classes, + exceptions=exceptions, + len_functions=len(functions), + len_classes=len(classes), + len_exceptions=len(exceptions)) + f.write(rendered) + else: + f.write('%s\n%s\n\n' % (name, '='*len(name))) + + if inspect.isclass(obj): + if issubclass(obj, Exception): + f.write(format_modulemember(name, 'autoexception')) + else: + f.write(format_modulemember(name, 'autoclass')) + elif inspect.ismethod(obj) or inspect.ismethoddescriptor(obj): + f.write(format_classmember(name, 'automethod')) + elif callable(obj): + f.write(format_modulemember(name, 'autofunction')) + elif hasattr(obj, '__get__'): + f.write(format_classmember(name, 'autoattribute')) + else: + f.write(format_modulemember(name, 'autofunction')) + finally: + f.close() + + +def format_modulemember(name, directive): + parts = name.split('.') + mod, name = '.'.join(parts[:-1]), parts[-1] + return '.. currentmodule:: %s\n\n.. %s:: %s\n' % (mod, directive, name) + + +def format_classmember(name, directive): + parts = name.split('.') + mod, name = '.'.join(parts[:-2]), '.'.join(parts[-2:]) + return '.. currentmodule:: %s\n\n.. %s:: %s\n' % (mod, directive, name) + + +title_underline_re = re.compile('^[-=*_^#]{3,}\s*$') +autodoc_re = re.compile(r'.. auto(function|method|attribute|class|exception' + '|module)::\s*([A-Za-z0-9_.]+)\s*$') +autosummary_re = re.compile(r'^\.\.\s+autosummary::\s*') +module_re = re.compile(r'^\.\.\s+(current)?module::\s*([a-zA-Z0-9_.]+)\s*$') +autosummary_item_re = re.compile(r'^\s+([_a-zA-Z][a-zA-Z0-9_.]*)\s*') +toctree_arg_re = re.compile(r'^\s+:toctree:\s*(.*?)\s*$') + +def get_documented(filenames): + """ + Find out what items are documented in the given filenames. + + Returns a dict of list of (filename, title, keyword, toctree) Keys are + documented names of objects. The value is a list of locations where the + object was documented. Each location is a tuple of filename, the current + section title, the name of the directive, and the value of the :toctree: + argument (if present) of the directive. + """ + + documented = {} + + for filename in filenames: + current_title = [] + last_line = None + toctree = None + current_module = None + in_autosummary = False + + f = open(filename, 'r') + for line in f: + try: + if in_autosummary: + m = toctree_arg_re.match(line) + if m: + toctree = m.group(1) + continue + + if line.strip().startswith(':'): + continue # skip options + + m = autosummary_item_re.match(line) + + if m: + name = m.group(1).strip() + if current_module and \ + not name.startswith(current_module + '.'): + name = '%s.%s' % (current_module, name) + documented.setdefault(name, []).append( + (filename, current_title, 'autosummary', toctree)) + continue + if line.strip() == '': + continue + in_autosummary = False + + m = autosummary_re.match(line) + if m: + in_autosummary = True + continue + + m = autodoc_re.search(line) + if m: + name = m.group(2).strip() + if current_module and \ + not name.startswith(current_module + '.'): + name = '%s.%s' % (current_module, name) + if m.group(1) == 'module': + current_module = name + documented.setdefault(name, []).append( + (filename, current_title, 'auto' + m.group(1), None)) + continue + + m = title_underline_re.match(line) + if m and last_line: + current_title = last_line.strip() + continue + + m = module_re.match(line) + if m: + current_module = m.group(2) + continue + finally: + last_line = line + return documented + + +def main(args=None): + if args is None: + args = sys.argv[1:] + + if len(args) < 2: + print >>sys.stderr, 'usage: %s sourcefile ... outputdir' % sys.argv[0] + + print 'generating docs from:', ', '.join(args[:-1]) + generate_autosummary_docs(args[:-1], args[-1]) + + +if __name__ == '__main__': + main() diff --git a/sphinx/ext/autosummary/templates/module b/sphinx/ext/autosummary/templates/module new file mode 100644 index 00000000..34dd8100 --- /dev/null +++ b/sphinx/ext/autosummary/templates/module @@ -0,0 +1,39 @@ +:mod:`{{name}}` +=============================================================================================================================================== + + +.. automodule:: {{name}} + +{% if len_functions > 0 %} +Functions +---------- +{% for item in functions %} +.. autofunction:: {{item}} +{% endfor %} +{% endif %} + +{% if len_classes > 0 %} +Classes +-------- +{% for item in classes %} +.. autoclass:: {{item}} + :show-inheritance: + :members: + :inherited-members: + :undoc-members: + +{% endfor %} +{% endif %} + +{% if len_exceptions > 0 %} +Exceptions +------------ +{% for item in exceptions %} +.. autoclass:: {{item}} + :show-inheritance: + :members: + :inherited-members: + :undoc-members: + +{% endfor %} +{% endif %}
\ No newline at end of file |
