Commits

Anonymous committed b1c0641

Correctly report source location for docstrings included
with autodoc.

  • Participants
  • Parent commits 596ba81

Comments (0)

Files changed (2)

   which makes the builder select the one that matches best.
 
 * The new config value `exclude_trees` can be used to exclude whole
-  subtrees from the search for source files.
+  subtrees from the search for source files.  Thanks to Sebastian
+  Wiesner.
 
 * Defaults for configuration values can now be callables, which allows
   dynamic defaults.
 Bugs fixed
 ----------
 
+* Correctly report the source location for docstrings included with
+  autodoc.
+
 * Fix the LaTeX output of description units with multiple signatures.
 
 * Handle the figure directive in LaTeX output.

sphinx/ext/autodoc.py

 _module_charsets = {}
 
 
+class AutodocReporter(object):
+    """
+    A reporter replacement that assigns the correct source name
+    and line number to a system message, as recorded in a ViewList.
+    """
+    def __init__(self, viewlist, reporter):
+        self.viewlist = viewlist
+        self.reporter = reporter
+
+    def __getattr__(self, name):
+        return getattr(self.reporter, name)
+
+    def system_message(self, level, message, *children, **kwargs):
+        if 'line' in kwargs:
+            try:
+                source, line = self.viewlist.items[kwargs['line']]
+            except IndexError:
+                pass
+            else:
+                kwargs['source'] = source
+                kwargs['line'] = line
+        return self.reporter.system_message(level, message,
+                                            *children, **kwargs)
+
+    def debug(self, *args, **kwargs):
+        if self.reporter.debug_flag:
+            return self.system_message(0, *args, **kwargs)
+
+    def info(self, *args, **kwargs):
+        return self.system_message(1, *args, **kwargs)
+
+    def warning(self, *args, **kwargs):
+        return self.system_message(2, *args, **kwargs)
+
+    def error(self, *args, **kwargs):
+        return self.system_message(3, *args, **kwargs)
+
+    def severe(self, *args, **kwargs):
+        return self.system_message(4, *args, **kwargs)
+
+
 def isdescriptor(x):
     """Check if the object is some kind of descriptor."""
     for item in '__get__', '__set__', '__delete__':
     """Return the charset of the given module (cached in _module_charsets)."""
     if module in _module_charsets:
         return _module_charsets[module]
-    filename = __import__(module, None, None, ['']).__file__
-    if filename[-4:] in ('.pyc', '.pyo'):
+    try:
+        filename = __import__(module, None, None, ['']).__file__
+    except (ImportError, AttributeError):
+        return None
+    if filename[-4:].lower() in ('.pyc', '.pyo'):
         filename = filename[:-1]
     for line in [linecache.getline(filename, x) for x in (1, 2)]:
         match = _charset_re.search(line)
                  lineno, indent='', 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:
     except:
         warning = document.reporter.warning(
             'invalid signature for auto%s (%r)' % (what, name), line=lineno)
-        return [warning], ViewList()
+        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
     # the name to put into the generated directive -- doesn't contain the module
     name_in_directive = '.'.join(objpath) or mod
 
-    # 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('', '')
-
     # now, import the module and get docstring(s) of object to document
     try:
         todoc = module = __import__(mod, None, None, ['foo'])
-        if filename_set is not None and hasattr(module, '__file__') and module.__file__:
+        if hasattr(module, '__file__') and module.__file__:
             modfile = module.__file__
-            if modfile.lower().endswith('.pyc') or modfile.lower().endswith('.pyo'):
+            if modfile[-4:].lower() in ('.pyc', '.pyo'):
                 modfile = modfile[:-1]
-            filename_set.add(modfile)
+            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):
                 (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('', '')
+
     # now, create the directive header
     result.append(indent + '.. %s:: %s%s' % (what, name_in_directive, args),
                   '<autodoc>')
     if what != 'module':
         indent += '   '
 
+    if modfile:
+        sourcename = '%s:docstring of %s' % (modfile, fullname)
+    else:
+        sourcename = 'docstring of %s' % fullname
+
     # add content from docstrings
     for i, line in enumerate(get_doc(what, todoc, env)):
-        result.append(indent + line, '<docstring of %s>' % fullname, i)
+        result.append(indent + line, sourcename, i)
 
     # add source content, if present
     if add_content:
     filename_set = set()
     warnings, result = generate_rst(what, name, members, inherited, undoc, content,
                                     state.document, lineno, filename_set=filename_set)
+    if result is None:
+        return warnings
 
     # record all filenames as dependencies -- this will at least partially make
     # automatic invalidation possible
     for fn in 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)
     if dirname == 'automodule':
         node = nodes.section()
         # hack around title style bookkeeping
         surrounding_section_level = state.memo.section_level
         state.memo.title_styles = []
         state.memo.section_level = 0
-        state.nested_parse(result, content_offset, node, match_titles=1)
+        state.nested_parse(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, content_offset, node)
+        state.nested_parse(result, 0, node)
+    state.memo.reporter = old_reporter
     return warnings + node.children
 
 def auto_directive(*args, **kwds):