diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 562c66a37e3294..fe0e495f3244c2 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -851,8 +851,8 @@ def expect_failure(self, block, err, *, filename=None, lineno=None): def checkDocstring(self, fn, expected): self.assertTrue(hasattr(fn, "docstring")) - self.assertEqual(fn.docstring.strip(), - dedent(expected).strip()) + self.assertEqual(dedent(expected).strip(), + fn.docstring.strip()) def test_trivial(self): parser = DSLParser(FakeClinic()) @@ -981,8 +981,12 @@ def test_function_docstring(self): path: str Path to be examined + Ensure that multiple lines are indented correctly. Perform a stat system call on the given path. + + Ensure that multiple lines are indented correctly. + Ensure that multiple lines are indented correctly. """) self.checkDocstring(function, """ stat($module, /, path) @@ -992,6 +996,10 @@ def test_function_docstring(self): path Path to be examined + Ensure that multiple lines are indented correctly. + + Ensure that multiple lines are indented correctly. + Ensure that multiple lines are indented correctly. """) def test_docstring_trailing_whitespace(self): diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 7525c1ca524003..d78f6db2f1c8fe 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -2640,6 +2640,15 @@ def get_displayname(self, i: int) -> str: else: return f'"argument {i}"' + def render_docstring(self) -> str: + if not self.docstring: + return "" + add, out = text_accumulator() + add(f" {self.name}\n") + for line in self.docstring.split("\n"): + add(f" {line}\n") + return out().rstrip() + CConverterClassT = TypeVar("CConverterClassT", bound=type["CConverter"]) @@ -5308,23 +5317,13 @@ def state_function_docstring(self, line: str) -> None: self.docstring_append(self.function, line) - def format_docstring(self) -> str: - f = self.function - assert f is not None - - new_or_init = f.kind.new_or_init - if new_or_init and not f.docstring: - # don't render a docstring at all, no signature, nothing. - return f.docstring - + @staticmethod + def format_docstring_signature( + f: Function, + parameters: list[Parameter] + ) -> str: text, add, output = _text_accumulator() - parameters = f.render_parameters - - ## - ## docstring first line - ## - - if new_or_init: + if f.kind.new_or_init: # classes get *just* the name of the class # not __new__, not __init__, and not module.classname assert f.cls @@ -5484,35 +5483,27 @@ def add_parameter(text: str) -> None: if not f.docstring_only: add("\n" + sig_end_marker + "\n") - docstring_first_line = output() + signature_line = output() # now fix up the places where the brackets look wrong - docstring_first_line = docstring_first_line.replace(', ]', ',] ') + return signature_line.replace(', ]', ',] ') - # okay. now we're officially building the "parameters" section. - # create substitution text for {parameters} - spacer_line = False - for p in parameters: - if not p.docstring.strip(): - continue - if spacer_line: - add('\n') - else: - spacer_line = True - add(" ") - add(p.name) + @staticmethod + def format_docstring_parameters(params: list[Parameter]) -> str: + """Create substitution text for {parameters}""" + add, output = text_accumulator() + docstrings = [p.render_docstring() for p in params if p.docstring] + for docstring in docstrings: + add(docstring) add('\n') - add(textwrap.indent(rstrip_lines(p.docstring.rstrip()), " ")) - parameters_output = output() - if parameters_output: - parameters_output += '\n' + return output() - ## - ## docstring body - ## - - docstring = f.docstring.rstrip() - lines = [line.rstrip() for line in docstring.split('\n')] + def format_docstring(self) -> str: + assert self.function is not None + f = self.function + if f.kind.new_or_init and not f.docstring: + # don't render a docstring at all, no signature, nothing. + return f.docstring # Enforce the summary line! # The first line of a docstring should be a summary of the function. @@ -5526,6 +5517,7 @@ def add_parameter(text: str) -> None: # Guido said Clinic should enforce this: # http://mail.python.org/pipermail/python-dev/2013-June/127110.html + lines = f.docstring.split('\n') if len(lines) >= 2: if lines[1]: fail("Docstring for " + f.full_name + " does not have a summary line!\n" + @@ -5537,26 +5529,23 @@ def add_parameter(text: str) -> None: # between it and the {parameters} we're about to add. lines.append('') - parameters_marker_count = len(docstring.split('{parameters}')) - 1 + parameters_marker_count = len(f.docstring.split('{parameters}')) - 1 if parameters_marker_count > 1: fail('You may not specify {parameters} more than once in a docstring!') + # insert signature at front and params after the summary line if not parameters_marker_count: - # insert after summary line lines.insert(2, '{parameters}') + lines.insert(0, '{signature}') - # insert at front of docstring - lines.insert(0, docstring_first_line) - + # finalize docstring + params = f.render_parameters + parameters = self.format_docstring_parameters(params) + signature = self.format_docstring_signature(f, params) docstring = "\n".join(lines) - - add(docstring) - docstring = output() - - docstring = linear_format(docstring, parameters=parameters_output) - docstring = docstring.rstrip() - - return docstring + return linear_format(docstring, + signature=signature, + parameters=parameters).rstrip() def do_post_block_processing_cleanup(self) -> None: """