Work a bit on coding style of autosummary.

This commit is contained in:
Georg Brandl 2009-03-14 21:08:52 +01:00
parent ce4d9c3665
commit c85d4c2db2
4 changed files with 197 additions and 150 deletions

15
sphinx-autogen.py Executable file
View File

@ -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))

View File

@ -1,80 +1,73 @@
# -*- coding: utf-8 -*-
"""
===========
autosummary
===========
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.
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.
An :autolink: role is also provided.
autosummary directive
---------------------
autosummary directive
---------------------
The autosummary directive has the form::
The autosummary directive has the form::
.. autosummary::
:nosignatures:
:toctree: generated/
module.function_1
module.function_2
...
.. autosummary::
:nosignatures:
:toctree: generated/
and it generates an output table (containing signatures, optionally)
module.function_1
module.function_2
...
======================== =============================================
module.function_1(args) Summary line from the docstring of function_1
module.function_2(args) Summary line from the docstring
...
======================== =============================================
and it generates an output table (containing signatures, optionally)
If the :toctree: option is specified, files matching the function names
are inserted to the toctree with the given prefix:
======================== =============================================
module.function_1(args) Summary line from the docstring of function_1
module.function_2(args) Summary line from the docstring
...
======================== =============================================
generated/module.function_1
generated/module.function_2
...
If the :toctree: option is specified, files matching the function names
are inserted to the toctree with the given prefix:
Note: The file names contain the module:: or currentmodule:: prefixes.
generated/module.function_1
generated/module.function_2
...
.. seealso:: autosummary_generate.py
Note: The file names contain the module:: or currentmodule:: prefixes.
.. seealso:: autosummary_generate.py
autolink role
-------------
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'.
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 sys, os, posixpath, re
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
import sphinx.addnodes, sphinx.roles, sphinx.builder
from sphinx import addnodes, roles
from sphinx.util import patfilter
import inspect
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
#------------------------------------------------------------------------------
# -- autosummary_toc node ------------------------------------------------------
class autosummary_toc(nodes.comment):
pass
@ -83,7 +76,6 @@ 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 = {}
@ -92,7 +84,7 @@ def process_autosummary_toc(app, doctree):
for j, subnode in enumerate(node):
try:
if (isinstance(subnode, autosummary_toc)
and isinstance(subnode[0], sphinx.addnodes.toctree)):
and isinstance(subnode[0], addnodes.toctree)):
env.note_toctree(env.docname, subnode[0])
continue
except IndexError:
@ -104,19 +96,18 @@ def process_autosummary_toc(app, doctree):
crawl_toc(doctree)
def autosummary_toc_visit_html(self, node):
"""Hide autosummary toctree list in HTML output"""
"""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"""
"""Show autosummary toctree (= put the referenced pages here) in Latex."""
pass
def autosummary_toc_depart_noop(self, node):
pass
#------------------------------------------------------------------------------
# .. autosummary::
#------------------------------------------------------------------------------
# -- .. autosummary:: ----------------------------------------------------------
def autosummary_directive(dirname, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
@ -155,7 +146,7 @@ def autosummary_directive(dirname, arguments, options, content, lineno,
line=lineno))
docnames.append(docname)
tocnode = sphinx.addnodes.toctree()
tocnode = addnodes.toctree()
tocnode['includefiles'] = docnames
tocnode['maxdepth'] = -1
tocnode['glob'] = None
@ -165,6 +156,7 @@ def autosummary_directive(dirname, arguments, options, content, lineno,
else:
return warnings + [node]
def get_autosummary(names, state, no_signatures=False):
"""
Generate a proper table node for autosummary:: directive.
@ -175,10 +167,10 @@ def get_autosummary(names, state, no_signatures=False):
Names of Python objects to be imported and added to the table.
document : document
Docutils document object
"""
document = state.document
real_names = {}
warnings = []
@ -209,14 +201,14 @@ def get_autosummary(names, state, no_signatures=False):
except ImportError:
warnings.append(document.reporter.warning(
'failed to import %s' % name))
append_row(":obj:`%s`" % name, "")
append_row(':obj:`%s`' % name, '')
continue
real_names[name] = real_name
title = ""
title = ''
qualifier = 'obj'
col1 = ":"+qualifier+":`%s <%s>`" % (name, real_name)
col1 = ':'+qualifier+':`%s <%s>`' % (name, real_name)
col2 = title
append_row(col1, col2)
@ -240,7 +232,7 @@ def import_by_name(name, prefixes=[None]):
The imported object
name
Name of the imported object (useful if `prefixes` was used)
"""
for prefix in prefixes:
try:
@ -254,9 +246,18 @@ def import_by_name(name, prefixes=[None]):
raise ImportError
def _import_by_name(name):
"""Import a Python object given its full 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)):
@ -279,20 +280,19 @@ def _import_by_name(name):
except (ValueError, ImportError, AttributeError, KeyError), e:
raise ImportError(e)
#------------------------------------------------------------------------------
# :autolink: (smart default role)
#------------------------------------------------------------------------------
# -- :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*".
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)
r = roles.xfileref_role('obj', rawtext, etext, lineno, inliner,
options, content)
pnode = r[0][0]
prefixes = [None]
@ -304,3 +304,15 @@ def autolink_role(typ, rawtext, etext, lineno, inliner,
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)

View File

@ -1,85 +1,91 @@
# -*- coding: utf-8 -*-
"""
autosummary_generate.py OPTIONS FILES
sphinx.ext.autosummary.generate
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Generate automatic RST source files for items referred to in
autosummary:: directives.
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.
Each generated RST file contains a single auto*:: directive which
extracts the docstring of the referred item.
Example Makefile rule::
Example Makefile rule::
generate:
sphinx-autogen source/*.rst source/generated
generate:
sphinx-autogen source/*.rst source/generated
:copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import glob, re, inspect, os, optparse
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
from jinja import Environment, PackageLoader
env = Environment(loader=PackageLoader('sphinx.ext.autosummary', 'templates'))
# create our own templating environment, for module template only
env = Environment(loader=PackageLoader('sphinx.ext.autosummary', 'templates'))
def main():
p = optparse.OptionParser(__doc__.strip())
options, args = p.parse_args()
if len(args) <2:
p.error("wrong number of arguments")
print 'generating docs from:', args[:-1]
generate_autosummary_docs(args[:-1], args[-1])
def generate_autosummary_docs(source_dir, output_dir):
def generate_autosummary_docs(sources, output_dir=None):
# read
names = {}
for name, loc in get_documented(source_dir).items():
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()):
path = output_dir
if not os.path.isdir(path):
os.makedirs(path)
if output_dir is not None:
path = output_dir
ensuredir(path)
try:
obj, name = import_by_name(name)
except ImportError, e:
print "Failed to import '%s': %s" % (name, e)
print >>sys.stderr, 'Failed to import %r: %s' % (name, e)
continue
fn = os.path.join(path, '%s.rst' % name)
if os.path.exists(fn):
# skip
# 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.html')
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,
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)
)
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'))
@ -96,40 +102,40 @@ def generate_autosummary_docs(source_dir, output_dir):
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)
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)
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 source/*.rst
Returns
-------
documented : dict of list of (filename, title, keyword, toctree)
Dictionary whose 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.
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.
"""
title_underline_re = re.compile("^[-=*_^#]{3,}\s*$")
autodoc_re = re.compile(".. 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*$')
documented = {}
for filename in filenames:
current_title = []
last_line = None
@ -150,32 +156,34 @@ def get_documented(filenames):
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)
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":
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))
(filename, current_title, 'auto' + m.group(1), None))
continue
m = title_underline_re.match(line)
@ -191,5 +199,17 @@ def get_documented(filenames):
last_line = line
return documented
if __name__ == "__main__":
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()