From a31e7e2f5d2e82e2af09081ac6ee128963522895 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 28 Feb 2010 11:39:13 +0100 Subject: [PATCH] #187: Added support for source ordering of members in autodoc, with ``autodoc_member_order = 'bysource'``. --- CHANGES | 3 +++ doc/ext/autodoc.rst | 10 ++++++++-- sphinx/ext/autodoc.py | 13 ++++++++++--- sphinx/pycode/__init__.py | 22 +++++++++++++++++----- tests/test_autodoc.py | 36 ++++++++++++++++++++++++++++++++++++ 5 files changed, 74 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index 31458763a..5fc365408 100644 --- a/CHANGES +++ b/CHANGES @@ -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``. diff --git a/doc/ext/autodoc.rst b/doc/ext/autodoc.rst index 4098d7113..08a540826 100644 --- a/doc/ext/autodoc.rst +++ b/doc/ext/autodoc.rst @@ -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 diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py index 7e42002e9..2597c03b1 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc.py @@ -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, diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py index 73c2042f6..63303a85d 100644 --- a/sphinx/pycode/__init__.py +++ b/sphinx/pycode/__init__.py @@ -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 diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index ea41dcaa9..62768b202 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -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 ------------