mirror of https://github.com/python/cpython
224 lines
6.5 KiB
Python
224 lines
6.5 KiB
Python
"""A collection of string formatting helpers."""
|
|
|
|
import functools
|
|
import textwrap
|
|
from typing import Final
|
|
|
|
from libclinic import ClinicError
|
|
|
|
|
|
SIG_END_MARKER: Final = "--"
|
|
|
|
|
|
def docstring_for_c_string(docstring: str) -> str:
|
|
lines = []
|
|
# Turn docstring into a properly quoted C string.
|
|
for line in docstring.split("\n"):
|
|
lines.append('"')
|
|
lines.append(_quoted_for_c_string(line))
|
|
lines.append('\\n"\n')
|
|
|
|
if lines[-2] == SIG_END_MARKER:
|
|
# If we only have a signature, add the blank line that the
|
|
# __text_signature__ getter expects to be there.
|
|
lines.append('"\\n"')
|
|
else:
|
|
lines.pop()
|
|
lines.append('"')
|
|
return "".join(lines)
|
|
|
|
|
|
def _quoted_for_c_string(text: str) -> str:
|
|
"""Helper for docstring_for_c_string()."""
|
|
for old, new in (
|
|
("\\", "\\\\"), # must be first!
|
|
('"', '\\"'),
|
|
("'", "\\'"),
|
|
):
|
|
text = text.replace(old, new)
|
|
return text
|
|
|
|
|
|
def c_repr(text: str) -> str:
|
|
return '"' + text + '"'
|
|
|
|
|
|
def wrapped_c_string_literal(
|
|
text: str,
|
|
*,
|
|
width: int = 72,
|
|
suffix: str = "",
|
|
initial_indent: int = 0,
|
|
subsequent_indent: int = 4
|
|
) -> str:
|
|
wrapped = textwrap.wrap(
|
|
text,
|
|
width=width,
|
|
replace_whitespace=False,
|
|
drop_whitespace=False,
|
|
break_on_hyphens=False,
|
|
)
|
|
separator = c_repr(suffix + "\n" + subsequent_indent * " ")
|
|
return initial_indent * " " + c_repr(separator.join(wrapped))
|
|
|
|
|
|
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.
|
|
If text is blank, return text unchanged.
|
|
|
|
(textwrap.indent only adds to non-blank lines.)
|
|
"""
|
|
*split, last = text.split("\n")
|
|
lines = [prefix + line + suffix + "\n" for line in split]
|
|
if last:
|
|
lines.append(prefix + last + suffix)
|
|
return "".join(lines)
|
|
|
|
|
|
def indent_all_lines(text: str, prefix: str) -> str:
|
|
return _add_prefix_and_suffix(text, prefix=prefix)
|
|
|
|
|
|
def suffix_all_lines(text: str, suffix: str) -> str:
|
|
return _add_prefix_and_suffix(text, suffix=suffix)
|
|
|
|
|
|
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)
|
|
|
|
|
|
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])
|