]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-113317: Move more formatting helpers into libclinic (#113438)
authorErlend E. Aasland <erlend@python.org>
Sat, 23 Dec 2023 17:08:10 +0000 (18:08 +0100)
committerGitHub <noreply@github.com>
Sat, 23 Dec 2023 17:08:10 +0000 (17:08 +0000)
Move the following global helpers into libclinic:
- format_escape()
- normalize_snippet()
- wrap_declarations()

Also move strip_leading_and_trailing_blank_lines() and make it internal to libclinic.

Lib/test/test_clinic.py
Tools/clinic/clinic.py
Tools/clinic/libclinic/__init__.py
Tools/clinic/libclinic/formatting.py

index cfb84bcaa3f3b7e7895abad48381ae4d66c12507..21f56fe0195e690ababde1aeeb4dc266c876c857 100644 (file)
@@ -3730,7 +3730,7 @@ class FormatHelperTests(unittest.TestCase):
         )
         for lines, expected in dataset:
             with self.subTest(lines=lines, expected=expected):
-                out = clinic.strip_leading_and_trailing_blank_lines(lines)
+                out = libclinic.normalize_snippet(lines)
                 self.assertEqual(out, expected)
 
     def test_normalize_snippet(self):
@@ -3759,7 +3759,7 @@ class FormatHelperTests(unittest.TestCase):
         expected_outputs = {0: zero_indent, 4: four_indent, 8: eight_indent}
         for indent, expected in expected_outputs.items():
             with self.subTest(indent=indent):
-                actual = clinic.normalize_snippet(snippet, indent=indent)
+                actual = libclinic.normalize_snippet(snippet, indent=indent)
                 self.assertEqual(actual, expected)
 
     def test_escaped_docstring(self):
@@ -3780,7 +3780,7 @@ class FormatHelperTests(unittest.TestCase):
     def test_format_escape(self):
         line = "{}, {a}"
         expected = "{{}}, {{a}}"
-        out = clinic.format_escape(line)
+        out = libclinic.format_escape(line)
         self.assertEqual(out, expected)
 
     def test_indent_all_lines(self):
index 532c45f4b39c4e1671bd8fa612c4bf22bd96af16..f004bec3cce8f64baf4920c9d8c129fe00f4fa58 100755 (executable)
@@ -189,12 +189,6 @@ def ensure_legal_c_identifier(s: str) -> str:
         return s + "_value"
     return s
 
-def format_escape(s: str) -> str:
-    # double up curly-braces, this string will be used
-    # as part of a format_map() template later
-    s = s.replace('{', '{{')
-    s = s.replace('}', '}}')
-    return s
 
 def linear_format(s: str, **kwargs: str) -> str:
     """
@@ -475,34 +469,6 @@ def permute_optional_groups(
     return tuple(accumulator)
 
 
-def strip_leading_and_trailing_blank_lines(s: str) -> str:
-    lines = s.rstrip().split('\n')
-    while lines:
-        line = lines[0]
-        if line.strip():
-            break
-        del lines[0]
-    return '\n'.join(lines)
-
-@functools.lru_cache()
-def normalize_snippet(
-        s: str,
-        *,
-        indent: int = 0
-) -> str:
-    """
-    Reformats s:
-        * removes leading and trailing blank lines
-        * ensures that it does not end with a newline
-        * dedents so the first nonwhite character on any line is at column "indent"
-    """
-    s = strip_leading_and_trailing_blank_lines(s)
-    s = textwrap.dedent(s)
-    if indent:
-        s = textwrap.indent(s, ' ' * indent)
-    return s
-
-
 def declare_parser(
         f: Function,
         *,
@@ -573,62 +539,7 @@ def declare_parser(
             }};
             #undef KWTUPLE
     """ % (format_ or fname)
