Skip to content

Commit

Permalink
pythongh-113317: Argument Clinic: move linear_format into libclinic
Browse files Browse the repository at this point in the history
  • Loading branch information
erlend-aasland committed Feb 15, 2024
1 parent ad4f909 commit 9ad47e8
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 56 deletions.
15 changes: 14 additions & 1 deletion Lib/test/test_clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -761,6 +761,19 @@ def test_multiline_substitution(self):
def
""", name='bingle\nbungle\n')

def test_text_before_block_marker(self):
regex = "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 = "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
Expand Down
65 changes: 10 additions & 55 deletions Tools/clinic/clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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'],
Expand All @@ -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)

Expand Down Expand Up @@ -6130,9 +6085,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)
Expand Down
2 changes: 2 additions & 0 deletions Tools/clinic/libclinic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
docstring_for_c_string,
format_escape,
indent_all_lines,
linear_format,
normalize_snippet,
pprint_words,
suffix_all_lines,
Expand All @@ -33,6 +34,7 @@
"docstring_for_c_string",
"format_escape",
"indent_all_lines",
"linear_format",
"normalize_snippet",
"pprint_words",
"suffix_all_lines",
Expand Down
47 changes: 47 additions & 0 deletions Tools/clinic/libclinic/formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import textwrap
from typing import Final

from libclinic import ClinicError


SIG_END_MARKER: Final = "--"

Expand Down Expand Up @@ -171,3 +173,48 @@ 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("Non-whitespace characters found before "
f"'{{{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])

0 comments on commit 9ad47e8

Please sign in to comment.