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.
This commit is contained in:
Erlend E. Aasland 2023-12-23 18:08:10 +01:00 committed by GitHub
parent 8bce593a63
commit ca71987f4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 160 additions and 159 deletions

View 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):

View File

@ -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)

View 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",
]

View 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)