mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2026-08-27 13:47:36 -05:00
autosummary: fix bugs, and include features from the Numpy version
This commit is contained in:
@@ -68,7 +68,7 @@ from sphinx import addnodes, roles
|
||||
from sphinx.util import patfilter
|
||||
from sphinx.util.compat import Directive
|
||||
|
||||
from sphinx.ext.autodoc import FunctionDocumenter
|
||||
import sphinx.ext.autodoc
|
||||
|
||||
|
||||
# -- autosummary_toc node ------------------------------------------------------
|
||||
@@ -110,6 +110,27 @@ def autosummary_toc_visit_latex(self, node):
|
||||
def autosummary_noop(self, node):
|
||||
pass
|
||||
|
||||
# -- autodoc integration -------------------------------------------------------
|
||||
|
||||
def get_documenter(obj):
|
||||
"""
|
||||
Get an autodoc.Documenter class suitable for documenting the given object
|
||||
"""
|
||||
reg = sphinx.ext.autodoc.AutoDirective._registry
|
||||
if inspect.isclass(obj):
|
||||
if issubclass(obj, Exception):
|
||||
return reg.get('exception')
|
||||
return reg.get('class')
|
||||
elif inspect.ismodule(obj):
|
||||
return reg.get('module')
|
||||
elif inspect.ismethod(obj) or inspect.ismethoddescriptor(obj):
|
||||
return reg.get('method')
|
||||
elif inspect.ismemberdescriptor(obj) or inspect.isgetsetdescriptor(obj):
|
||||
return reg.get('attribute')
|
||||
elif inspect.isroutine(obj):
|
||||
return reg.get('function')
|
||||
else:
|
||||
return reg.get('data')
|
||||
|
||||
# -- .. autosummary:: ----------------------------------------------------------
|
||||
|
||||
@@ -129,15 +150,21 @@ class Autosummary(Directive):
|
||||
'nosignatures': directives.flag,
|
||||
}
|
||||
|
||||
def warn(self, msg):
|
||||
self.warnings.append(self.state.document.reporter.warning(
|
||||
msg, line=self.lineno))
|
||||
|
||||
def run(self):
|
||||
self.env = env = self.state.document.settings.env
|
||||
self.genopt = {}
|
||||
self.warnings = []
|
||||
|
||||
names = []
|
||||
names += [x.strip() for x in self.content if x.strip()]
|
||||
|
||||
table, warnings, real_names = get_autosummary(
|
||||
names, self, 'nosignatures' in self.options)
|
||||
node = table
|
||||
table, real_names = self.get_table(names)
|
||||
nodes = [table]
|
||||
|
||||
env = self.state.document.settings.env
|
||||
suffix = env.config.source_suffix
|
||||
all_docnames = env.found_docs.copy()
|
||||
dirname = posixpath.dirname(env.docname)
|
||||
@@ -153,9 +180,8 @@ class Autosummary(Directive):
|
||||
docname = docname[:-len(suffix)]
|
||||
docname = posixpath.normpath(posixpath.join(dirname, docname))
|
||||
if docname not in env.found_docs:
|
||||
warnings.append(self.state.document.reporter.warning(
|
||||
'toctree references unknown document %r' % docname,
|
||||
line=self.lineno))
|
||||
self.warn('toctree references unknown document %r'
|
||||
% docname)
|
||||
docnames.append(docname)
|
||||
|
||||
tocnode = addnodes.toctree()
|
||||
@@ -165,67 +191,124 @@ class Autosummary(Directive):
|
||||
tocnode['glob'] = None
|
||||
|
||||
tocnode = autosummary_toc('', '', tocnode)
|
||||
return warnings + [node] + [tocnode]
|
||||
else:
|
||||
return warnings + [node]
|
||||
nodes.append(tocnode)
|
||||
|
||||
return self.warnings + nodes
|
||||
|
||||
def get_autosummary(names, directive, no_signatures=False):
|
||||
def get_table(self, names, no_signatures=False):
|
||||
"""
|
||||
Generate a proper table node for autosummary:: directive.
|
||||
|
||||
*names* is a list of names of Python objects to be imported
|
||||
and added to the table.
|
||||
|
||||
"""
|
||||
state = self.state
|
||||
document = state.document
|
||||
|
||||
prefixes = ['']
|
||||
prefixes.insert(0, document.settings.env.currmodule)
|
||||
|
||||
real_names = {}
|
||||
|
||||
table = nodes.table('')
|
||||
group = nodes.tgroup('', cols=2)
|
||||
table.append(group)
|
||||
group.append(nodes.colspec('', colwidth=10))
|
||||
group.append(nodes.colspec('', colwidth=90))
|
||||
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:
|
||||
self.warn('failed to import %s' % name)
|
||||
append_row(':obj:`%s`' % name, '')
|
||||
continue
|
||||
|
||||
documenter = get_documenter(obj)(self, real_name)
|
||||
if not documenter.parse_name():
|
||||
append_row(':obj:`%s`' % name, '')
|
||||
continue
|
||||
if not documenter.import_object():
|
||||
append_row(':obj:`%s`' % name, '')
|
||||
continue
|
||||
|
||||
real_names[name] = documenter.fullname
|
||||
|
||||
sig = documenter.format_signature()
|
||||
if not sig or 'nosignatures' in self.options:
|
||||
sig = ''
|
||||
else:
|
||||
sig = mangle_signature(sig)
|
||||
|
||||
doc = list(documenter.process_doc(documenter.get_doc()))
|
||||
if doc:
|
||||
# grab the summary
|
||||
while doc and not doc[0].strip():
|
||||
doc.pop(0)
|
||||
m = re.search(r"^([A-Z].*?\.\s)", " ".join(doc).strip())
|
||||
if m:
|
||||
summary = m.group(1).strip()
|
||||
else:
|
||||
summary = doc[0].strip()
|
||||
else:
|
||||
summary = ''
|
||||
|
||||
qualifier = 'obj'
|
||||
col1 = ':' + qualifier + r':`%s <%s>`\ %s' % (name, real_name, sig)
|
||||
col2 = summary
|
||||
append_row(col1, col2)
|
||||
|
||||
return table, real_names
|
||||
|
||||
def mangle_signature(sig, max_chars=30):
|
||||
"""
|
||||
Generate a proper table node for autosummary:: directive.
|
||||
|
||||
*names* is a list of names of Python objects to be imported and added to the
|
||||
table. *document* is the Docutils document object.
|
||||
Reformat function signature to a more compact form.
|
||||
|
||||
"""
|
||||
state = directive.state
|
||||
document = state.document
|
||||
sig = re.sub(r"^\((.*)\)$", r"\1", sig) + ", "
|
||||
r = re.compile(r"(?P<name>[a-zA_Z0-9_*]+)(?P<default>=.*?)?, ")
|
||||
items = r.findall(sig)
|
||||
|
||||
real_names = {}
|
||||
warnings = []
|
||||
args = []
|
||||
opts = []
|
||||
|
||||
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:
|
||||
documenter = FunctionDocumenter(self, name)
|
||||
documenter.parse_name()
|
||||
|
||||
real_names[name] = documenter.fullname
|
||||
|
||||
sig = documenter.format_signature()
|
||||
if sig:
|
||||
pass
|
||||
total_len = 4
|
||||
for name, default in items:
|
||||
if default:
|
||||
opts.append(name)
|
||||
else:
|
||||
sig = ''
|
||||
args.append(name)
|
||||
total_len += len(name) + 2
|
||||
|
||||
doc = list(documenter.process_doc([documenter.get_doc()]))
|
||||
if doc:
|
||||
title = doc[0]
|
||||
else:
|
||||
title = ''
|
||||
if total_len > max_chars:
|
||||
if opts:
|
||||
opts.append('...')
|
||||
else:
|
||||
args.append('...')
|
||||
break
|
||||
|
||||
qualifier = 'obj'
|
||||
col1 = ':' + qualifier + r':`%s <%s>`\ %s' % (name, real_name, sig)
|
||||
col2 = title
|
||||
append_row(col1, col2)
|
||||
if opts:
|
||||
sig = ", ".join(args) + "[, " + ", ".join(opts) + "]"
|
||||
else:
|
||||
sig = ", ".join(args)
|
||||
|
||||
return table, warnings, real_names
|
||||
sig = unicode(sig).replace(u" ", u"\u00a0")
|
||||
return u"(%s)" % sig
|
||||
|
||||
# -- Importing items -----------------------------------------------------------
|
||||
|
||||
def import_by_name(name, prefixes=[None]):
|
||||
"""
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import getopt
|
||||
import optparse
|
||||
import inspect
|
||||
|
||||
from jinja2 import Environment, PackageLoader
|
||||
@@ -38,7 +38,7 @@ def _simple_info(msg):
|
||||
def _simple_warn(msg):
|
||||
print >>sys.stderr, 'WARNING: ' + msg
|
||||
|
||||
def generate_autosummary_docs(sources, output_dir=None, suffix=None,
|
||||
def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
|
||||
warn=_simple_warn, info=_simple_info):
|
||||
info('generating autosummary for: %s' % ', '.join(sources))
|
||||
if output_dir:
|
||||
@@ -62,7 +62,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix=None,
|
||||
warn('failed to import %r: %s' % (name, e))
|
||||
continue
|
||||
|
||||
fn = os.path.join(path, name + (suffix or '.rst'))
|
||||
fn = os.path.join(path, name + suffix)
|
||||
# skip it if it exists
|
||||
if os.path.isfile(fn):
|
||||
continue
|
||||
@@ -211,28 +211,22 @@ def get_documented(filenames):
|
||||
return documented
|
||||
|
||||
|
||||
def main():
|
||||
usage = 'usage: %s [-o output_dir] [-s suffix] sourcefile ...' % sys.argv[0]
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], 'o:s:')
|
||||
except getopt.error:
|
||||
print >>sys.stderr, usage
|
||||
return 1
|
||||
|
||||
output_dir = None
|
||||
suffix = None
|
||||
for opt, val in opts:
|
||||
if opt == '-o':
|
||||
output_dir = val
|
||||
elif opt == '-s':
|
||||
suffix = val
|
||||
def main(argv):
|
||||
usage = """%prog [OPTIONS] SOURCEFILE ..."""
|
||||
p = optparse.OptionParser(usage.strip())
|
||||
p.add_option("-o", "--output-dir", action="store", type="string",
|
||||
dest="output_dir", default=None,
|
||||
help="Directory to place all output in")
|
||||
p.add_option("-s", "--suffix", action="store", type="string",
|
||||
dest="suffix", default="rst",
|
||||
help="Default suffix for files (default: %default)")
|
||||
options, args = p.parse_args(argv[1:])
|
||||
|
||||
if len(args) < 1:
|
||||
print >>sys.stderr, usage
|
||||
return 1
|
||||
|
||||
generate_autosummary_docs(args, output_dir, suffix)
|
||||
p.error('no input files given')
|
||||
|
||||
generate_autosummary_docs(args, options.output_dir,
|
||||
"." + options.suffix)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
test_autosummary
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Test the autosummary extension.
|
||||
|
||||
:copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
import string
|
||||
|
||||
from util import *
|
||||
|
||||
from sphinx.ext.autosummary import mangle_signature
|
||||
|
||||
|
||||
def test_mangle_signature():
|
||||
TEST = """
|
||||
() :: ()
|
||||
(a, b, c, d, e) :: (a, b, c, d, e)
|
||||
(a, b, c=1, d=2, e=3) :: (a, b[, c, d, e])
|
||||
(a, b, aaa=1, bbb=1, ccc=1, eee=1, fff=1, ggg=1, hhh=1, iii=1, jjj=1) :: (a, b[, aaa, bbb, ccc, eee, fff, ...])
|
||||
(a, b, c=(), d=<foo>) :: (a, b[, c, d])
|
||||
(a, b, c='foobar()', d=123) :: (a, b[, c, d])
|
||||
"""
|
||||
|
||||
TEST = [map(string.strip, x.split("::")) for x in TEST.split("\n")
|
||||
if '::' in x]
|
||||
for inp, outp in TEST:
|
||||
res = mangle_signature(inp).strip().replace(u"\u00a0", " ")
|
||||
assert res == outp, (u"'%s' -> '%s' != '%s'" % (inp, res, outp))
|
||||
Reference in New Issue
Block a user