]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-113317: Argument Clinic: move linear_format into libclinic (#115518)
authorErlend E. Aasland <erlend@python.org>
Thu, 15 Feb 2024 22:52:20 +0000 (23:52 +0100)
committerGitHub <noreply@github.com>
Thu, 15 Feb 2024 22:52:20 +0000 (23:52 +0100)
Lib/test/test_clinic.py
Tools/clinic/clinic.py
Tools/clinic/libclinic/__init__.py
Tools/clinic/libclinic/formatting.py

index be35e80fb02c725fe5abb122cbd566d38a5ae71d..f5e9b11ad1cc8ac01d2a8ac9aa435b507843c7f3 100644 (file)
@@ -711,7 +711,7 @@ class ClinicGroupPermuterTest(TestCase):
 
 class ClinicLinearFormatTest(TestCase):
     def _test(self, input, output, **kwargs):
-        computed = clinic.linear_format(input, **kwargs)
+        computed = libclinic.linear_format(input, **kwargs)
         self.assertEqual(output, computed)
 
     def test_empty_strings(self):
@@ -761,6 +761,19 @@ class ClinicLinearFormatTest(TestCase):
           def
         """, name='bingle\nbungle\n')
 
+    def test_text_before_block_marker(self):
+        regex = re.escape("found before '{marker}'")
+        with self.assertRaisesRegex(clinic.ClinicError, regex):
+            libclinic.linear_format("no text before marker for you! {marker}",
+                                    marker="not allowed!")
+
+    def test_text_after_block_marker(self):
+        regex = re.escape("found after '{marker}'")
+        with self.assertRaisesRegex(clinic.ClinicError, regex):
+            libclinic.linear_format("{marker} no text after marker for you!",
+                                    marker="not allowed!")
+
+
 class InertParser:
     def __init__(self, clinic):
         pass
index 7e657351b3f629b9296527ae5e7ede28682df140..4925f27b2937b195bd07aef680e6911da0e0189b 100755 (executable)
@@ -163,50 +163,6 @@ def ensure_legal_c_identifier(s: str) -> str:
     return s
 
 
-def linear_format(s: str, **kwargs: str) -> str:
-    """
-    Perform str.format-like substitution, except:
-      * The strings substituted must be on lines by
-        themselves.  (This line is the "source line".)
-      * If the substitution text is empty, the source line
-        is removed in the output.
-      * If the field is not recognized, the original line
-        is passed unmodified through to the output.
-      * If the substitution text is not empty:
-          * Each line of the substituted text is indented
-            by the indent of the source line.
-          * A newline will be added to the end.
-    """
-    lines = []
-    for line in s.split('\n'):
-        indent, curly, trailing = line.partition('{')
-        if not curly:
-            lines.extend([line, "\n"])
-            continue
-
-        name, curly, trailing = trailing.partition('}')
-        if not curly or name not in kwargs:
-            lines.extend([line, "\n"])
-            continue
-
-        if trailing:
-            fail(f"Text found after {{{name}}} block marker! "
-                 "It must be on a line by itself.")
-        if indent.strip():
-            fail(f"Non-whitespace characters found before {{{name}}} block marker! "
-                 "It must be on a line by itself.")
-
-        value = kwargs[name]
-        if not value:
-            continue
-
-        stripped = [line.rstrip() for line in value.split("\n")]
-        value = textwrap.indent("\n".join(stripped), indent)
-        lines.extend([value, "\n"])
-
-    return "".join(lines[:-1])
-
-
 class CRenderData:
     def __init__(self) -> None:
 
@@ -915,7 +871,8 @@ class CLanguage(Language):
             """)
             for field in preamble, *fields, finale:
                 lines.append(field)
-            return linear_format("\n".join(lines), parser_declarations=declarations)
+            return libclinic.linear_format("\n".join(lines),
+                                           parser_declarations=declarations)
 
         fastcall = not new_or_init
         limited_capi = clinic.limited_capi
