1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
|
"""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])
|