diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 00aacccde20..35753bc7fdd 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -301,6 +301,7 @@ def get_items(self, names: list[str]) -> list[tuple[str, str | None, str, str]]: ) raise ValueError(msg) + document_settings = self.state.document.settings env = self.env config = env.config current_document = env.current_document @@ -395,7 +396,7 @@ def get_items(self, names: list[str]) -> list[tuple[str, str | None, str, str]]: props=props, options=opts, ) - summary = extract_summary(list(docstring_lines), self.state.document) + summary = extract_summary(list(docstring_lines), document_settings) items.append((display_name, sig, summary, real_name)) @@ -537,43 +538,39 @@ def mangle_signature(sig: str, max_chars: int = 30) -> str: return '(%s)' % sig -def extract_summary(doc: list[str], document: Any) -> str: +def extract_summary(doc: Sequence[str], settings: Any) -> str: """Extract summary from docstring.""" + # Find the first stanza (heading, sentence, paragraph, etc.). + # If there's a blank line, then we can assume that the stanza has ended, + # so anything after shouldn't be part of the summary. + first_stanza = [] + content_started = False + for line in doc: + is_blank_line = not line or line.isspace() + if not content_started: + # Skip any blank lines at the start + if is_blank_line: + continue + content_started = True + if content_started: + if is_blank_line: + break + first_stanza.append(line) - def parse(doc: list[str], settings: Any) -> nodes.document: - state_machine = RSTStateMachine(state_classes, 'Body') - node = new_document('', settings) - node.reporter = NullReporter() - state_machine.run(doc, node) - - return node - - # Skip a blank lines at the top - while doc and not doc[0].strip(): - doc.pop(0) - - # If there's a blank line, then we can assume the first sentence / - # paragraph has ended, so anything after shouldn't be part of the - # summary - for i, piece in enumerate(doc): - if not piece.strip(): - doc = doc[:i] - break - - if doc == []: + if not first_stanza: return '' # parse the docstring - node = parse(doc, document.settings) + node = _parse_summary(first_stanza, settings) if isinstance(node[0], nodes.section): # document starts with a section heading, so use that. summary = node[0].astext().strip() elif not isinstance(node[0], nodes.paragraph): # document starts with non-paragraph: pick up the first line - summary = doc[0].strip() + summary = first_stanza[0].strip() else: # Try to find the "first sentence", which may span multiple lines - sentences = periods_re.split(' '.join(doc)) + sentences = periods_re.split(' '.join(first_stanza)) if len(sentences) == 1: summary = sentences[0].strip() else: @@ -581,7 +578,7 @@ def parse(doc: list[str], settings: Any) -> nodes.document: for i in range(len(sentences)): summary = '. '.join(sentences[: i + 1]).rstrip('.') + '.' node[:] = [] - node = parse(doc, document.settings) + node = _parse_summary(first_stanza, settings) if summary.endswith(WELL_KNOWN_ABBREVIATIONS): pass elif not any(node.findall(nodes.system_message)): @@ -594,6 +591,15 @@ def parse(doc: list[str], settings: Any) -> nodes.document: return summary +def _parse_summary(doc: Sequence[str], settings: Any) -> nodes.document: + state_machine = RSTStateMachine(state_classes, 'Body') + node = new_document('', settings) + node.reporter = NullReporter() + state_machine.run(doc, node) + + return node + + def limited_join( sep: str, items: list[str], max_chars: int = 30, overflow_marker: str = '...' ) -> str: diff --git a/tests/test_ext_autosummary/test_ext_autosummary.py b/tests/test_ext_autosummary/test_ext_autosummary.py index e4b150b34ca..9749cd8f050 100644 --- a/tests/test_ext_autosummary/test_ext_autosummary.py +++ b/tests/test_ext_autosummary/test_ext_autosummary.py @@ -26,7 +26,6 @@ ) from sphinx.ext.autosummary.generate import main as autogen_main from sphinx.testing.util import assert_node, etree_parse -from sphinx.util.docutils import new_document if TYPE_CHECKING: from xml.etree.ElementTree import Element @@ -86,7 +85,6 @@ def test_extract_summary(capsys): pep_reference=False, rfc_reference=False, ) - document = new_document('', settings) # normal case doc = [ @@ -95,52 +93,52 @@ def test_extract_summary(capsys): '', 'Second block is here', ] - assert extract_summary(doc, document) == 'This is a first sentence.' + assert extract_summary(doc, settings) == 'This is a first sentence.' # inliner case doc = [ 'This sentence contains *emphasis text having dots.*,', 'it does not break sentence.', ] - assert extract_summary(doc, document) == ' '.join(doc) + assert extract_summary(doc, settings) == ' '.join(doc) # abbreviations doc = ['Blabla, i.e. bla.'] - assert extract_summary(doc, document) == ' '.join(doc) + assert extract_summary(doc, settings) == ' '.join(doc) doc = ['Blabla, (i.e. bla).'] - assert extract_summary(doc, document) == ' '.join(doc) + assert extract_summary(doc, settings) == ' '.join(doc) doc = ['Blabla, e.g. bla.'] - assert extract_summary(doc, document) == ' '.join(doc) + assert extract_summary(doc, settings) == ' '.join(doc) doc = ['Blabla, (e.g. bla).'] - assert extract_summary(doc, document) == ' '.join(doc) + assert extract_summary(doc, settings) == ' '.join(doc) doc = ['Blabla, et al. bla.'] - assert extract_summary(doc, document) == ' '.join(doc) + assert extract_summary(doc, settings) == ' '.join(doc) # literal doc = ['blah blah::'] - assert extract_summary(doc, document) == 'blah blah.' + assert extract_summary(doc, settings) == 'blah blah.' # heading doc = [ 'blah blah', '=========', ] - assert extract_summary(doc, document) == 'blah blah' + assert extract_summary(doc, settings) == 'blah blah' doc = [ '=========', 'blah blah', '=========', ] - assert extract_summary(doc, document) == 'blah blah' + assert extract_summary(doc, settings) == 'blah blah' # hyperlink target doc = ['Do `this `_ and that. blah blah blah.'] - extracted = extract_summary(doc, document) + extracted = extract_summary(doc, settings) assert extracted == 'Do `this `_ and that.' _, err = capsys.readouterr()