From 34da6f89346a6524639a199ab7a0505c06a63c4d Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 15 Jun 2008 17:09:23 +0000 Subject: [PATCH] Correctly report source location for docstrings included with autodoc. --- CHANGES | 6 ++- sphinx/ext/autodoc.py | 90 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 80 insertions(+), 16 deletions(-) diff --git a/CHANGES b/CHANGES index f9a5b5557..ee1fc7e7f 100644 --- a/CHANGES +++ b/CHANGES @@ -42,7 +42,8 @@ New features added 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. @@ -58,6 +59,9 @@ New features added 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. diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py index ddf9b766f..f40c042d1 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc.py @@ -33,6 +33,47 @@ _charset_re = re.compile(r'coding[:=]\s*([-\w.]+)') _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__': @@ -65,8 +106,11 @@ def get_module_charset(module): """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) @@ -137,6 +181,8 @@ def generate_rst(what, name, members, inherited, undoc, add_content, document, 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: @@ -144,7 +190,7 @@ def generate_rst(what, name, members, inherited, undoc, add_content, document, 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 @@ -203,20 +249,17 @@ def generate_rst(what, name, members, inherited, undoc, add_content, document, # 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): @@ -244,6 +287,12 @@ def generate_rst(what, name, members, inherited, undoc, add_content, document, (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), '') @@ -257,9 +306,14 @@ def generate_rst(what, name, members, inherited, undoc, add_content, document, 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, '' % fullname, i) + result.append(indent + line, sourcename, i) # add source content, if present if add_content: @@ -349,12 +403,17 @@ def _auto_directive(dirname, arguments, options, content, lineno, 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 @@ -362,12 +421,13 @@ def _auto_directive(dirname, arguments, options, content, lineno, 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):