#187: Added support for source ordering of members in autodoc, with `autodoc_member_order = 'bysource'`.

This commit is contained in:
Georg Brandl 2010-02-28 11:39:13 +01:00
parent 9a316a21a0
commit a31e7e2f5d
5 changed files with 74 additions and 10 deletions

View File

@ -10,6 +10,9 @@ Release 1.0 (in development)
* Added a manual page builder.
* Added support for source ordering of members in autodoc, with
``autodoc_member_order = 'bysource'``.
* In HTML output, inline roles now get a CSS class with their name,
allowing styles to customize their appearance. Domain-specific
roles get two classes, ``domain`` and ``domain-rolename``.

View File

@ -223,10 +223,16 @@ There are also new config values that you can set:
.. confval:: autodoc_member_order
This value selects if automatically documented members are sorted
alphabetical (value ``'alphabetical'``) or by member type (value
``'groupwise'``). The default is alphabetical.
alphabetical (value ``'alphabetical'``), by member type (value
``'groupwise'``) or by source order (value ``'bysource'``). The default is
alphabetical.
Note that for source order, the module must be a Python module with the
source code available.
.. versionadded:: 0.6
.. versionchanged:: 1.0
Support for ``'bysource'``.
.. confval:: autodoc_default_flags

View File

@ -599,12 +599,19 @@ class Documenter(object):
'.'.join(self.objpath + [mname])
memberdocumenters.append(
classes[-1](self.directive, full_mname, self.indent))
if (self.options.member_order or self.env.config.autodoc_member_order) \
== 'groupwise':
member_order = self.options.member_order or \
self.env.config.autodoc_member_order
if member_order == 'groupwise':
# sort by group; relies on stable sort to keep items in the
# same group sorted alphabetically
memberdocumenters.sort(key=lambda d: d.member_order)
elif member_order == 'bysource' and self.analyzer:
# sort by source order, by virtue of the module analyzer
tagorder = self.analyzer.tagorder
def keyfunc(documenter):
fullname = documenter.name.split('::')[1]
return tagorder.get(fullname, len(tagorder))
memberdocumenters.sort(key=keyfunc)
for documenter in memberdocumenters:
documenter.generate(all_members=True,

View File

@ -58,9 +58,17 @@ class AttrDocVisitor(nodes.NodeVisitor):
self.encoding = encoding
self.namespace = []
self.collected = {}
self.tagnumber = 0
self.tagorder = {}
def add_tag(self, name):
name = '.'.join(self.namespace + [name])
self.tagorder[name] = self.tagnumber
self.tagnumber += 1
def visit_classdef(self, node):
"""Visit a class."""
self.add_tag(node[1].value)
self.namespace.append(node[1].value)
self.generic_visit(node)
self.namespace.pop()
@ -68,6 +76,7 @@ class AttrDocVisitor(nodes.NodeVisitor):
def visit_funcdef(self, node):
"""Visit a function (or method)."""
# usually, don't descend into functions -- nothing interesting there
self.add_tag(node[1].value)
if node[1].value == '__init__':
# however, collect attributes set in __init__ methods
self.in_init += 1
@ -91,8 +100,7 @@ class AttrDocVisitor(nodes.NodeVisitor):
prefix = pnode.get_prefix()
prefix = prefix.decode(self.encoding)
docstring = prepare_commentdoc(prefix)
if docstring:
self.add_docstring(node, docstring)
self.add_docstring(node, docstring)
def visit_simple_stmt(self, node):
"""Visit a docstring statement which may have an assignment before."""
@ -133,9 +141,11 @@ class AttrDocVisitor(nodes.NodeVisitor):
continue
else:
name = target.value
namespace = '.'.join(self.namespace)
if namespace.startswith(self.scope):
self.collected[namespace, name] = docstring
self.add_tag(name)
if docstring:
namespace = '.'.join(self.namespace)
if namespace.startswith(self.scope):
self.collected[namespace, name] = docstring
class ModuleAnalyzer(object):
@ -197,6 +207,7 @@ class ModuleAnalyzer(object):
self.parsetree = None
# will be filled by find_attr_docs()
self.attr_docs = None
self.tagorder = None
# will be filled by find_tags()
self.tags = None
@ -234,6 +245,7 @@ class ModuleAnalyzer(object):
attr_visitor = AttrDocVisitor(number2name, scope, self.encoding)
attr_visitor.visit(self.parsetree)
self.attr_docs = attr_visitor.collected
self.tagorder = attr_visitor.tagorder
# now that we found everything we could in the tree, throw it away
# (it takes quite a bit of memory for large modules)
self.parsetree = None

View File

@ -341,6 +341,26 @@ def test_generate():
assert item in directive.result
del directive.result[:]
def assert_order(items, objtype, name, member_order, **kw):
inst = AutoDirective._registry[objtype](directive, name)
inst.options.member_order = member_order
inst.generate(**kw)
assert len(_warnings) == 0, _warnings
items = list(reversed(items))
lineiter = iter(directive.result)
#for line in directive.result:
# if line.strip():
# print repr(line)
while items:
item = items.pop()
for line in lineiter:
if line == item:
break
else: # ran out of items!
assert False, 'item %r not found in result or not in the ' \
' correct order' % item
del directive.result[:]
options.members = []
# no module found?
@ -442,6 +462,22 @@ def test_generate():
assert_processes([('function', 'time.asctime')], 'function', 'asctime')
assert_processes([('function', 'time.asctime')], 'function', 'asctime')
# test autodoc_member_order == 'source'
directive.env.temp_data['py:module'] = 'test_autodoc'
assert_order(['.. py:class:: Class(arg)',
' .. py:attribute:: Class.descr',
' .. py:method:: Class.meth()',
' .. py:method:: Class.undocmeth()',
' .. py:attribute:: Class.attr',
' .. py:attribute:: Class.prop',
' .. py:attribute:: Class.docattr',
' .. py:attribute:: Class.udocattr',
' .. py:attribute:: Class.inst_attr_comment',
' .. py:attribute:: Class.inst_attr_string',
' .. py:method:: Class.inheritedmeth()',
],
'class', 'Class', member_order='bysource', all_members=True)
# --- generate fodder ------------