-    return normalize_snippet(declarations)
-
-
-def wrap_declarations(
-        text: str,
-        length: int = 78
-) -> str:
-    """
-    A simple-minded text wrapper for C function declarations.
-
-    It views a declaration line as looking like this:
-        xxxxxxxx(xxxxxxxxx,xxxxxxxxx)
-    If called with length=30, it would wrap that line into
-        xxxxxxxx(xxxxxxxxx,
-                 xxxxxxxxx)
-    (If the declaration has zero or one parameters, this
-    function won't wrap it.)
-
-    If this doesn't work properly, it's probably better to
-    start from scratch with a more sophisticated algorithm,
-    rather than try and improve/debug this dumb little function.
-    """
-    lines = []
-    for line in text.split('\n'):
-        prefix, _, after_l_paren = line.partition('(')
-        if not after_l_paren:
-            lines.append(line)
-            continue
-        in_paren, _, after_r_paren = after_l_paren.partition(')')
-        if not _:
-            lines.append(line)
-            continue
-        if ',' not in in_paren:
-            lines.append(line)
-            continue
-        parameters = [x.strip() + ", " for x in in_paren.split(',')]
-        prefix += "("
-        if len(prefix) < length:
-            spaces = " " * len(prefix)
-        else:
-            spaces = " " * 4
-
-        while parameters:
-            line = prefix
-            first = True
-            while parameters:
-                if (not first and
-                    (len(line) + len(parameters[0]) > length)):
-                    break
-                line += parameters.pop(0)
-                first = False
-            if not parameters:
-                line = line.rstrip(", ") + ")" + after_r_paren
-            lines.append(line.rstrip())
-            prefix = spaces
-    return "\n".join(lines)
+    return libclinic.normalize_snippet(declarations)
 
 
 class CLanguage(Language):
@@ -642,67 +553,67 @@ class CLanguage(Language):
 
     NO_VARARG: Final[str] = "PY_SSIZE_T_MAX"
 
