[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