gh-113317: Argument Clinic: move linear_format into libclinic (#115518)

This commit is contained in:
Erlend E. Aasland 2024-02-15 23:52:20 +01:00 committed by GitHub
parent fd2bb4be3d
commit 58cb634632
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 76 additions and 56 deletions

View file

@ -711,7 +711,7 @@ def fn():
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 @@ def test_multiline_substitution(self):
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

View file

@ -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 @@ def parser_body(
""")
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 @@ def render_option_group_parsing(
{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 @@ def render_function(
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 @@ def render_function(
# 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 @@ def format_docstring(self) -> str:
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)

View file

@ -9,6 +9,7 @@
docstring_for_c_string,
format_escape,
indent_all_lines,
linear_format,
normalize_snippet,
pprint_words,
suffix_all_lines,
@ -33,6 +34,7 @@
"docstring_for_c_string",
"format_escape",
"indent_all_lines",
"linear_format",
"normalize_snippet",
"pprint_words",
"suffix_all_lines",

View file

@ -4,6 +4,8 @@
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])