-    PARSER_PROTOTYPE_KEYWORD: Final[str] = normalize_snippet("""
+    PARSER_PROTOTYPE_KEYWORD: Final[str] = libclinic.normalize_snippet("""
         static PyObject *
         {c_basename}({self_type}{self_name}, PyObject *args, PyObject *kwargs)
     """)
-    PARSER_PROTOTYPE_KEYWORD___INIT__: Final[str] = normalize_snippet("""
+    PARSER_PROTOTYPE_KEYWORD___INIT__: Final[str] = libclinic.normalize_snippet("""
         static int
         {c_basename}({self_type}{self_name}, PyObject *args, PyObject *kwargs)
     """)
-    PARSER_PROTOTYPE_VARARGS: Final[str] = normalize_snippet("""
+    PARSER_PROTOTYPE_VARARGS: Final[str] = libclinic.normalize_snippet("""
         static PyObject *
         {c_basename}({self_type}{self_name}, PyObject *args)
     """)
-    PARSER_PROTOTYPE_FASTCALL: Final[str] = normalize_snippet("""
+    PARSER_PROTOTYPE_FASTCALL: Final[str] = libclinic.normalize_snippet("""
         static PyObject *
         {c_basename}({self_type}{self_name}, PyObject *const *args, Py_ssize_t nargs)
     """)
-    PARSER_PROTOTYPE_FASTCALL_KEYWORDS: Final[str] = normalize_snippet("""
+    PARSER_PROTOTYPE_FASTCALL_KEYWORDS: Final[str] = libclinic.normalize_snippet("""
         static PyObject *
         {c_basename}({self_type}{self_name}, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
     """)
-    PARSER_PROTOTYPE_DEF_CLASS: Final[str] = normalize_snippet("""
+    PARSER_PROTOTYPE_DEF_CLASS: Final[str] = libclinic.normalize_snippet("""
         static PyObject *
         {c_basename}({self_type}{self_name}, PyTypeObject *{defining_class_name}, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
     """)
-    PARSER_PROTOTYPE_NOARGS: Final[str] = normalize_snippet("""
+    PARSER_PROTOTYPE_NOARGS: Final[str] = libclinic.normalize_snippet("""
         static PyObject *
         {c_basename}({self_type}{self_name}, PyObject *Py_UNUSED(ignored))
     """)
-    PARSER_PROTOTYPE_GETTER: Final[str] = normalize_snippet("""
+    PARSER_PROTOTYPE_GETTER: Final[str] = libclinic.normalize_snippet("""
         static PyObject *
         {c_basename}({self_type}{self_name}, void *Py_UNUSED(context))
     """)
-    PARSER_PROTOTYPE_SETTER: Final[str] = normalize_snippet("""
+    PARSER_PROTOTYPE_SETTER: Final[str] = libclinic.normalize_snippet("""
         static int
         {c_basename}({self_type}{self_name}, PyObject *value, void *Py_UNUSED(context))
     """)
-    METH_O_PROTOTYPE: Final[str] = normalize_snippet("""
+    METH_O_PROTOTYPE: Final[str] = libclinic.normalize_snippet("""
         static PyObject *
         {c_basename}({impl_parameters})
     """)
-    DOCSTRING_PROTOTYPE_VAR: Final[str] = normalize_snippet("""
+    DOCSTRING_PROTOTYPE_VAR: Final[str] = libclinic.normalize_snippet("""
         PyDoc_VAR({c_basename}__doc__);
     """)
-    DOCSTRING_PROTOTYPE_STRVAR: Final[str] = normalize_snippet("""
+    DOCSTRING_PROTOTYPE_STRVAR: Final[str] = libclinic.normalize_snippet("""
         PyDoc_STRVAR({c_basename}__doc__,
         {docstring});
     """)
-    GETSET_DOCSTRING_PROTOTYPE_STRVAR: Final[str] = normalize_snippet("""
+    GETSET_DOCSTRING_PROTOTYPE_STRVAR: Final[str] = libclinic.normalize_snippet("""
         PyDoc_STRVAR({getset_basename}__doc__,
         {docstring});
         #define {getset_basename}_HAS_DOCSTR
     """)
-    IMPL_DEFINITION_PROTOTYPE: Final[str] = normalize_snippet("""
+    IMPL_DEFINITION_PROTOTYPE: Final[str] = libclinic.normalize_snippet("""
         static {impl_return_type}
         {c_basename}_impl({impl_parameters})
     """)
-    METHODDEF_PROTOTYPE_DEFINE: Final[str] = normalize_snippet(r"""
+    METHODDEF_PROTOTYPE_DEFINE: Final[str] = libclinic.normalize_snippet(r"""
         #define {methoddef_name}    \
             {{"{name}", {methoddef_cast}{c_basename}{methoddef_cast_end}, {methoddef_flags}, {c_basename}__doc__}},
     """)
-    GETTERDEF_PROTOTYPE_DEFINE: Final[str] = normalize_snippet(r"""
+    GETTERDEF_PROTOTYPE_DEFINE: Final[str] = libclinic.normalize_snippet(r"""
         #if defined({getset_basename}_HAS_DOCSTR)
         #  define {getset_basename}_DOCSTR {getset_basename}__doc__
         #else
@@ -715,7 +626,7 @@ class CLanguage(Language):
         #  define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, NULL, {getset_basename}_DOCSTR}},
         #endif
     """)
-    SETTERDEF_PROTOTYPE_DEFINE: Final[str] = normalize_snippet(r"""
+    SETTERDEF_PROTOTYPE_DEFINE: Final[str] = libclinic.normalize_snippet(r"""
         #if defined({getset_name}_HAS_DOCSTR)
         #  define {getset_basename}_DOCSTR {getset_basename}__doc__
         #else
@@ -728,7 +639,7 @@ class CLanguage(Language):
         #  define {getset_name}_GETSETDEF {{"{name}", NULL, (setter){getset_basename}_set, NULL}},
         #endif
     """)
-    METHODDEF_PROTOTYPE_IFNDEF: Final[str] = normalize_snippet("""
+    METHODDEF_PROTOTYPE_IFNDEF: Final[str] = libclinic.normalize_snippet("""
         #ifndef {methoddef_name}
             #define {methoddef_name}
         #endif /* !defined({methoddef_name}) */
@@ -797,7 +708,7 @@ class CLanguage(Language):
             minor=minversion[1],
             message=libclinic.c_repr(message),
         )
-        return normalize_snippet(code)
+        return libclinic.normalize_snippet(code)
 
     def deprecate_positional_use(
             self,
@@ -848,7 +759,7 @@ class CLanguage(Language):
             message=libclinic.wrapped_c_string_literal(message, width=64,
                                                        subsequent_indent=20),
         )
-        return normalize_snippet(code, indent=4)
+        return libclinic.normalize_snippet(code, indent=4)
 
     def deprecate_keyword_use(
             self,
@@ -931,7 +842,7 @@ class CLanguage(Language):
             message=libclinic.wrapped_c_string_literal(message, width=64,
                                                        subsequent_indent=20),
         )
-        return normalize_snippet(code, indent=4)
+        return libclinic.normalize_snippet(code, indent=4)
 
     def output_templates(
             self,
@@ -1036,14 +947,14 @@ class CLanguage(Language):
             lines.append(prototype)
             parser_body_fields = fields
 
-            preamble = normalize_snippet("""
+            preamble = libclinic.normalize_snippet("""
                 {{
                     {return_value_declaration}
                     {parser_declarations}
                     {declarations}
                     {initializers}
             """) + "\n"
-            finale = normalize_snippet("""
+            finale = libclinic.normalize_snippet("""
                     {modifications}
                     {lock}
                     {return_value} = {c_basename}_impl({impl_arguments});
@@ -1095,7 +1006,7 @@ class CLanguage(Language):
                 parser_prototype = self.PARSER_PROTOTYPE_DEF_CLASS
                 return_error = ('return NULL;' if simple_return
                                 else 'goto exit;')
-                parser_code = [normalize_snippet("""
+                parser_code = [libclinic.normalize_snippet("""
                     if (nargs) {{
                         PyErr_SetString(PyExc_TypeError, "{name}() takes no arguments");
                         %s
@@ -1135,7 +1046,7 @@ class CLanguage(Language):
                 argname = 'arg'
                 if parameters[0].name == argname:
                     argname += '_'
-                parser_prototype = normalize_snippet("""
+                parser_prototype = libclinic.normalize_snippet("""
                     static PyObject *
                     {c_basename}({self_type}{self_name}, PyObject *%s)
                     """ % argname)
@@ -1149,7 +1060,7 @@ class CLanguage(Language):
                         }}
                         """ % argname
                 parser_definition = parser_body(parser_prototype,
-                                                normalize_snippet(parsearg, indent=4))
+                                                libclinic.normalize_snippet(parsearg, indent=4))
 
         elif has_option_groups:
             # positional parameters with option groups
@@ -1187,11 +1098,12 @@ class CLanguage(Language):
             if limited_capi:
                 parser_code = []
                 if nargs != 'nargs':
-                    parser_code.append(normalize_snippet(f'Py_ssize_t nargs = {nargs};', indent=4))
+                    nargs_def = f'Py_ssize_t nargs = {nargs};'
+                    parser_code.append(libclinic.normalize_snippet(nargs_def, indent=4))
                     nargs = 'nargs'
                 if min_pos == max_args:
                     pl = '' if min_pos == 1 else 's'
-                    parser_code.append(normalize_snippet(f"""
+                    parser_code.append(libclinic.normalize_snippet(f"""
                         if ({nargs} != {min_pos}) {{{{
                             PyErr_Format(PyExc_TypeError, "{{name}} expected {min_pos} argument{pl}, got %zd", {nargs});
                             goto exit;
@@ -1201,7 +1113,7 @@ class CLanguage(Language):
                 else:
                     if min_pos:
                         pl = '' if min_pos == 1 else 's'
-                        parser_code.append(normalize_snippet(f"""
+                        parser_code.append(libclinic.normalize_snippet(f"""
                             if ({nargs} < {min_pos}) {{{{
                                 PyErr_Format(PyExc_TypeError, "{{name}} expected at least {min_pos} argument{pl}, got %zd", {nargs});
                                 goto exit;
@@ -1210,7 +1122,7 @@ class CLanguage(Language):
                             indent=4))
                     if max_args != self.NO_VARARG:
                         pl = '' if max_args == 1 else 's'
-                        parser_code.append(normalize_snippet(f"""
+                        parser_code.append(libclinic.normalize_snippet(f"""
                             if ({nargs} > {max_args}) {{{{
                                 PyErr_Format(PyExc_TypeError, "{{name}} expected at most {max_args} argument{pl}, got %zd", {nargs});
                                 goto exit;
@@ -1220,7 +1132,7 @@ class CLanguage(Language):
             else:
                 clinic.add_include('pycore_modsupport.h',
                                    '_PyArg_CheckPositional()')
-                parser_code = [normalize_snippet(f"""
+                parser_code = [libclinic.normalize_snippet(f"""
                     if (!_PyArg_CheckPositional("{{name}}", {nargs}, {min_pos}, {max_args})) {{{{
                         goto exit;
                     }}}}
@@ -1230,7 +1142,7 @@ class CLanguage(Language):
             for i, p in enumerate(parameters):
                 if p.is_vararg():
                     if fastcall:
-                        parser_code.append(normalize_snippet("""
+                        parser_code.append(libclinic.normalize_snippet("""
                             %s = PyTuple_New(%s);
                             if (!%s) {{
                                 goto exit;
@@ -1247,7 +1159,7 @@ class CLanguage(Language):
                                 max_pos
                             ), indent=4))
                     else:
-                        parser_code.append(normalize_snippet("""
+                        parser_code.append(libclinic.normalize_snippet("""
                             %s = PyTuple_GetSlice(%d, -1);
                             """ % (
                                 p.converter.parser_name,
@@ -1263,12 +1175,12 @@ class CLanguage(Language):
                     break
                 if has_optional or p.is_optional():
                     has_optional = True
-                    parser_code.append(normalize_snippet("""
+                    parser_code.append(libclinic.normalize_snippet("""
                         if (%s < %d) {{
                             goto skip_optional;
                         }}
                         """, indent=4) % (nargs, i + 1))
-                parser_code.append(normalize_snippet(parsearg, indent=4))
+                parser_code.append(libclinic.normalize_snippet(parsearg, indent=4))
 
             if parser_code is not None:
                 if has_optional:
@@ -1279,7 +1191,7 @@ class CLanguage(Language):
                 if fastcall:
                     clinic.add_include('pycore_modsupport.h',
                                        '_PyArg_ParseStack()')
-                    parser_code = [normalize_snippet("""
+                    parser_code = [libclinic.normalize_snippet("""
                         if (!_PyArg_ParseStack(args, nargs, "{format_units}:{name}",
                             {parse_arguments})) {{
                             goto exit;
@@ -1288,7 +1200,7 @@ class CLanguage(Language):
                 else:
                     flags = "METH_VARARGS"
                     parser_prototype = self.PARSER_PROTOTYPE_VARARGS
-                    parser_code = [normalize_snippet("""
+                    parser_code = [libclinic.normalize_snippet("""
                         if (!PyArg_ParseTuple(args, "{format_units}:{name}",
                             {parse_arguments})) {{
                             goto exit;
@@ -1343,7 +1255,7 @@ class CLanguage(Language):
                     declarations += "\nPyObject *argsbuf[%s];" % len(converters)
                     if has_optional_kw:
                         declarations += "\nPy_ssize_t noptargs = %s + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - %d;" % (nargs, min_pos + min_kw_only)
-                    parser_code = [normalize_snippet("""
+                    parser_code = [libclinic.normalize_snippet("""
                         args = %s(args, nargs, NULL, kwnames, &_parser, %s, argsbuf);
                         if (!args) {{
                             goto exit;
@@ -1361,7 +1273,7 @@ class CLanguage(Language):
                     declarations += "\nPy_ssize_t nargs = PyTuple_GET_SIZE(args);"
                     if has_optional_kw:
                         declarations += "\nPy_ssize_t noptargs = %s + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - %d;" % (nargs, min_pos + min_kw_only)
-                    parser_code = [normalize_snippet("""
+                    parser_code = [libclinic.normalize_snippet("""
                         fastargs = %s(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, %s, argsbuf);
                         if (!fastargs) {{
                             goto exit;
@@ -1394,19 +1306,19 @@ class CLanguage(Language):
                         parser_code.append("%s:" % add_label)
                         add_label = None
                     if not p.is_optional():
-                        parser_code.append(normalize_snippet(parsearg, indent=4))
+                        parser_code.append(libclinic.normalize_snippet(parsearg, indent=4))
                     elif i < pos_only:
                         add_label = 'skip_optional_posonly'
-                        parser_code.append(normalize_snippet("""
+                        parser_code.append(libclinic.normalize_snippet("""
                             if (nargs < %d) {{
                                 goto %s;
                             }}
                             """ % (i + 1, add_label), indent=4))
                         if has_optional_kw:
-                            parser_code.append(normalize_snippet("""
+                            parser_code.append(libclinic.normalize_snippet("""
                                 noptargs--;
                                 """, indent=4))
-                        parser_code.append(normalize_snippet(parsearg, indent=4))
+                        parser_code.append(libclinic.normalize_snippet(parsearg, indent=4))
                     else:
                         if i < max_pos:
                             label = 'skip_optional_pos'
@@ -1418,20 +1330,20 @@ class CLanguage(Language):
                                 first_opt += 1
                         if i == first_opt:
                             add_label = label
-                            parser_code.append(normalize_snippet("""
+                            parser_code.append(libclinic.normalize_snippet("""
                                 if (!noptargs) {{
                                     goto %s;
                                 }}
                                 """ % add_label, indent=4))
                         if i + 1 == len(parameters):
-                            parser_code.append(normalize_snippet(parsearg, indent=4))
+                            parser_code.append(libclinic.normalize_snippet(parsearg, indent=4))
                         else:
                             add_label = label
-                            parser_code.append(normalize_snippet("""
+                            parser_code.append(libclinic.normalize_snippet("""
                                 if (%s) {{
                                 """ % (argname_fmt % i), indent=4))
-                            parser_code.append(normalize_snippet(parsearg, indent=8))
-                            parser_code.append(normalize_snippet("""
+                            parser_code.append(libclinic.normalize_snippet(parsearg, indent=8))
+                            parser_code.append(libclinic.normalize_snippet("""
                                     if (!--noptargs) {{
                                         goto %s;
                                     }}
@@ -1450,7 +1362,7 @@ class CLanguage(Language):
                     assert not fastcall
                     flags = "METH_VARARGS|METH_KEYWORDS"
                     parser_prototype = self.PARSER_PROTOTYPE_KEYWORD
-                    parser_code = [normalize_snippet("""
+                    parser_code = [libclinic.normalize_snippet("""
                         if (!PyArg_ParseTupleAndKeywords(args, kwargs, "{format_units}:{name}", _keywords,
                             {parse_arguments}))
                             goto exit;
@@ -1462,7 +1374,7 @@ class CLanguage(Language):
                 elif fastcall:
                     clinic.add_include('pycore_modsupport.h',
                                        '_PyArg_ParseStackAndKeywords()')
-                    parser_code = [normalize_snippet("""
+                    parser_code = [libclinic.normalize_snippet("""
                         if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser{parse_arguments_comma}
                             {parse_arguments})) {{
                             goto exit;
@@ -1471,7 +1383,7 @@ class CLanguage(Language):
                 else:
                     clinic.add_include('pycore_modsupport.h',
                                        '_PyArg_ParseTupleAndKeywordsFast()')
-                    parser_code = [normalize_snippet("""
+                    parser_code = [libclinic.normalize_snippet("""
                         if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser,
                             {parse_arguments})) {{
                             goto exit;
@@ -1518,7 +1430,7 @@ class CLanguage(Language):
                 declarations = '{base_type_ptr}'
                 clinic.add_include('pycore_modsupport.h',
                                    '_PyArg_NoKeywords()')
-                fields.insert(0, normalize_snippet("""
+                fields.insert(0, libclinic.normalize_snippet("""
                     if ({self_type_check}!_PyArg_NoKeywords("{name}", kwargs)) {{
                         goto exit;
                     }}
@@ -1526,7 +1438,7 @@ class CLanguage(Language):
                 if not parses_positional:
                     clinic.add_include('pycore_modsupport.h',
                                        '_PyArg_NoPositional()')
-                    fields.insert(0, normalize_snippet("""
+                    fields.insert(0, libclinic.normalize_snippet("""
                         if ({self_type_check}!_PyArg_NoPositional("{name}", args)) {{
                             goto exit;
                         }}
@@ -1715,7 +1627,7 @@ class CLanguage(Language):
         out.append('        goto exit;\n')
         out.append("}")
 
-        template_dict['option_group_parsing'] = format_escape("".join(out))
+        template_dict['option_group_parsing'] = libclinic.format_escape("".join(out))
 
     def render_function(
             self,
@@ -1825,7 +1737,7 @@ class CLanguage(Language):
         else:
             template_dict['impl_return_type'] = f.return_converter.type
 
-        template_dict['declarations'] = format_escape("\n".join(data.declarations))
+        template_dict['declarations'] = libclinic.format_escape("\n".join(data.declarations))
         template_dict['initializers'] = "\n\n".join(data.initializers)
         template_dict['modifications'] = '\n\n'.join(data.modifications)
         template_dict['keywords_c'] = ' '.join('"' + k + '",'
@@ -1841,9 +1753,11 @@ class CLanguage(Language):
             template_dict['parse_arguments_comma'] = '';
         template_dict['impl_parameters'] = ", ".join(data.impl_parameters)
         template_dict['impl_arguments'] = ", ".join(data.impl_arguments)
-        template_dict['return_conversion'] = format_escape("".join(data.return_conversion).rstrip())
-        template_dict['post_parsing'] = format_escape("".join(data.post_parsing).rstrip())
-        template_dict['cleanup'] = format_escape("".join(data.cleanup))
+
+        template_dict['return_conversion'] = libclinic.format_escape("".join(data.return_conversion).rstrip())
+        template_dict['post_parsing'] = libclinic.format_escape("".join(data.post_parsing).rstrip())
+        template_dict['cleanup'] = libclinic.format_escape("".join(data.cleanup))
+
         template_dict['return_value'] = data.return_value
         template_dict['lock'] = "\n".join(data.lock)
         template_dict['unlock'] = "\n".join(data.unlock)
@@ -1887,7 +1801,7 @@ class CLanguage(Language):
             # mild hack:
             # reflow long impl declarations
             if name in {"impl_prototype", "impl_definition"}:
-                s = wrap_declarations(s)
+                s = libclinic.wrap_declarations(s)
 
             if clinic.line_prefix:
                 s = libclinic.indent_all_lines(s, clinic.line_prefix)
index 32ab2259ce42266dedce8fbdec573885256e42ef..0c3c6840901a42cfaca063306a8dd8ef63152a7b 100644 (file)
@@ -1,25 +1,31 @@
 from typing import Final
 
 from .formatting import (
+    SIG_END_MARKER,
     c_repr,
     docstring_for_c_string,
+    format_escape,
     indent_all_lines,
+    normalize_snippet,
     pprint_words,
     suffix_all_lines,
+    wrap_declarations,
     wrapped_c_string_literal,
-    SIG_END_MARKER,
 )
 
 
 __all__ = [
     # Formatting helpers
+    "SIG_END_MARKER",
     "c_repr",
     "docstring_for_c_string",
+    "format_escape",
     "indent_all_lines",
+    "normalize_snippet",
     "pprint_words",
     "suffix_all_lines",
+    "wrap_declarations",
     "wrapped_c_string_literal",
-    "SIG_END_MARKER",
 ]
 
 
index 691a8fc47ef0374fcd4f8e772def7a4bc34e884d..8b3ad7ba566bc846c556b931dfbec09f4ac13544 100644 (file)
@@ -1,5 +1,6 @@
 """A collection of string formatting helpers."""
 
+import functools
 import textwrap
 from typing import Final
 
@@ -59,11 +60,7 @@ def wrapped_c_string_literal(
     return initial_indent * " " + c_repr(separator.join(wrapped))
 
 
-def _add_prefix_and_suffix(
-    text: str,
-    prefix: str = "",
-    suffix: str = ""
-) -> str:
+def _add_prefix_and_suffix(text: str, *, prefix: str = "", suffix: str = "") -> str:
     """Return 'text' with 'prefix' prepended and 'suffix' appended to all lines.
 
     If the last line is empty, it remains unchanged.
@@ -90,3 +87,87 @@ def pprint_words(items: list[str]) -> str:
     if len(items) <= 2:
         return " and ".join(items)
     return ", ".join(items[:-1]) + " and " + items[-1]
+
+
+def _strip_leading_and_trailing_blank_lines(text: str) -> str:
+    lines = text.rstrip().split("\n")
+    while lines:
+        line = lines[0]
+        if line.strip():
+            break
+        del lines[0]
+    return "\n".join(lines)
+
+
+@functools.lru_cache()
+def normalize_snippet(text: str, *, indent: int = 0) -> str:
+    """
+    Reformats 'text':
+        * removes leading and trailing blank lines
+        * ensures that it does not end with a newline
+        * dedents so the first nonwhite character on any line is at column "indent"
+    """
+    text = _strip_leading_and_trailing_blank_lines(text)
+    text = textwrap.dedent(text)
+    if indent:
+        text = textwrap.indent(text, " " * indent)
+    return text
+
+
+def format_escape(text: str) -> str:
+    # double up curly-braces, this string will be used
+    # as part of a format_map() template later
+    text = text.replace("{", "{{")
+    text = text.replace("}", "}}")
+    return text
+
+
+def wrap_declarations(text: str, length: int = 78) -> str:
+    """
+    A simple-minded text wrapper for C function declarations.
+
+    It views a declaration line as looking like this:
+        xxxxxxxx(xxxxxxxxx,xxxxxxxxx)
+    If called with length=30, it would wrap that line into
+        xxxxxxxx(xxxxxxxxx,
+                 xxxxxxxxx)
+    (If the declaration has zero or one parameters, this
+    function won't wrap it.)
+
+    If this doesn't work properly, it's probably better to
+    start from scratch with a more sophisticated algorithm,
+    rather than try and improve/debug this dumb little function.
+    """
+    lines = []
+    for line in text.split("\n"):
+        prefix, _, after_l_paren = line.partition("(")
+        if not after_l_paren:
+            lines.append(line)
+            continue
+        in_paren, _, after_r_paren = after_l_paren.partition(")")
+        if not _:
+            lines.append(line)
+            continue
+        if "," not in in_paren:
+            lines.append(line)
+            continue
+        parameters = [x.strip() + ", " for x in in_paren.split(",")]
+        prefix += "("
+        if len(prefix) < length:
+            spaces = " " * len(prefix)
+        else:
+            spaces = " " * 4
+
+        while parameters:
+            line = prefix
+            first = True
+            while parameters:
+                if not first and (len(line) + len(parameters[0]) > length):
+                    break
+                line += parameters.pop(0)
+                first = False
+            if not parameters:
+                line = line.rstrip(", ") + ")" + after_r_paren
+            lines.append(line.rstrip())
+            prefix = spaces
+    return "\n".join(lines)