mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
First iteration of an autodoc that handles attribute documentation.
This commit is contained in:
parent
0edeba7acb
commit
83cf763804
@ -22,6 +22,7 @@ from docutils.parsers.rst import directives
|
||||
from docutils.statemachine import ViewList
|
||||
|
||||
from sphinx.util import rpartition, nested_parse_with_titles
|
||||
from sphinx.pycode import ModuleAnalyzer, PycodeError
|
||||
|
||||
clstypes = (type, ClassType)
|
||||
try:
|
||||
@ -313,7 +314,7 @@ class RstGenerator(object):
|
||||
'for automodule %s' % name)
|
||||
return (path or '') + base, [], None, None
|
||||
|
||||
elif what in ('exception', 'function', 'class'):
|
||||
elif what in ('exception', 'function', 'class', 'data'):
|
||||
if mod is None:
|
||||
if path:
|
||||
mod = path.rstrip('.')
|
||||
@ -434,7 +435,9 @@ class RstGenerator(object):
|
||||
modfile = None # e.g. for builtin and C modules
|
||||
for part in objpath:
|
||||
todoc = getattr(todoc, part)
|
||||
except (ImportError, AttributeError), err:
|
||||
# also get a source code analyzer for attribute docs
|
||||
analyzer = ModuleAnalyzer.for_module(mod)
|
||||
except (ImportError, AttributeError, PycodeError), err:
|
||||
self.warn('autodoc can\'t import/find %s %r, it reported error: "%s", '
|
||||
'please check your spelling and sys.path' %
|
||||
(what, str(fullname), err))
|
||||
@ -503,6 +506,15 @@ class RstGenerator(object):
|
||||
else:
|
||||
sourcename = 'docstring of %s' % fullname
|
||||
|
||||
# add content from attribute documentation
|
||||
attr_docs = analyzer.find_attr_docs()
|
||||
if what in ('data', 'attribute'):
|
||||
key = ('.'.join(objpath[:-1]), objpath[-1])
|
||||
if key in attr_docs:
|
||||
no_docstring = True
|
||||
for i, line in enumerate(attr_docs[key]):
|
||||
self.result.append(indent + line, sourcename, i)
|
||||
|
||||
# add content from docstrings
|
||||
if not no_docstring:
|
||||
for i, line in enumerate(self.get_doc(what, fullname, todoc)):
|
||||
@ -524,9 +536,9 @@ class RstGenerator(object):
|
||||
self.env.autodoc_current_class = objpath[0]
|
||||
|
||||
# add members, if possible
|
||||
_all = members == ['__all__']
|
||||
all_members = members == ['__all__']
|
||||
members_check_module = False
|
||||
if _all:
|
||||
if all_members:
|
||||
# unqualified :members: given
|
||||
if what == 'module':
|
||||
if hasattr(todoc, '__all__'):
|
||||
@ -555,14 +567,28 @@ class RstGenerator(object):
|
||||
else:
|
||||
all_members = [(mname, getattr(todoc, mname)) for mname in members]
|
||||
|
||||
# search for members in source code too
|
||||
namespace = '.'.join(objpath) # will be empty for modules
|
||||
|
||||
for (membername, member) in all_members:
|
||||
if _all and membername.startswith('_'):
|
||||
# if isattr is True, the member is documented as an attribute
|
||||
isattr = False
|
||||
# if content is not None, no extra content from docstrings will be added
|
||||
content = None
|
||||
|
||||
if all_members and membername.startswith('_'):
|
||||
# ignore members whose name starts with _ by default
|
||||
skip = True
|
||||
else:
|
||||
# ignore undocumented members if :undoc-members: is not given
|
||||
doc = getattr(member, '__doc__', None)
|
||||
skip = not self.options.undoc_members and not doc
|
||||
if (namespace, membername) in attr_docs:
|
||||
# keep documented attributes
|
||||
skip = False
|
||||
isattr = True
|
||||
else:
|
||||
# ignore undocumented members if :undoc-members: is not given
|
||||
doc = getattr(member, '__doc__', None)
|
||||
skip = not self.options.undoc_members and not doc
|
||||
|
||||
# give the user a chance to decide whether this member should be skipped
|
||||
if self.env.app:
|
||||
# let extensions preprocess docstrings
|
||||
@ -573,10 +599,11 @@ class RstGenerator(object):
|
||||
if skip:
|
||||
continue
|
||||
|
||||
content = None
|
||||
if what == 'module':
|
||||
if isinstance(member, (FunctionType, BuiltinFunctionType)):
|
||||
memberwhat = 'function'
|
||||
elif isattr:
|
||||
memberwhat = 'attribute'
|
||||
elif isinstance(member, clstypes):
|
||||
if member.__name__ != membername:
|
||||
# assume it's aliased
|
||||
@ -588,10 +615,13 @@ class RstGenerator(object):
|
||||
else:
|
||||
memberwhat = 'class'
|
||||
else:
|
||||
# XXX: todo -- attribute docs
|
||||
continue
|
||||
else:
|
||||
if isinstance(member, clstypes):
|
||||
if inspect.isroutine(member):
|
||||
memberwhat = 'method'
|
||||
elif isattr:
|
||||
memberwhat = 'attribute'
|
||||
elif isinstance(member, clstypes):
|
||||
if member.__name__ != membername:
|
||||
# assume it's aliased
|
||||
memberwhat = 'attribute'
|
||||
@ -599,12 +629,9 @@ class RstGenerator(object):
|
||||
source='')
|
||||
else:
|
||||
memberwhat = 'class'
|
||||
elif inspect.isroutine(member):
|
||||
memberwhat = 'method'
|
||||
elif isdescriptor(member):
|
||||
memberwhat = 'attribute'
|
||||
else:
|
||||
# XXX: todo -- attribute docs
|
||||
continue
|
||||
# give explicitly separated module name, so that members of inner classes
|
||||
# can be documented
|
||||
|
@ -43,7 +43,7 @@ def prepare_commentdoc(s):
|
||||
result.append(line[3:])
|
||||
if result and result[-1]:
|
||||
result.append('')
|
||||
return '\n'.join(result)
|
||||
return result
|
||||
|
||||
|
||||
_eq = pytree.Leaf(token.EQUAL, '=')
|
||||
@ -57,7 +57,7 @@ class ClassAttrVisitor(pytree.NodeVisitor):
|
||||
def init(self, scope):
|
||||
self.scope = scope
|
||||
self.namespace = []
|
||||
self.collected = []
|
||||
self.collected = {}
|
||||
|
||||
def visit_classdef(self, node):
|
||||
self.namespace.append(node[1].value)
|
||||
@ -87,9 +87,9 @@ class ClassAttrVisitor(pytree.NodeVisitor):
|
||||
if target.type != token.NAME:
|
||||
# don't care about complex targets
|
||||
continue
|
||||
name = '.'.join(self.namespace + [target.value])
|
||||
if name.startswith(self.scope):
|
||||
self.collected.append((name, docstring))
|
||||
namespace = '.'.join(self.namespace)
|
||||
if namespace.startswith(self.scope):
|
||||
self.collected[namespace, target.value] = docstring
|
||||
|
||||
def visit_funcdef(self, node):
|
||||
# don't descend into functions -- nothing interesting there
|
||||
@ -105,6 +105,8 @@ class PycodeError(Exception):
|
||||
|
||||
|
||||
class ModuleAnalyzer(object):
|
||||
# cache for analyzer objects
|
||||
cache = {}
|
||||
|
||||
def __init__(self, tree, modname, srcname):
|
||||
self.tree = tree
|
||||
@ -131,6 +133,8 @@ class ModuleAnalyzer(object):
|
||||
|
||||
@classmethod
|
||||
def for_module(cls, modname):
|
||||
if modname in cls.cache:
|
||||
return cls.cache[modname]
|
||||
if modname not in sys.modules:
|
||||
try:
|
||||
__import__(modname)
|
||||
@ -142,7 +146,9 @@ class ModuleAnalyzer(object):
|
||||
source = mod.__loader__.get_source(modname)
|
||||
except Exception, err:
|
||||
raise PycodeError('error getting source for %r' % modname, err)
|
||||
return cls.for_string(source, modname)
|
||||
obj = cls.for_string(source, modname)
|
||||
cls.cache[modname] = obj
|
||||
return obj
|
||||
filename = getattr(mod, '__file__', None)
|
||||
if filename is None:
|
||||
raise PycodeError('no source found for module %r' % modname)
|
||||
@ -153,13 +159,16 @@ class ModuleAnalyzer(object):
|
||||
raise PycodeError('source is not a .py file: %r' % filename)
|
||||
if not path.isfile(filename):
|
||||
raise PycodeError('source file is not present: %r' % filename)
|
||||
return cls.for_file(filename, modname)
|
||||
obj = cls.for_file(filename, modname)
|
||||
cls.cache[modname] = obj
|
||||
return obj
|
||||
|
||||
def find_attrs(self):
|
||||
attr_visitor = ClassAttrVisitor(number2name, '')
|
||||
def find_attr_docs(self, scope=''):
|
||||
attr_visitor = ClassAttrVisitor(number2name, scope)
|
||||
attr_visitor.visit(self.tree)
|
||||
return attr_visitor.collected
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
x0 = time.time()
|
||||
ma = ModuleAnalyzer.for_file('sphinx/builders/html.py', 'sphinx.builders.html')
|
||||
|
Loading…
Reference in New Issue
Block a user