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