@@ -1570,7 +1527,7 @@ class CLanguage(Language):
         {group_booleans}
         break;
 """
-            s = linear_format(s, group_booleans=lines)
+            s = libclinic.linear_format(s, group_booleans=lines)
             s = s.format_map(d)
             out.append(s)
 
@@ -1729,9 +1686,9 @@ class CLanguage(Language):
         for name, destination in clinic.destination_buffers.items():
             template = templates[name]
             if has_option_groups:
-                template = linear_format(template,
+                template = libclinic.linear_format(template,
                         option_group_parsing=template_dict['option_group_parsing'])
-            template = linear_format(template,
+            template = libclinic.linear_format(template,
                 declarations=template_dict['declarations'],
                 return_conversion=template_dict['return_conversion'],
                 initializers=template_dict['initializers'],
@@ -1744,10 +1701,8 @@ class CLanguage(Language):
 
             # Only generate the "exit:" label
             # if we have any gotos
-            need_exit_label = "goto exit;" in template
-            template = linear_format(template,
-                exit_label="exit:" if need_exit_label else ''
-                )
+            label = "exit:" if "goto exit;" in template else ""
+            template = libclinic.linear_format(template, exit_label=label)
 
             s = template.format_map(template_dict)
 
@@ -6125,9 +6080,9 @@ class DSLParser:
         parameters = self.format_docstring_parameters(params)
         signature = self.format_docstring_signature(f, params)
         docstring = "\n".join(lines)
-        return linear_format(docstring,
-                             signature=signature,
-                             parameters=parameters).rstrip()
+        return libclinic.linear_format(docstring,
+                                       signature=signature,
+                                       parameters=parameters).rstrip()
 
     def check_remaining_star(self, lineno: int | None = None) -> None:
         assert isinstance(self.function, Function)
index 1b300b55acc21e6a98965b356d2a9bac7d99aa6b..6237809764d9e1f67ee9cbb25e231dbe6ec00a52 100644 (file)
@@ -9,6 +9,7 @@ from .formatting import (
     docstring_for_c_string,
     format_escape,
     indent_all_lines,
+    linear_format,
     normalize_snippet,
     pprint_words,
     suffix_all_lines,
@@ -33,6 +34,7 @@ __all__ = [
     "docstring_for_c_string",
     "format_escape",
     "indent_all_lines",
+    "linear_format",
     "normalize_snippet",
     "pprint_words",
     "suffix_all_lines",
index 8b3ad7ba566bc846c556b931dfbec09f4ac13544..873ece6210017a877972bb72274275ec509f056e 100644 (file)
@@ -4,6 +4,8 @@ import functools
 import textwrap
 from typing import Final
 
+from libclinic import ClinicError
+
 
 SIG_END_MARKER: Final = "--"
 
@@ -171,3 +173,51 @@ def wrap_declarations(text: str, length: int = 78) -> str:
             lines.append(line.rstrip())
             prefix = spaces
     return "\n".join(lines)
+
+
+def linear_format(text: str, **kwargs: str) -> str:
+    """
+    Perform str.format-like substitution, except:
+      * The strings substituted must be on lines by
+        themselves.  (This line is the "source line".)
+      * If the substitution text is empty, the source line
+        is removed in the output.
+      * If the field is not recognized, the original line
+        is passed unmodified through to the output.
+      * If the substitution text is not empty:
+          * Each line of the substituted text is indented
+            by the indent of the source line.
+          * A newline will be added to the end.
+    """
+    lines = []
+    for line in text.split("\n"):
+        indent, curly, trailing = line.partition("{")
+        if not curly:
+            lines.extend([line, "\n"])
+            continue
+
+        name, curly, trailing = trailing.partition("}")
+        if not curly or name not in kwargs:
+            lines.extend([line, "\n"])
+            continue
+
+        if trailing:
+            raise ClinicError(
+                f"Text found after '{{{name}}}' block marker! "
+                "It must be on a line by itself."
+            )
+        if indent.strip():
+            raise ClinicError(
+                f"Non-whitespace characters found before '{{{name}}}' block marker! "
+                "It must be on a line by itself."
+            )
+
+        value = kwargs[name]
+        if not value:
+            continue
+
+        stripped = [line.rstrip() for line in value.split("\n")]
+        value = textwrap.indent("\n".join(stripped), indent)
+        lines.extend([value, "\n"])
+
+    return "".join(lines[:-1])