diff --git a/CHANGES b/CHANGES index 292dffa0e..f35e7ed54 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,8 @@ Bugs fixed ---------- * PR#311: sphinx-quickstart does not work on python 3.4. +* Fix :confval:`autodoc_docstring_signature` not working with signatures + in class docstrings. Release 1.3b1 (released Oct 10, 2014) @@ -127,6 +129,7 @@ Bugs fixed * #1438: Updated jQuery version from 1.8.3 to 1.11.1. * #1568: Fix a crash when a "centered" directive contains a reference. +* Now sphinx.ext.autodoc works with python-2.5 again. * #1563: :meth:`~sphinx.application.Sphinx.add_search_language` raises AssertionError for correct type of argument. Thanks to rikoman. * #1174: Fix smart quotes being applied inside roles like :rst:role:`program` or diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py index 5b0bda17a..c60372242 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc.py @@ -945,36 +945,37 @@ class DocstringSignatureMixin(object): """ def _find_signature(self, encoding=None): - docstrings = Documenter.get_doc(self, encoding) - if len(docstrings) != 1: - return - doclines = docstrings[0] - setattr(self, '__new_doclines', doclines) - if not doclines: - return - # match first line of docstring against signature RE - match = py_ext_sig_re.match(doclines[0]) - if not match: - return - exmod, path, base, args, retann = match.groups() - # the base name must match ours - if not self.objpath or base != self.objpath[-1]: - return - # re-prepare docstring to ignore indentation after signature - docstrings = Documenter.get_doc(self, encoding, 2) - doclines = docstrings[0] - # ok, now jump over remaining empty lines and set the remaining - # lines as the new doclines - i = 1 - while i < len(doclines) and not doclines[i].strip(): - i += 1 - setattr(self, '__new_doclines', doclines[i:]) - return args, retann + docstrings = self.get_doc(encoding) + self._new_docstrings = docstrings[:] + result = None + for i, doclines in enumerate(docstrings): + # no lines in docstring, no match + if not doclines: + continue + # match first line of docstring against signature RE + match = py_ext_sig_re.match(doclines[0]) + if not match: + continue + exmod, path, base, args, retann = match.groups() + # the base name must match ours + valid_names = [self.objpath[-1]] + if isinstance(self, ClassDocumenter): + valid_names.append('__init__') + if hasattr(self.object, '__mro__'): + valid_names.extend(cls.__name__ for cls in self.object.__mro__) + if base not in valid_names: + continue + # re-prepare docstring to ignore more leading indentation + self._new_docstrings[i] = prepare_docstring('\n'.join(doclines[1:])) + result = args, retann + # don't look any further + break + return result def get_doc(self, encoding=None, ignore=1): - lines = getattr(self, '__new_doclines', None) + lines = getattr(self, '_new_docstrings', None) if lines is not None: - return [lines] + return lines return Documenter.get_doc(self, encoding, ignore) def format_signature(self): @@ -1046,7 +1047,7 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): pass -class ClassDocumenter(ModuleLevelDocumenter): +class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): """ Specialized Documenter subclass for classes. """ @@ -1098,18 +1099,7 @@ class ClassDocumenter(ModuleLevelDocumenter): if self.doc_as_attr: return '' - # get __init__ method signature from __init__.__doc__ - if self.env.config.autodoc_docstring_signature: - # only act if the feature is enabled - init_doc = MethodDocumenter(self.directive, '__init__') - init_doc.object = self.get_attr(self.object, '__init__', None) - init_doc.objpath = ['__init__'] - result = init_doc._find_signature() - if result is not None: - # use args only for Class signature - return '(%s)' % result[0] - - return ModuleLevelDocumenter.format_signature(self) + return DocstringSignatureMixin.format_signature(self) def add_directive_header(self, sig): if self.doc_as_attr: @@ -1128,6 +1118,10 @@ class ClassDocumenter(ModuleLevelDocumenter): '') def get_doc(self, encoding=None, ignore=1): + lines = getattr(self, '_new_docstrings', None) + if lines is not None: + return lines + content = self.env.config.autoclass_content docstrings = [] @@ -1138,18 +1132,8 @@ class ClassDocumenter(ModuleLevelDocumenter): # for classes, what the "docstring" is can be controlled via a # config value; the default is only the class docstring if content in ('both', 'init'): - # get __init__ method document from __init__.__doc__ - if self.env.config.autodoc_docstring_signature: - # only act if the feature is enabled - init_doc = MethodDocumenter(self.directive, '__init__') - init_doc.object = self.get_attr(self.object, '__init__', None) - init_doc.objpath = ['__init__'] - init_doc._find_signature() # this effects to get_doc() result - initdocstring = '\n'.join( - ['\n'.join(l) for l in init_doc.get_doc(encoding)]) - else: - initdocstring = self.get_attr( - self.get_attr(self.object, '__init__', None), '__doc__') + initdocstring = self.get_attr( + self.get_attr(self.object, '__init__', None), '__doc__') # for new-style classes, no __init__ means default __init__ if (initdocstring is not None and (initdocstring == object.__init__.__doc__ or # for pypy diff --git a/sphinx/util/docstrings.py b/sphinx/util/docstrings.py index 6b66eee9b..ae973d49e 100644 --- a/sphinx/util/docstrings.py +++ b/sphinx/util/docstrings.py @@ -34,7 +34,8 @@ def prepare_docstring(s, ignore=1): if i < len(lines): lines[i] = lines[i].lstrip() if margin < sys.maxsize: - for i in range(ignore, len(lines)): lines[i] = lines[i][margin:] + for i in range(ignore, len(lines)): + lines[i] = lines[i][margin:] # Remove any leading blank lines. while lines and not lines[0]: lines.pop(0) diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 10921b652..96e46196a 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -151,9 +151,12 @@ def test_format_signature(): inst.fullname = name inst.doc_as_attr = False # for class objtype inst.object = obj + inst.objpath = [name] inst.args = args inst.retann = retann - return inst.format_signature() + res = inst.format_signature() + print res + return res # no signatures for modules assert formatsig('module', 'test', None, None, None) == '' @@ -186,7 +189,8 @@ def test_format_signature(): assert formatsig('class', 'C', C, None, None) == '(a, b=None)' assert formatsig('class', 'C', D, 'a, b', 'X') == '(a, b) -> X' - #__init__ have signature at first line of docstring + # __init__ have signature at first line of docstring + directive.env.config.autoclass_content = 'both' class F2: '''some docstring for F2.''' def __init__(self, *args, **kw): @@ -197,9 +201,11 @@ def test_format_signature(): ''' class G2(F2, object): pass - for C in (F2, G2): - assert formatsig('class', 'C', C, None, None) == \ - '(a1, a2, kw1=True, kw2=False)' + + assert formatsig('class', 'F2', F2, None, None) == \ + '(a1, a2, kw1=True, kw2=False)' + assert formatsig('class', 'G2', G2, None, None) == \ + '(a1, a2, kw1=True, kw2=False)' # test for methods class H: @@ -215,6 +221,7 @@ def test_format_signature(): assert formatsig('method', 'H.foo', H.foo3, None, None) == r"(d='\\n')" # test exception handling (exception is caught and args is '') + directive.env.config.autodoc_docstring_signature = False assert formatsig('function', 'int', int, None, None) == '' del _warnings[:] @@ -242,9 +249,14 @@ def test_get_doc(): def getdocl(objtype, obj, encoding=None): inst = AutoDirective._registry[objtype](directive, 'tmp') inst.object = obj + inst.objpath = [obj.__name__] + inst.doc_as_attr = False + inst.format_signature() # handle docstring signatures! ds = inst.get_doc(encoding) # for testing purposes, concat them and strip the empty line at the end - return sum(ds, [])[:-1] + res = sum(ds, [])[:-1] + print res + return res # objects without docstring def f(): @@ -305,7 +317,7 @@ def test_get_doc(): assert getdocl('class', D) == ['Class docstring', '', 'Init docstring', '', 'Other', ' lines'] - #__init__ have signature at first line of docstring + # __init__ have signature at first line of docstring class E: """Class docstring""" def __init__(self, *args, **kw): @@ -330,7 +342,7 @@ def test_get_doc(): # signature line in the docstring will be removed when # autodoc_docstring_signature == True - directive.env.config.autodoc_docstring_signature = True #default + directive.env.config.autodoc_docstring_signature = True # default directive.env.config.autoclass_content = 'class' assert getdocl('class', E) == ['Class docstring'] directive.env.config.autoclass_content = 'init'