From fd632eb911878a9c535ea847990338355eb375ca Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 4 Aug 2023 13:01:34 +0200 Subject: [PATCH 1/6] gh-104683: Argument Clinic: refactor format_docstring() Extract helper methods for formatting the signature and parameter sections, and clean up the remaining function body. --- Tools/clinic/clinic.py | 84 +++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 46 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 7525c1ca524003..a8be0f03039e1d 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -5308,23 +5308,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 +5474,39 @@ 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} + @staticmethod + def format_docstring_parameters(params: list[Parameter]) -> str: + """Create substitution text for {parameters}""" + text, add, output = _text_accumulator() spacer_line = False - for p in parameters: - if not p.docstring.strip(): + for param in params: + docstring = param.docstring.strip() + if not docstring: continue if spacer_line: add('\n') else: spacer_line = True add(" ") - add(p.name) + add(param.name) add('\n') - add(textwrap.indent(rstrip_lines(p.docstring.rstrip()), " ")) - parameters_output = output() - if parameters_output: - parameters_output += '\n' - - ## - ## docstring body - ## + stripped = rstrip_lines(docstring.rstrip()) + add(textwrap.indent(stripped, " ")) + if text: + add('\n') + return output() - 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 +5520,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 = [line for line in 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 +5532,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: """ From 1d244dc64220ff1f69677827149197a918d5bc5b Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 4 Aug 2023 14:00:34 +0200 Subject: [PATCH 2/6] Use splitlines() iso. comprehension Co-authored-by: Alex Waygood --- Tools/clinic/clinic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index a8be0f03039e1d..2c5e7ae9db73d3 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -5520,7 +5520,7 @@ def format_docstring(self) -> str: # Guido said Clinic should enforce this: # http://mail.python.org/pipermail/python-dev/2013-June/127110.html - lines = [line for line in f.docstring.split('\n')] + lines = f.docstring.splitlines() if len(lines) >= 2: if lines[1]: fail("Docstring for " + f.full_name + " does not have a summary line!\n" + From 9d3ed09e64b23885c5f774610384929fa15bf8a8 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 4 Aug 2023 14:25:46 +0200 Subject: [PATCH 3/6] Revert "Use splitlines() iso. comprehension" This reverts commit 1d244dc64220ff1f69677827149197a918d5bc5b. --- Tools/clinic/clinic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 2c5e7ae9db73d3..a8be0f03039e1d 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -5520,7 +5520,7 @@ def format_docstring(self) -> str: # Guido said Clinic should enforce this: # http://mail.python.org/pipermail/python-dev/2013-June/127110.html - lines = f.docstring.splitlines() + lines = [line for line in f.docstring.split('\n')] if len(lines) >= 2: if lines[1]: fail("Docstring for " + f.full_name + " does not have a summary line!\n" + From 0902130a75a02d0deffd4643486d4304fd1e96e0 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 4 Aug 2023 14:28:33 +0200 Subject: [PATCH 4/6] Split, not splitlines Co-authored-by: Alex Waygood --- Tools/clinic/clinic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index a8be0f03039e1d..c95b0f84e99963 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -5520,7 +5520,7 @@ def format_docstring(self) -> str: # Guido said Clinic should enforce this: # http://mail.python.org/pipermail/python-dev/2013-June/127110.html - lines = [line for line in f.docstring.split('\n')] + 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" + From fa29529c311696973b517ecd5f2c43cf613dd4e5 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 4 Aug 2023 15:12:23 +0200 Subject: [PATCH 5/6] gh-104683: Argument Clinic: refactor parameter docstring rendering Previously, parameter docstring rendering was done in the DSLParser class. Instead, let the Parameter class render themselves. --- Lib/test/test_clinic.py | 12 ++++++++++-- Tools/clinic/clinic.py | 27 ++++++++++++--------------- 2 files changed, 22 insertions(+), 17 deletions(-) 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 c95b0f84e99963..e17a17c37d4a9b 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"]) @@ -5483,21 +5492,9 @@ def add_parameter(text: str) -> None: def format_docstring_parameters(params: list[Parameter]) -> str: """Create substitution text for {parameters}""" text, add, output = _text_accumulator() - spacer_line = False - for param in params: - docstring = param.docstring.strip() - if not docstring: - continue - if spacer_line: - add('\n') - else: - spacer_line = True - add(" ") - add(param.name) - add('\n') - stripped = rstrip_lines(docstring.rstrip()) - add(textwrap.indent(stripped, " ")) - if text: + docstrings = [p.render_docstring() for p in params if p.docstring] + for docstring in docstrings: + add(docstring) add('\n') return output() From f6ef520a29ac4214dca15f3e7b51043d4638b5cd Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 4 Aug 2023 15:26:37 +0200 Subject: [PATCH 6/6] text_accumulator() iso. _text_accumulator() --- Tools/clinic/clinic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index e17a17c37d4a9b..d78f6db2f1c8fe 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -5491,7 +5491,7 @@ def add_parameter(text: str) -> None: @staticmethod def format_docstring_parameters(params: list[Parameter]) -> str: """Create substitution text for {parameters}""" - text, add, output = _text_accumulator() + add, output = text_accumulator() docstrings = [p.render_docstring() for p in params if p.docstring] for docstring in docstrings: add(docstring)