summaryrefslogtreecommitdiff
path: root/doc/sphinxext/autosummary.py
diff options
context:
space:
mode:
Diffstat (limited to 'doc/sphinxext/autosummary.py')
-rw-r--r--doc/sphinxext/autosummary.py334
1 files changed, 334 insertions, 0 deletions
diff --git a/doc/sphinxext/autosummary.py b/doc/sphinxext/autosummary.py
new file mode 100644
index 000000000..d99f861ee
--- /dev/null
+++ b/doc/sphinxext/autosummary.py
@@ -0,0 +1,334 @@
+"""
+===========
+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'.
+
+"""
+import sys, os, posixpath, re
+
+from docutils.parsers.rst import directives
+from docutils.statemachine import ViewList
+from docutils import nodes
+
+import sphinx.addnodes, sphinx.roles, sphinx.builder
+from sphinx.util import patfilter
+
+from docscrape_sphinx import get_doc_object
+
+
+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)
+
+#------------------------------------------------------------------------------
+# 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], sphinx.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 = sphinx.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
+
+ doc = get_doc_object(obj)
+
+ if doc['Summary']:
+ title = " ".join(doc['Summary'])
+ else:
+ title = ""
+
+ col1 = ":obj:`%s <%s>`" % (name, real_name)
+ if doc['Signature']:
+ sig = re.sub('^[a-zA-Z_0-9.-]*', '', doc['Signature'])
+ if '=' in sig:
+ # abbreviate optional arguments
+ sig = re.sub(r', ([a-zA-Z0-9_]+)=', r'[, \1=', sig, count=1)
+ sig = re.sub(r'\(([a-zA-Z0-9_]+)=', r'([\1=', sig, count=1)
+ sig = re.sub(r'=[^,)]+,', ',', sig)
+ sig = re.sub(r'=[^,)]+\)$', '])', sig)
+ # shorten long strings
+ sig = re.sub(r'(\[.{16,16}[^,)]*?),.*?\]\)', r'\1, ...])', sig)
+ else:
+ sig = re.sub(r'(\(.{16,16}[^,)]*?),.*?\)', r'\1, ...)', sig)
+ col1 += " " + sig
+ 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 = sphinx.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