summaryrefslogtreecommitdiffstats
path: root/Tools/clinic
diff options
context:
space:
mode:
authorErlend E. Aasland <erlend@python.org>2023-12-23 17:08:10 (GMT)
committerGitHub <noreply@github.com>2023-12-23 17:08:10 (GMT)
commitca71987f4e3be56a369a1dd57763c6077b3c4899 (patch)
treed1e2439413d5b1e1efe9fd674614fb50c32dadf3 /Tools/clinic
parent8bce593a6317882da82693e6e8f7c49df0cf59a5 (diff)
downloadcpython-ca71987f4e3be56a369a1dd57763c6077b3c4899.zip
cpython-ca71987f4e3be56a369a1dd57763c6077b3c4899.tar.gz
cpython-ca71987f4e3be56a369a1dd57763c6077b3c4899.tar.bz2
gh-113317: Move more formatting helpers into libclinic (#113438)
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.
Diffstat (limited to 'Tools/clinic')
-rwxr-xr-xTools/clinic/clinic.py212
-rw-r--r--Tools/clinic/libclinic/__init__.py10
-rw-r--r--Tools/clinic/libclinic/formatting.py91
3 files changed, 157 insertions, 156 deletions
diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py
index 532c45f..f004bec3 100755
--- a/Tools/clinic/clinic.py
+++ b/Tools/clinic/clinic.py
@@ -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)
diff --git a/Tools/clinic/libclinic/__init__.py b/Tools/clinic/libclinic/__init__.py
index 32ab225..0c3c684 100644
--- a/Tools/clinic/libclinic/__init__.py
+++ b/Tools/clinic/libclinic/__init__.py
@@ -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",
]
diff --git a/Tools/clinic/libclinic/formatting.py b/Tools/clinic/libclinic/formatting.py
index 691a8fc..8b3ad7b 100644
--- a/Tools/clinic/libclinic/formatting.py
+++ b/Tools/clinic/libclinic/formatting.py
@@ -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)