summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgbrandl <devnull@localhost>2009-03-14 21:14:48 +0100
committergbrandl <devnull@localhost>2009-03-14 21:14:48 +0100
commitab97ffb4d30111cd2a8af3eb0bbff5f503347a31 (patch)
treee168143c124212589c8ef4121971cbe7a5ef9b1e
parent68a77dd93ffb50c4712bbd8aeb85977b67f63859 (diff)
parent45deb642412529c9f00ae41ad39fd5a9c6a9c762 (diff)
downloadsphinx-ab97ffb4d30111cd2a8af3eb0bbff5f503347a31.tar.gz
merge with autosummary branch
-rw-r--r--setup.py3
-rwxr-xr-xsphinx-autogen.py15
-rw-r--r--sphinx/cmdline.py16
-rw-r--r--sphinx/ext/autosummary/LICENSE.txt31
-rw-r--r--sphinx/ext/autosummary/__init__.py318
-rw-r--r--sphinx/ext/autosummary/generate.py215
-rw-r--r--sphinx/ext/autosummary/templates/module39
7 files changed, 635 insertions, 2 deletions
diff --git a/setup.py b/setup.py
index bdfbd3f7..ffecc844 100644
--- a/setup.py
+++ b/setup.py
@@ -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