mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Fix documentation of inner classes in autodoc. Messy!
This commit is contained in:
parent
409cc1fe39
commit
dafb55a467
4
CHANGES
4
CHANGES
@ -35,7 +35,9 @@ New features added
|
|||||||
|
|
||||||
- Italian by Sandro Dentella.
|
- Italian by Sandro Dentella.
|
||||||
|
|
||||||
* Extension API:
|
* Extensions and API:
|
||||||
|
|
||||||
|
- Autodoc now handles inner classes and their methods.
|
||||||
|
|
||||||
- There is now a ``Sphinx.add_lexer()`` method to be able to use
|
- There is now a ``Sphinx.add_lexer()`` method to be able to use
|
||||||
custom Pygments lexers easily.
|
custom Pygments lexers easily.
|
||||||
|
@ -23,7 +23,6 @@ from docutils.parsers.rst import directives
|
|||||||
from docutils.statemachine import ViewList
|
from docutils.statemachine import ViewList
|
||||||
|
|
||||||
from sphinx.util import rpartition, nested_parse_with_titles
|
from sphinx.util import rpartition, nested_parse_with_titles
|
||||||
from sphinx.directives.desc import py_sig_re
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
base_exception = BaseException
|
base_exception = BaseException
|
||||||
@ -33,6 +32,14 @@ except NameError:
|
|||||||
_charset_re = re.compile(r'coding[:=]\s*([-\w.]+)')
|
_charset_re = re.compile(r'coding[:=]\s*([-\w.]+)')
|
||||||
_module_charsets = {}
|
_module_charsets = {}
|
||||||
|
|
||||||
|
py_ext_sig_re = re.compile(
|
||||||
|
r'''^ ([\w.]+::)? # explicit module name
|
||||||
|
([\w.]+\.)? # module and/or class name(s)
|
||||||
|
(\w+) \s* # thing name
|
||||||
|
(?: \((.*)\) # optional arguments
|
||||||
|
(\s* -> \s* .*)? )? $ # optional return annotation
|
||||||
|
''', re.VERBOSE)
|
||||||
|
|
||||||
|
|
||||||
class Options(object):
|
class Options(object):
|
||||||
pass
|
pass
|
||||||
@ -282,24 +289,31 @@ class RstGenerator(object):
|
|||||||
# first, parse the definition -- auto directives for classes and functions
|
# first, parse the definition -- auto directives for classes and functions
|
||||||
# can contain a signature which is then used instead of an autogenerated one
|
# can contain a signature which is then used instead of an autogenerated one
|
||||||
try:
|
try:
|
||||||
path, base, args, retann = py_sig_re.match(name).groups()
|
mod, path, base, args, retann = py_ext_sig_re.match(name).groups()
|
||||||
except:
|
except:
|
||||||
self.warn('invalid signature for auto%s (%r)' % (what, name))
|
self.warn('invalid signature for auto%s (%r)' % (what, name))
|
||||||
return
|
return None, [], None, None
|
||||||
# fullname is the fully qualified name, base the name after the last dot
|
|
||||||
fullname = (path or '') + base
|
# support explicit module and class name separation via ::
|
||||||
|
if mod is not None:
|
||||||
|
mod = mod[:-2]
|
||||||
|
parents = path and path.rstrip('.').split('.') or []
|
||||||
|
else:
|
||||||
|
parents = []
|
||||||
|
|
||||||
if what == 'module':
|
if what == 'module':
|
||||||
|
if mod is not None:
|
||||||
|
self.warn('"::" in automodule name doesn\'t make sense')
|
||||||
if args or retann:
|
if args or retann:
|
||||||
self.warn('ignoring signature arguments and return annotation '
|
self.warn('ignoring signature arguments and return annotation '
|
||||||
'for automodule %s' % fullname)
|
'for automodule %s' % name)
|
||||||
return fullname, fullname, [], None, None
|
return (path or '') + base, [], None, None
|
||||||
|
|
||||||
elif what in ('class', 'exception', 'function'):
|
elif what in ('exception', 'function', 'class'):
|
||||||
|
if mod is None:
|
||||||
if path:
|
if path:
|
||||||
mod = path.rstrip('.')
|
mod = path.rstrip('.')
|
||||||
else:
|
else:
|
||||||
mod = None
|
|
||||||
# if documenting a toplevel object without explicit module, it can
|
# if documenting a toplevel object without explicit module, it can
|
||||||
# be contained in another auto directive ...
|
# be contained in another auto directive ...
|
||||||
if hasattr(self.env, 'autodoc_current_module'):
|
if hasattr(self.env, 'autodoc_current_module'):
|
||||||
@ -307,9 +321,10 @@ class RstGenerator(object):
|
|||||||
# ... or in the scope of a module directive
|
# ... or in the scope of a module directive
|
||||||
if not mod:
|
if not mod:
|
||||||
mod = self.env.currmodule
|
mod = self.env.currmodule
|
||||||
return fullname, mod, [base], args, retann
|
return mod, parents + [base], args, retann
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
if mod is None:
|
||||||
if path:
|
if path:
|
||||||
mod_cls = path.rstrip('.')
|
mod_cls = path.rstrip('.')
|
||||||
else:
|
else:
|
||||||
@ -323,14 +338,15 @@ class RstGenerator(object):
|
|||||||
mod_cls = self.env.currclass
|
mod_cls = self.env.currclass
|
||||||
# ... if still None, there's no way to know
|
# ... if still None, there's no way to know
|
||||||
if mod_cls is None:
|
if mod_cls is None:
|
||||||
return fullname, None, [], args, retann
|
return None, [], None, None
|
||||||
mod, cls = rpartition(mod_cls, '.')
|
mod, cls = rpartition(mod_cls, '.')
|
||||||
|
parents = [cls]
|
||||||
# if the module name is still missing, get it like above
|
# if the module name is still missing, get it like above
|
||||||
if not mod and hasattr(self.env, 'autodoc_current_module'):
|
if not mod and hasattr(self.env, 'autodoc_current_module'):
|
||||||
mod = self.env.autodoc_current_module
|
mod = self.env.autodoc_current_module
|
||||||
if not mod:
|
if not mod:
|
||||||
mod = self.env.currmodule
|
mod = self.env.currmodule
|
||||||
return fullname, mod, [cls, base], args, retann
|
return mod, parents + [base], args, retann
|
||||||
|
|
||||||
def format_signature(self, what, name, obj, args, retann):
|
def format_signature(self, what, name, obj, args, retann):
|
||||||
"""
|
"""
|
||||||
@ -386,13 +402,15 @@ class RstGenerator(object):
|
|||||||
"""
|
"""
|
||||||
Generate reST for the object in self.result.
|
Generate reST for the object in self.result.
|
||||||
"""
|
"""
|
||||||
fullname, mod, objpath, args, retann = self.resolve_name(what, name)
|
mod, objpath, args, retann = self.resolve_name(what, name)
|
||||||
if not mod:
|
if not mod:
|
||||||
# need a module to import
|
# need a module to import
|
||||||
self.warn('don\'t know which module to import for autodocumenting %r '
|
self.warn('don\'t know which module to import for autodocumenting %r '
|
||||||
'(try placing a "module" or "currentmodule" directive in the '
|
'(try placing a "module" or "currentmodule" directive in the '
|
||||||
'document, or giving an explicit module name)' % fullname)
|
'document, or giving an explicit module name)' % name)
|
||||||
return
|
return
|
||||||
|
# fully-qualified name
|
||||||
|
fullname = mod + (objpath and '.' + '.'.join(objpath) or '')
|
||||||
|
|
||||||
# the name to put into the generated directive -- doesn't contain the module
|
# the name to put into the generated directive -- doesn't contain the module
|
||||||
name_in_directive = '.'.join(objpath) or mod
|
name_in_directive = '.'.join(objpath) or mod
|
||||||
@ -423,7 +441,7 @@ class RstGenerator(object):
|
|||||||
|
|
||||||
# format the object's signature, if any
|
# format the object's signature, if any
|
||||||
try:
|
try:
|
||||||
sig = self.format_signature(what, name, todoc, args, retann)
|
sig = self.format_signature(what, fullname, todoc, args, retann)
|
||||||
except Exception, err:
|
except Exception, err:
|
||||||
self.warn('error while formatting signature for %s: %s' %
|
self.warn('error while formatting signature for %s: %s' %
|
||||||
(fullname, err))
|
(fullname, err))
|
||||||
@ -548,8 +566,7 @@ class RstGenerator(object):
|
|||||||
if isinstance(member, (types.FunctionType,
|
if isinstance(member, (types.FunctionType,
|
||||||
types.BuiltinFunctionType)):
|
types.BuiltinFunctionType)):
|
||||||
memberwhat = 'function'
|
memberwhat = 'function'
|
||||||
elif isinstance(member, types.ClassType) or \
|
elif isinstance(member, (types.ClassType, type)):
|
||||||
isinstance(member, type):
|
|
||||||
if issubclass(member, base_exception):
|
if issubclass(member, base_exception):
|
||||||
memberwhat = 'exception'
|
memberwhat = 'exception'
|
||||||
else:
|
else:
|
||||||
@ -558,14 +575,18 @@ class RstGenerator(object):
|
|||||||
# XXX: todo -- attribute docs
|
# XXX: todo -- attribute docs
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
if callable(member):
|
if isinstance(member, (types.ClassType, type)):
|
||||||
|
memberwhat = 'class'
|
||||||
|
elif callable(member):
|
||||||
memberwhat = 'method'
|
memberwhat = 'method'
|
||||||
elif isdescriptor(member):
|
elif isdescriptor(member):
|
||||||
memberwhat = 'attribute'
|
memberwhat = 'attribute'
|
||||||
else:
|
else:
|
||||||
# XXX: todo -- attribute docs
|
# XXX: todo -- attribute docs
|
||||||
continue
|
continue
|
||||||
full_membername = fullname + '.' + membername
|
# give explicitly separated module name, so that members of inner classes
|
||||||
|
# can be documented
|
||||||
|
full_membername = mod + '::' + '.'.join(objpath + [membername])
|
||||||
self.generate(memberwhat, full_membername, ['__all__'], None, indent,
|
self.generate(memberwhat, full_membername, ['__all__'], None, indent,
|
||||||
check_module=members_check_module)
|
check_module=members_check_module)
|
||||||
|
|
||||||
|
@ -82,41 +82,41 @@ def skip_member(app, what, name, obj, skip, options):
|
|||||||
def test_resolve_name():
|
def test_resolve_name():
|
||||||
# for modules
|
# for modules
|
||||||
assert gen.resolve_name('module', 'test_autodoc') == \
|
assert gen.resolve_name('module', 'test_autodoc') == \
|
||||||
('test_autodoc', 'test_autodoc', [], None, None)
|
('test_autodoc', [], None, None)
|
||||||
assert gen.resolve_name('module', 'test.test_autodoc') == \
|
assert gen.resolve_name('module', 'test.test_autodoc') == \
|
||||||
('test.test_autodoc', 'test.test_autodoc', [], None, None)
|
('test.test_autodoc', [], None, None)
|
||||||
|
|
||||||
assert gen.resolve_name('module', 'test(arg)') == \
|
assert gen.resolve_name('module', 'test(arg)') == \
|
||||||
('test', 'test', [], None, None)
|
('test', [], None, None)
|
||||||
assert 'ignoring signature arguments' in gen.warnings[0]
|
assert 'ignoring signature arguments' in gen.warnings[0]
|
||||||
del gen.warnings[:]
|
del gen.warnings[:]
|
||||||
|
|
||||||
# for functions/classes
|
# for functions/classes
|
||||||
assert gen.resolve_name('function', 'util.raises') == \
|
assert gen.resolve_name('function', 'util.raises') == \
|
||||||
('util.raises', 'util', ['raises'], None, None)
|
('util', ['raises'], None, None)
|
||||||
assert gen.resolve_name('function', 'util.raises(exc) -> None') == \
|
assert gen.resolve_name('function', 'util.raises(exc) -> None') == \
|
||||||
('util.raises', 'util', ['raises'], 'exc', ' -> None')
|
('util', ['raises'], 'exc', ' -> None')
|
||||||
gen.env.autodoc_current_module = 'util'
|
gen.env.autodoc_current_module = 'util'
|
||||||
assert gen.resolve_name('function', 'raises') == \
|
assert gen.resolve_name('function', 'raises') == \
|
||||||
('raises', 'util', ['raises'], None, None)
|
('util', ['raises'], None, None)
|
||||||
gen.env.autodoc_current_module = None
|
gen.env.autodoc_current_module = None
|
||||||
gen.env.currmodule = 'util'
|
gen.env.currmodule = 'util'
|
||||||
assert gen.resolve_name('function', 'raises') == \
|
assert gen.resolve_name('function', 'raises') == \
|
||||||
('raises', 'util', ['raises'], None, None)
|
('util', ['raises'], None, None)
|
||||||
assert gen.resolve_name('class', 'TestApp') == \
|
assert gen.resolve_name('class', 'TestApp') == \
|
||||||
('TestApp', 'util', ['TestApp'], None, None)
|
('util', ['TestApp'], None, None)
|
||||||
|
|
||||||
# for members
|
# for members
|
||||||
gen.env.currmodule = 'foo'
|
gen.env.currmodule = 'foo'
|
||||||
assert gen.resolve_name('method', 'util.TestApp.cleanup') == \
|
assert gen.resolve_name('method', 'util.TestApp.cleanup') == \
|
||||||
('util.TestApp.cleanup', 'util', ['TestApp', 'cleanup'], None, None)
|
('util', ['TestApp', 'cleanup'], None, None)
|
||||||
gen.env.currmodule = 'util'
|
gen.env.currmodule = 'util'
|
||||||
gen.env.currclass = 'Foo'
|
gen.env.currclass = 'Foo'
|
||||||
gen.env.autodoc_current_class = 'TestApp'
|
gen.env.autodoc_current_class = 'TestApp'
|
||||||
assert gen.resolve_name('method', 'cleanup') == \
|
assert gen.resolve_name('method', 'cleanup') == \
|
||||||
('cleanup', 'util', ['TestApp', 'cleanup'], None, None)
|
('util', ['TestApp', 'cleanup'], None, None)
|
||||||
assert gen.resolve_name('method', 'TestApp.cleanup') == \
|
assert gen.resolve_name('method', 'TestApp.cleanup') == \
|
||||||
('TestApp.cleanup', 'util', ['TestApp', 'cleanup'], None, None)
|
('util', ['TestApp', 'cleanup'], None, None)
|
||||||
|
|
||||||
# and clean up
|
# and clean up
|
||||||
gen.env.currmodule = None
|
gen.env.currmodule = None
|
||||||
@ -321,17 +321,17 @@ def test_generate():
|
|||||||
assert_works('exception', 'test_autodoc.CustomEx', [], None)
|
assert_works('exception', 'test_autodoc.CustomEx', [], None)
|
||||||
|
|
||||||
# test diverse inclusion settings for members
|
# test diverse inclusion settings for members
|
||||||
should = [('class', 'Class')]
|
should = [('class', 'test_autodoc.Class')]
|
||||||
assert_processes(should, 'class', 'Class', [], None)
|
assert_processes(should, 'class', 'Class', [], None)
|
||||||
should.extend([('method', 'Class.meth')])
|
should.extend([('method', 'test_autodoc.Class.meth')])
|
||||||
assert_processes(should, 'class', 'Class', ['meth'], None)
|
assert_processes(should, 'class', 'Class', ['meth'], None)
|
||||||
should.extend([('attribute', 'Class.prop')])
|
should.extend([('attribute', 'test_autodoc.Class.prop')])
|
||||||
assert_processes(should, 'class', 'Class', ['__all__'], None)
|
assert_processes(should, 'class', 'Class', ['__all__'], None)
|
||||||
options.undoc_members = True
|
options.undoc_members = True
|
||||||
should.append(('method', 'Class.undocmeth'))
|
should.append(('method', 'test_autodoc.Class.undocmeth'))
|
||||||
assert_processes(should, 'class', 'Class', ['__all__'], None)
|
assert_processes(should, 'class', 'Class', ['__all__'], None)
|
||||||
options.inherited_members = True
|
options.inherited_members = True
|
||||||
should.append(('method', 'Class.inheritedmeth'))
|
should.append(('method', 'test_autodoc.Class.inheritedmeth'))
|
||||||
assert_processes(should, 'class', 'Class', ['__all__'], None)
|
assert_processes(should, 'class', 'Class', ['__all__'], None)
|
||||||
|
|
||||||
# test module flags
|
# test module flags
|
||||||
@ -363,6 +363,12 @@ def test_generate():
|
|||||||
assert_result_contains('.. class:: CustomDict', 'class', 'CustomDict',
|
assert_result_contains('.. class:: CustomDict', 'class', 'CustomDict',
|
||||||
['__all__'], None)
|
['__all__'], None)
|
||||||
|
|
||||||
|
# test inner class handling
|
||||||
|
assert_processes([('class', 'test_autodoc.Outer'),
|
||||||
|
('class', 'test_autodoc.Outer.Inner'),
|
||||||
|
('method', 'test_autodoc.Outer.Inner.meth')],
|
||||||
|
'class', 'Outer', ['__all__'], None)
|
||||||
|
|
||||||
|
|
||||||
# --- generate fodder ------------
|
# --- generate fodder ------------
|
||||||
|
|
||||||
@ -404,3 +410,13 @@ def function(foo, *args, **kwds):
|
|||||||
Return spam.
|
Return spam.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Outer(object):
|
||||||
|
"""Foo"""
|
||||||
|
|
||||||
|
class Inner(object):
|
||||||
|
"""Foo"""
|
||||||
|
|
||||||
|
def meth(self):
|
||||||
|
"""Foo"""
|
||||||
|
Loading…
Reference in New Issue
Block a user