[Python-checkins] r65154 - doctools/trunk/sphinx/ext/autodoc.py
georg.brandl
python-checkins at python.org
Sun Jul 20 13:12:14 CEST 2008
Author: georg.brandl
Date: Sun Jul 20 13:12:14 2008
New Revision: 65154
Log:
Refactor generate_rst into a class.
Modified:
doctools/trunk/sphinx/ext/autodoc.py
Modified: doctools/trunk/sphinx/ext/autodoc.py
==============================================================================
--- doctools/trunk/sphinx/ext/autodoc.py (original)
+++ doctools/trunk/sphinx/ext/autodoc.py Sun Jul 20 13:12:14 2008
@@ -202,309 +202,312 @@
return charset
-def get_doc(what, name, obj, options, env):
- """Format and yield lines of the docstring(s) for the object."""
- docstrings = []
- if getattr(obj, '__doc__', None):
- docstrings.append(obj.__doc__)
- # skip some lines in module docstrings if configured
- if what == 'module' and env.config.automodule_skip_lines and docstrings:
- docstrings[0] = '\n'.join(docstrings[0].splitlines()
- [env.config.automodule_skip_lines:])
- # for classes, what the "docstring" is can be controlled via an option
- if what in ('class', 'exception'):
- content = env.config.autoclass_content
- if content in ('both', 'init'):
- initdocstring = getattr(obj, '__init__', None).__doc__
- # for new-style classes, no __init__ means default __init__
- if initdocstring == object.__init__.__doc__:
- initdocstring = None
- if initdocstring:
- if content == 'init':
- docstrings = [initdocstring]
+class RstGenerator(object):
+ def __init__(self, options, document, lineno):
+ self.options = options
+ self.env = document.settings.env
+ self.reporter = document.reporter
+ self.lineno = lineno
+ self.filename_set = set()
+ self.warnings = []
+ self.result = ViewList()
+
+ def warn(self, msg):
+ self.warnings.append(self.reporter.warning(msg, line=self.lineno))
+
+ def get_doc(self, what, name, obj):
+ """Format and yield lines of the docstring(s) for the object."""
+ docstrings = []
+ if getattr(obj, '__doc__', None):
+ docstrings.append(obj.__doc__)
+ # skip some lines in module docstrings if configured
+ if what == 'module' and self.env.config.automodule_skip_lines and docstrings:
+ docstrings[0] = '\n'.join(docstrings[0].splitlines()
+ [self.env.config.automodule_skip_lines:])
+ # for classes, what the "docstring" is can be controlled via an option
+ if what in ('class', 'exception'):
+ content = self.env.config.autoclass_content
+ if content in ('both', 'init'):
+ initdocstring = getattr(obj, '__init__', None).__doc__
+ # for new-style classes, no __init__ means default __init__
+ if initdocstring == object.__init__.__doc__:
+ initdocstring = None
+ if initdocstring:
+ if content == 'init':
+ docstrings = [initdocstring]
+ else:
+ docstrings.append('\n\n' + initdocstring)
+ # the default is only the class docstring
+
+ # decode the docstrings using the module's source encoding
+ charset = None
+ module = getattr(obj, '__module__', None)
+ if module is not None:
+ charset = get_module_charset(module)
+
+ for docstring in docstrings:
+ if isinstance(docstring, str):
+ if charset:
+ docstring = docstring.decode(charset)
else:
- docstrings.append('\n\n' + initdocstring)
- # the default is only the class docstring
+ try:
+ # try decoding with utf-8, should only work for real UTF-8
+ docstring = docstring.decode('utf-8')
+ except UnicodeError:
+ # last resort -- can't fail
+ docstring = docstring.decode('latin1')
+ docstringlines = prepare_docstring(docstring)
+ if self.env.app:
+ # let extensions preprocess docstrings
+ self.env.app.emit('autodoc-process-docstring',
+ what, name, obj, self.options, docstringlines)
+ for line in docstringlines:
+ yield line
+
+ def resolve_name(self, what, name):
+ """
+ Determine what module to import and what attribute to document.
+
+ Returns a tuple of: the full name, the module name, a path of
+ names to get via getattr, the signature and return annotation.
+ """
+ # first, parse the definition -- auto directives for classes and functions
+ # can contain a signature which is then used instead of an autogenerated one
+ try:
+ path, base, args, retann = py_sig_re.match(name).groups()
+ except:
+ self.warn('invalid signature for auto%s (%r)' % (what, name))
+ return
+ # fullname is the fully qualified name, base the name after the last dot
+ fullname = (path or '') + base
- # decode the docstrings using the module's source encoding
- charset = None
- module = getattr(obj, '__module__', None)
- if module is not None:
- charset = get_module_charset(module)
-
- for docstring in docstrings:
- if isinstance(docstring, str):
- if charset:
- docstring = docstring.decode(charset)
+ if what == 'module':
+ if args or retann:
+ self.warn('ignoring signature arguments and return annotation '
+ 'for automodule %s' % mod)
+ return fullname, fullname, [], None, None
+
+ elif what in ('class', 'exception', 'function'):
+ if path:
+ mod = path.rstrip('.')
else:
- try:
- # try decoding with utf-8, should only work for real UTF-8
- docstring = docstring.decode('utf-8')
- except UnicodeError:
- # last resort -- can't fail
- docstring = docstring.decode('latin1')
- docstringlines = prepare_docstring(docstring)
- if env.app:
- # let extensions preprocess docstrings
- env.app.emit('autodoc-process-docstring',
- what, name, obj, options, docstringlines)
- for line in docstringlines:
- yield line
-
-
-def format_signature(what, obj):
- """Return the signature of the object, formatted for display."""
- if what not in ('class', 'method', 'function'):
- return ''
- if what == 'class':
- # for classes, the relevant signature is the __init__ method's
- obj = getattr(obj, '__init__', None)
- # classes without __init__ method?
- if obj is None or obj is object.__init__ or not \
- (inspect.ismethod(obj) or inspect.isfunction(obj)):
- return ''
- argspec = inspect.getargspec(obj)
- if what in ('class', 'method') and argspec[0] and \
- argspec[0][0] in ('cls', 'self'):
- del argspec[0][0]
- return inspect.formatargspec(*argspec)
-
-
-def generate_rst(what, name, members, options, add_content, document, lineno,
- indent=u'', filename_set=None, check_module=False):
- env = document.settings.env
-
- result = None
-
- # first, parse the definition -- auto directives for classes and functions
- # can contain a signature which is then used instead of an autogenerated one
- try:
- path, base, signature, retann = py_sig_re.match(name).groups()
- except:
- warning = document.reporter.warning(
- 'invalid signature for auto%s (%r)' % (what, name), line=lineno)
- return [warning], result
- # fullname is the fully qualified name, base the name after the last dot
- fullname = (path or '') + base
- # path is the name up to the last dot
- path = path and path.rstrip('.')
-
- warnings = []
-
- # determine what module to import -- mod is the module name, objpath the
- # path of names to get via getattr
- mod = None
- if what == 'module':
- mod = fullname
- if signature:
- warnings.append(document.reporter.warning(
- 'ignoring arguments for automodule %s' % mod, line=lineno))
- objpath = []
- elif what in ('class', 'exception', 'function'):
- if path:
- mod = path
+ mod = None
+ # if documenting a toplevel object without explicit module, it can
+ # be contained in another auto directive ...
+ if hasattr(self.env, 'autodoc_current_module'):
+ mod = self.env.autodoc_current_module
+ # ... or in the scope of a module directive
+ if not mod:
+ mod = self.env.currmodule
+ return fullname, mod, [base], args, retann
+
else:
- # if documenting a toplevel object without explicit module, it can
- # be contained in another auto directive ...
- if hasattr(env, 'autodoc_current_module'):
- mod = env.autodoc_current_module
- # ... or in the scope of a module directive
+ if path:
+ mod_cls = path.rstrip('.')
+ else:
+ # if documenting a class-level object without path, there must be a
+ # current class, either from a parent auto directive ...
+ if hasattr(self.env, 'autodoc_current_class'):
+ mod_cls = self.env.autodoc_current_class
+ # ... or from a class directive
+ if not mod_cls:
+ mod_cls = self.env.currclass
+ mod, cls = rpartition(mod_cls, '.')
+ # if the module name is still missing, get it like above
+ if not mod and hasattr(self.env, 'autodoc_current_module'):
+ mod = self.env.autodoc_current_module
if not mod:
- mod = env.currmodule
- objpath = [base]
- else:
- if path:
- mod_cls = path
- else:
- # if documenting a class-level object without path, there must be a
- # current class, either from a parent auto directive ...
- if hasattr(env, 'autodoc_current_class'):
- mod_cls = env.autodoc_current_class
- # ... or from a class directive
- if not mod_cls:
- mod_cls = env.currclass
- mod, cls = rpartition(mod_cls, '.')
- # if the module name is still missing, get it like above
- if not mod and hasattr(env, 'autodoc_current_module'):
- mod = env.autodoc_current_module
+ mod = self.env.currmodule
+ return fullname, mod, [cls, base], args, retann
+
+ def format_signature(self, what, obj, args, retann):
+ """
+ Return the signature of the object, formatted for display.
+ """
+ if what not in ('class', 'method', 'function'):
+ return ''
+ if args is not None:
+ # signature given explicitly
+ return '(%s)%s' % (args, retann or '')
+ if what == 'class':
+ # for classes, the relevant signature is the __init__ method's
+ obj = getattr(obj, '__init__', None)
+ # classes without __init__ method?
+ if obj is None or obj is object.__init__ or not \
+ (inspect.ismethod(obj) or inspect.isfunction(obj)):
+ return ''
+ argspec = inspect.getargspec(obj)
+ if what in ('class', 'method') and argspec[0] and \
+ argspec[0][0] in ('cls', 'self'):
+ del argspec[0][0]
+ return inspect.formatargspec(*argspec)
+
+ def generate(self, what, name, members, add_content, indent=u'', check_module=False):
+ """
+ Generate reST for the object in self.result.
+ """
+ fullname, mod, objpath, args, retann = self.resolve_name(what, name)
if not mod:
- mod = env.currmodule
- objpath = [cls, base]
+ # need a module to import
+ self.warn('don\'t know which module to import for autodocumenting %r '
+ '(try placing a "module" or "currentmodule" directive in the '
+ 'document, or giving an explicit module name)' % fullname)
+ return
- # by this time, a module *must* be determined
- if mod is None:
- warnings.append(document.reporter.warning(
- 'don\'t know which module to import for autodocumenting %r '
- '(try placing a "module" or "currentmodule" directive in the document, '
- 'or giving an explicit module name)' % fullname, line=lineno))
- return warnings, result
-
- # the name to put into the generated directive -- doesn't contain the module
- name_in_directive = '.'.join(objpath) or mod
-
- # now, import the module and get docstring(s) of object to document
- try:
- todoc = module = __import__(mod, None, None, ['foo'])
- if hasattr(module, '__file__') and module.__file__:
- modfile = module.__file__
- if modfile[-4:].lower() in ('.pyc', '.pyo'):
- modfile = modfile[:-1]
- if filename_set is not None:
- filename_set.add(modfile)
- else:
- modfile = None # e.g. for builtin and C modules
- for part in objpath:
- todoc = getattr(todoc, part)
- except (ImportError, AttributeError), err:
- warnings.append(document.reporter.warning(
- 'autodoc can\'t import/find %s %r, it reported error: "%s",'
- 'please check your spelling and sys.path' %
- (what, str(fullname), err), line=lineno))
- return warnings, result
-
- # check __module__ of object if wanted (for members not given explicitly)
- if check_module:
- if hasattr(todoc, '__module__'):
- if todoc.__module__ != mod:
- return warnings, result
-
- # format the object's signature, if any
- if signature is not None:
- # signature given explicitly -- the parentheses were stripped by the regex
- args = '(%s)' % signature
- if retann:
- args += retann
- else:
- try:
- args = format_signature(what, todoc)
- except Exception, err:
- warnings.append(document.reporter.warning(
- 'error while formatting signature for %s: %s' %
- (fullname, err), line=lineno))
- args = ''
-
- # make sure that the view list starts with an empty line. This is
- # necessary for some situations where another directive preprocesses
- # reST and no starting newline is present
- result = ViewList()
- result.append(u'', '')
-
- # now, create the directive header
- directive = (what == 'method' and is_static_method(todoc)) \
- and 'staticmethod' or what
- result.append(indent + u'.. %s:: %s%s' % (directive, name_in_directive, args),
- '<autodoc>')
- if what == 'module':
- # Add some module-specific options
- if options.synopsis:
- result.append(indent + u' :synopsis: ' + options.synopsis, '<autodoc>')
- if options.platform:
- result.append(indent + u' :platform: ' + options.platform, '<autodoc>')
- if options.deprecated:
- result.append(indent + u' :deprecated:', '<autodoc>')
- else:
- # Be explicit about the module, this is necessary since .. class:: doesn't
- # support a prepended module name
- result.append(indent + u' :module: %s' % mod, '<autodoc>')
- if options.noindex:
- result.append(indent + u' :noindex:', '<autodoc>')
- result.append(u'', '<autodoc>')
-
- if options.show_inheritance and what in ('class', 'exception'):
- if len(todoc.__bases__):
- bases = [b.__module__ == '__builtin__' and
- u':class:`%s`' % b.__name__ or
- u':class:`%s.%s`' % (b.__module__, b.__name__)
- for b in todoc.__bases__]
- result.append(indent + u' Bases: %s' % ', '.join(bases), '<autodoc>')
- result.append(u'', '<autodoc>')
-
- # the module directive doesn't have content
- if what != 'module':
- indent += u' '
+ # the name to put into the generated directive -- doesn't contain the module
+ name_in_directive = '.'.join(objpath) or mod
- if modfile:
- sourcename = '%s:docstring of %s' % (modfile, fullname)
- else:
- sourcename = 'docstring of %s' % fullname
+ # now, import the module and get object to document
+ try:
+ todoc = module = __import__(mod, None, None, ['foo'])
+ if hasattr(module, '__file__') and module.__file__:
+ modfile = module.__file__
+ if modfile[-4:].lower() in ('.pyc', '.pyo'):
+ modfile = modfile[:-1]
+ self.filename_set.add(modfile)
+ else:
+ modfile = None # e.g. for builtin and C modules
+ for part in objpath:
+ todoc = getattr(todoc, part)
+ except (ImportError, AttributeError), 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))
+ return
+
+ # check __module__ of object if wanted (for members not given explicitly)
+ if check_module:
+ if hasattr(todoc, '__module__'):
+ if todoc.__module__ != mod:
+ return
- # add content from docstrings
- for i, line in enumerate(get_doc(what, fullname, todoc, options, env)):
- result.append(indent + line, sourcename, i)
-
- # add source content, if present
- if add_content:
- for line, src in zip(add_content.data, add_content.items):
- result.append(indent + line, src[0], src[1])
-
- # document members?
- if not members or what in ('function', 'method', 'attribute'):
- return warnings, result
-
- # set current namespace for finding members
- env.autodoc_current_module = mod
- if objpath:
- env.autodoc_current_class = objpath[0]
-
- # add members, if possible
- _all = members == ['__all__']
- members_check_module = False
- if _all:
- # unqualified :members: given
+ # format the object's signature, if any
+ try:
+ sig = self.format_signature(what, todoc, args, retann)
+ except Exception, err:
+ self.warn('error while formatting signature for %s: %s' %
+ (fullname, err))
+ sig = ''
+
+ # make sure that the result starts with an empty line. This is
+ # necessary for some situations where another directive preprocesses
+ # reST and no starting newline is present
+ self.result.append(u'', '')
+
+ # now, create the directive header
+ directive = (what == 'method' and is_static_method(todoc)) \
+ and 'staticmethod' or what
+ self.result.append(indent + u'.. %s:: %s%s' %
+ (directive, name_in_directive, sig), '<autodoc>')
if what == 'module':
- # for implicit module members, check __module__ to avoid documenting
- # imported objects if __all__ is not defined
- members_check_module = not hasattr(todoc, '__all__')
- all_members = inspect.getmembers(todoc)
+ # Add some module-specific options
+ if self.options.synopsis:
+ self.result.append(indent + u' :synopsis: ' + self.options.synopsis,
+ '<autodoc>')
+ if self.options.platform:
+ self.result.append(indent + u' :platform: ' + self.options.platform,
+ '<autodoc>')
+ if self.options.deprecated:
+ self.result.append(indent + u' :deprecated:', '<autodoc>')
+ else:
+ # Be explicit about the module, this is necessary since .. class:: doesn't
+ # support a prepended module name
+ self.result.append(indent + u' :module: %s' % mod, '<autodoc>')
+ if self.options.noindex:
+ self.result.append(indent + u' :noindex:', '<autodoc>')
+ self.result.append(u'', '<autodoc>')
+
+ if self.options.show_inheritance and what in ('class', 'exception'):
+ if len(todoc.__bases__):
+ bases = [b.__module__ == '__builtin__' and
+ u':class:`%s`' % b.__name__ or
+ u':class:`%s.%s`' % (b.__module__, b.__name__)
+ for b in todoc.__bases__]
+ self.result.append(indent + u' Bases: %s' % ', '.join(bases),
+ '<autodoc>')
+ self.result.append(u'', '<autodoc>')
+
+ # the module directive doesn't have content
+ if what != 'module':
+ indent += u' '
+
+ if modfile:
+ sourcename = '%s:docstring of %s' % (modfile, fullname)
else:
- if options.inherited_members:
- # getmembers() uses dir() which pulls in members from all base classes
+ sourcename = 'docstring of %s' % fullname
+
+ # add content from docstrings
+ for i, line in enumerate(self.get_doc(what, fullname, todoc)):
+ self.result.append(indent + line, sourcename, i)
+
+ # add source content, if present
+ if add_content:
+ for line, src in zip(add_content.data, add_content.items):
+ self.result.append(indent + line, src[0], src[1])
+
+ # document members?
+ if not members or what in ('function', 'method', 'attribute'):
+ return
+
+ # set current namespace for finding members
+ self.env.autodoc_current_module = mod
+ if objpath:
+ self.env.autodoc_current_class = objpath[0]
+
+ # add members, if possible
+ _all = members == ['__all__']
+ members_check_module = False
+ if _all:
+ # unqualified :members: given
+ if what == 'module':
+ # for implicit module members, check __module__ to avoid documenting
+ # imported objects if __all__ is not defined
+ members_check_module = not hasattr(todoc, '__all__')
all_members = inspect.getmembers(todoc)
else:
- # __dict__ contains only the members directly defined in the class
- all_members = sorted(todoc.__dict__.iteritems())
- else:
- all_members = [(mname, getattr(todoc, mname)) for mname in members]
- for (membername, member) in all_members:
- # ignore members whose name starts with _ by default
- if _all and membername.startswith('_'):
- continue
- # ignore undocumented members if :undoc-members: is not given
- doc = getattr(member, '__doc__', None)
- if not options.undoc_members and not doc:
- continue
- if what == 'module':
- if isinstance(member, types.FunctionType):
- memberwhat = 'function'
- elif isinstance(member, types.ClassType) or \
- isinstance(member, type):
- if issubclass(member, base_exception):
- memberwhat = 'exception'
+ if self.options.inherited_members:
+ # getmembers() uses dir() which pulls in members from all
+ # base classes
+ all_members = inspect.getmembers(todoc)
else:
- memberwhat = 'class'
- else:
- # XXX: todo -- attribute docs
- continue
+ # __dict__ contains only the members directly defined in the class
+ all_members = sorted(todoc.__dict__.iteritems())
else:
- if callable(member):
- memberwhat = 'method'
- elif isdescriptor(member):
- memberwhat = 'attribute'
- else:
- # XXX: todo -- attribute docs
+ all_members = [(mname, getattr(todoc, mname)) for mname in members]
+ for (membername, member) in all_members:
+ # ignore members whose name starts with _ by default
+ if _all and membername.startswith('_'):
continue
- full_membername = fullname + '.' + membername
- subwarn, subres = generate_rst(memberwhat, full_membername, ['__all__'],
- options, None, document, lineno, indent,
- check_module=members_check_module)
- warnings.extend(subwarn)
- if subres is not None:
- result.extend(subres)
-
- env.autodoc_current_module = None
- env.autodoc_current_class = None
+ # ignore undocumented members if :undoc-members: is not given
+ doc = getattr(member, '__doc__', None)
+ if not self.options.undoc_members and not doc:
+ continue
+ if what == 'module':
+ if isinstance(member, types.FunctionType):
+ memberwhat = 'function'
+ elif isinstance(member, types.ClassType) or \
+ isinstance(member, type):
+ if issubclass(member, base_exception):
+ memberwhat = 'exception'
+ else:
+ memberwhat = 'class'
+ else:
+ # XXX: todo -- attribute docs
+ continue
+ else:
+ if callable(member):
+ memberwhat = 'method'
+ elif isdescriptor(member):
+ memberwhat = 'attribute'
+ else:
+ # XXX: todo -- attribute docs
+ continue
+ full_membername = fullname + '.' + membername
+ self.generate(memberwhat, full_membername, ['__all__'], None, indent,
+ check_module=members_check_module)
- return warnings, result
+ self.env.autodoc_current_module = None
+ self.env.autodoc_current_class = None
def _auto_directive(dirname, arguments, options, content, lineno,
@@ -524,20 +527,19 @@
genopt.platform = options.get('platform', '')
genopt.deprecated = 'deprecated' in options
- filename_set = set()
- warnings, result = generate_rst(what, name, members, genopt, content, state.document,
- lineno, filename_set=filename_set)
- if result is None:
- return warnings
+ generator = RstGenerator(genopt, state.document, lineno)
+ generator.generate(what, name, members, content)
+ if not generator.result:
+ return generator.warnings
# record all filenames as dependencies -- this will at least partially make
# automatic invalidation possible
- for fn in filename_set:
+ for fn in generator.filename_set:
state.document.settings.env.note_dependency(fn)
# use a custom reporter that correctly assigns lines to source and lineno
old_reporter = state.memo.reporter
- state.memo.reporter = AutodocReporter(result, state.memo.reporter)
+ state.memo.reporter = AutodocReporter(generator.result, state.memo.reporter)
if dirname == 'automodule':
node = nodes.section()
# hack around title style bookkeeping
@@ -545,14 +547,14 @@
surrounding_section_level = state.memo.section_level
state.memo.title_styles = []
state.memo.section_level = 0
- state.nested_parse(result, 0, node, match_titles=1)
+ state.nested_parse(generator.result, 0, node, match_titles=1)
state.memo.title_styles = surrounding_title_styles
state.memo.section_level = surrounding_section_level
else:
node = nodes.paragraph()
- state.nested_parse(result, 0, node)
+ state.nested_parse(generator.result, 0, node)
state.memo.reporter = old_reporter
- return warnings + node.children
+ return generator.warnings + node.children
def auto_directive(*args, **kwds):
return _auto_directive(*args, **kwds)
More information about the Python-checkins
mailing list