@redpanda-data/docs-extensions-and-macros 4.3.0 → 4.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/doc-tools.js +376 -0
- package/cli-utils/add-caret-external-links.py +68 -0
- package/cli-utils/beta-from-antora.js +27 -0
- package/cli-utils/generate-cluster-docs.sh +83 -0
- package/cli-utils/install-test-dependencies.sh +158 -0
- package/cli-utils/python-venv.sh +20 -0
- package/cli-utils/start-cluster.sh +53 -0
- package/docker-compose/bootstrap.yml +67 -0
- package/docker-compose/docker-compose.yml +414 -0
- package/docker-compose/generate-profiles.yaml +77 -0
- package/docker-compose/rpk-profile.yaml +24 -0
- package/docker-compose/transactions-schema.json +37 -0
- package/docker-compose/transactions.md +46 -0
- package/docker-compose/transform/README.adoc +73 -0
- package/docker-compose/transform/go.mod +5 -0
- package/docker-compose/transform/go.sum +2 -0
- package/docker-compose/transform/regex.wasm +0 -0
- package/docker-compose/transform/transform.go +122 -0
- package/docker-compose/transform/transform.yaml +33 -0
- package/extensions/replace-attributes-in-attachments.js +1 -1
- package/extensions/util/compute-out.js +38 -0
- package/extensions/util/create-asciidoc-file.js +15 -0
- package/macros/data-template.js +2 -2
- package/package.json +15 -3
- package/tools/docusaurus-to-antora-conversion-scripts/convert-docs.sh +114 -0
- package/tools/docusaurus-to-antora-conversion-scripts/get-file-changes.sh +9 -0
- package/tools/docusaurus-to-antora-conversion-scripts/post-process-asciidoc.js +63 -0
- package/tools/docusaurus-to-antora-conversion-scripts/pre-process-markdown.js +108 -0
- package/tools/fetch-from-github.js +63 -0
- package/tools/gen-rpk-ascii.py +477 -0
- package/tools/get-console-version.js +53 -0
- package/tools/get-redpanda-version.js +53 -0
- package/tools/metrics/metrics.py +199 -0
- package/tools/metrics/requirements.txt +1 -0
- package/tools/property-extractor/Makefile +99 -0
- package/tools/property-extractor/README.adoc +206 -0
- package/tools/property-extractor/definitions.json +245 -0
- package/tools/property-extractor/file_pair.py +7 -0
- package/tools/property-extractor/json-to-asciidoc/generate_docs.py +460 -0
- package/tools/property-extractor/parser.py +224 -0
- package/tools/property-extractor/property_bag.py +4 -0
- package/tools/property-extractor/property_extractor.py +243 -0
- package/tools/property-extractor/requirements.txt +2 -0
- package/tools/property-extractor/tests/transformers_test.py +376 -0
- package/tools/property-extractor/transformers.py +397 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from property_bag import PropertyBag
|
|
3
|
+
from parser import normalize_string
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BasicInfoTransformer:
|
|
7
|
+
def accepts(self, info, file_pair):
|
|
8
|
+
return True
|
|
9
|
+
|
|
10
|
+
def parse(self, property, info, file_pair):
|
|
11
|
+
property["name"] = info["params"][0]["value"]
|
|
12
|
+
property["defined_in"] = re.sub(
|
|
13
|
+
r"^.*src/", "src/", str(file_pair.implementation)
|
|
14
|
+
)
|
|
15
|
+
property["description"] = (
|
|
16
|
+
info["params"][1]["value"] if len(info["params"]) > 1 else None
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class IsNullableTransformer:
|
|
21
|
+
def accepts(self, info, file_pair):
|
|
22
|
+
return True
|
|
23
|
+
|
|
24
|
+
def parse(self, property, info, file_pair):
|
|
25
|
+
if len(info["params"]) > 2 and "required" in info["params"][2]["value"]:
|
|
26
|
+
is_required = (
|
|
27
|
+
re.sub(r"^.*::", "", info["params"][2]["value"]["required"]) == "yes"
|
|
28
|
+
)
|
|
29
|
+
property["nullable"] = not is_required
|
|
30
|
+
elif "std::optional" in info["declaration"]:
|
|
31
|
+
property["nullable"] = True
|
|
32
|
+
else:
|
|
33
|
+
property["nullable"] = False
|
|
34
|
+
|
|
35
|
+
return property
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class IsArrayTransformer:
|
|
39
|
+
def __init__(self, type_transformer):
|
|
40
|
+
self.type_transformer = type_transformer
|
|
41
|
+
|
|
42
|
+
def accepts(self, info, file_pair):
|
|
43
|
+
return "std::vector" in info["declaration"]
|
|
44
|
+
|
|
45
|
+
def parse(self, property, info, file_pair):
|
|
46
|
+
property["type"] = "array"
|
|
47
|
+
property["items"] = PropertyBag()
|
|
48
|
+
property["items"]["type"] = self.type_transformer.get_type_from_declaration(
|
|
49
|
+
info["declaration"]
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class NeedsRestartTransformer:
|
|
54
|
+
def accepts(self, info, file_pair):
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
def parse(self, property, info, file_pair):
|
|
58
|
+
needs_restart = "yes"
|
|
59
|
+
if len(info["params"]) > 2 and "needs_restart" in info["params"][2]["value"]:
|
|
60
|
+
needs_restart = re.sub(
|
|
61
|
+
r"^.*::", "", info["params"][2]["value"]["needs_restart"]
|
|
62
|
+
)
|
|
63
|
+
property["needs_restart"] = needs_restart != "no" # True by default, unless we find "no"
|
|
64
|
+
|
|
65
|
+
class GetsRestoredTransformer:
|
|
66
|
+
def accepts(self, info, file_pair):
|
|
67
|
+
# only run if the third param blob exists and has our flag
|
|
68
|
+
return (
|
|
69
|
+
len(info.get("params", [])) > 2
|
|
70
|
+
and isinstance(info["params"][2].get("value"), dict)
|
|
71
|
+
and "gets_restored" in info["params"][2]["value"]
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def parse(self, property, info, file_pair):
|
|
75
|
+
raw = info["params"][2]["value"]["gets_restored"]
|
|
76
|
+
# strip off e.g. "gets_restored::no" → "no"
|
|
77
|
+
flag = re.sub(r"^.*::", "", raw)
|
|
78
|
+
# store as boolean
|
|
79
|
+
property["gets_restored"] = (flag != "no")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class VisibilityTransformer:
|
|
83
|
+
def accepts(self, info, file_pair):
|
|
84
|
+
return (
|
|
85
|
+
True
|
|
86
|
+
if len(info["params"]) > 2 and "visibility" in info["params"][2]["value"]
|
|
87
|
+
else False
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def parse(self, property, info, file_pair):
|
|
91
|
+
property["visibility"] = re.sub(
|
|
92
|
+
r"^.*::", "", info["params"][2]["value"]["visibility"]
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class TypeTransformer:
|
|
97
|
+
def accepts(self, info, file_pair):
|
|
98
|
+
return True
|
|
99
|
+
|
|
100
|
+
def get_cpp_type_from_declaration(self, declaration):
|
|
101
|
+
one_line_declaration = declaration.replace("\n", "").strip()
|
|
102
|
+
raw_type = (
|
|
103
|
+
re.sub(r"^.*property<(.+)>.*", "\\1", one_line_declaration)
|
|
104
|
+
.split()[0]
|
|
105
|
+
.replace(",", "")
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if "std::optional" in raw_type:
|
|
109
|
+
raw_type = re.sub(".*std::optional<(.+)>.*", "\\1", raw_type)
|
|
110
|
+
|
|
111
|
+
if "std::vector" in raw_type:
|
|
112
|
+
raw_type = re.sub(".*std::vector<(.+)>.*", "\\1", raw_type)
|
|
113
|
+
|
|
114
|
+
return raw_type
|
|
115
|
+
|
|
116
|
+
def get_type_from_declaration(self, declaration):
|
|
117
|
+
raw_type = self.get_cpp_type_from_declaration(declaration)
|
|
118
|
+
type_mapping = [ # (regex, type)
|
|
119
|
+
("^u(nsigned|int)", "integer"),
|
|
120
|
+
("^(int|(std::)?size_t)", "integer"),
|
|
121
|
+
("data_directory_path", "string"),
|
|
122
|
+
("filesystem::path", "string"),
|
|
123
|
+
("(double|float)", "number"),
|
|
124
|
+
("string", "string"),
|
|
125
|
+
("bool", "boolean"),
|
|
126
|
+
("vector<[^>]+string>", "string[]"),
|
|
127
|
+
("std::chrono", "integer"),
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
for m in type_mapping:
|
|
131
|
+
if re.search(m[0], raw_type):
|
|
132
|
+
return m[1]
|
|
133
|
+
|
|
134
|
+
return raw_type
|
|
135
|
+
|
|
136
|
+
def parse(self, property, info, file_pair):
|
|
137
|
+
property["type"] = self.get_type_from_declaration(info["declaration"])
|
|
138
|
+
return property
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class DeprecatedTransformer:
|
|
142
|
+
def accepts(self, info, file_pair):
|
|
143
|
+
return "deprecated_property" in info["declaration"] or (
|
|
144
|
+
len(info["params"]) > 2
|
|
145
|
+
and "visibility" in info["params"][2]["value"]
|
|
146
|
+
and "deprecated" in info["params"][2]["value"]["visibility"]
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def parse(self, property, info, file_pair):
|
|
150
|
+
property["is_deprecated"] = True
|
|
151
|
+
property["type"] = None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class IsSecretTransformer:
|
|
155
|
+
def accepts(self, info, file_pair):
|
|
156
|
+
return (
|
|
157
|
+
True
|
|
158
|
+
if len(info["params"]) > 2 and "secret" in info["params"][2]["value"]
|
|
159
|
+
else False
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def parse(self, property, info, file_pair):
|
|
163
|
+
is_secret = re.sub(r"^.*::", "", info["params"][2]["value"]["secret"])
|
|
164
|
+
property["is_secret"] = is_secret == "yes"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class NumericBoundsTransformer:
|
|
168
|
+
def __init__(self, type_transformer):
|
|
169
|
+
self.type_transformer = type_transformer
|
|
170
|
+
|
|
171
|
+
def accepts(self, info, file_pair):
|
|
172
|
+
type_str = self.type_transformer.get_cpp_type_from_declaration(info["declaration"])
|
|
173
|
+
return re.search("^(unsigned|u?int(8|16|32|64)?(_t)?)", type_str)
|
|
174
|
+
|
|
175
|
+
def parse(self, property, info, file_pair):
|
|
176
|
+
type_mapping = dict(
|
|
177
|
+
unsigned=(0, 2**32 - 1),
|
|
178
|
+
uint8_t=(0, 2**8 - 1),
|
|
179
|
+
uint16_t=(0, 2**16 - 1),
|
|
180
|
+
uint32_t=(0, 2**32 - 1),
|
|
181
|
+
uint64_t=(0, 2**64 - 1),
|
|
182
|
+
int=(-(2**31), 2**31 - 1),
|
|
183
|
+
int8_t=(-(2**7), 2**7 - 1),
|
|
184
|
+
int16_t=(-(2**15), 2**15 - 1),
|
|
185
|
+
int32_t=(-(2**31), 2**31 - 1),
|
|
186
|
+
int64_t=(-(2**63), 2**63 - 1),
|
|
187
|
+
)
|
|
188
|
+
type_str = self.type_transformer.get_cpp_type_from_declaration(info["declaration"])
|
|
189
|
+
if type_str in type_mapping:
|
|
190
|
+
property["minimum"] = type_mapping[type_str][0]
|
|
191
|
+
property["maximum"] = type_mapping[type_str][1]
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class DurationBoundsTransformer:
|
|
195
|
+
def __init__(self, type_transformer):
|
|
196
|
+
self.type_transformer = type_transformer
|
|
197
|
+
|
|
198
|
+
def accepts(self, info, file_pair):
|
|
199
|
+
return re.search("std::chrono::", info["declaration"])
|
|
200
|
+
|
|
201
|
+
def parse(self, property, info, file_pair):
|
|
202
|
+
# Sizes based on: https://en.cppreference.com/w/cpp/chrono/duration
|
|
203
|
+
type_mapping = dict(
|
|
204
|
+
nanoseconds=(-(2**63), 2**63 - 1), # int 64
|
|
205
|
+
microseconds=(-(2**54), 2**54 - 1), # int 55
|
|
206
|
+
milliseconds=(-(2**44), 2**44 - 1), # int 45
|
|
207
|
+
seconds=(-(2**34), 2**34 - 1), # int 35
|
|
208
|
+
minutes=(-(2**28), 2**28 - 1), # int 29
|
|
209
|
+
hours=(-(2**22), 2**22 - 1), # int 23
|
|
210
|
+
days=(-(2**24), 2**24 - 1), # int 25
|
|
211
|
+
weeks=(-(2**21), 2**21 - 1), # int 22
|
|
212
|
+
months=(-(2**19), 2**19 - 1), # int 20
|
|
213
|
+
years=(-(2**16), 2**16 - 1), # int 17
|
|
214
|
+
)
|
|
215
|
+
type_str = self.type_transformer.get_cpp_type_from_declaration(info["declaration"])
|
|
216
|
+
duration_type = type_str.replace("std::chrono::", "")
|
|
217
|
+
if duration_type in type_mapping:
|
|
218
|
+
property["minimum"] = type_mapping[duration_type][0]
|
|
219
|
+
property["maximum"] = type_mapping[duration_type][1]
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class SimpleDefaultValuesTransformer:
|
|
223
|
+
def accepts(self, info, file_pair):
|
|
224
|
+
# The default value is the 4th parameter.
|
|
225
|
+
return info["params"] and len(info["params"]) > 3
|
|
226
|
+
|
|
227
|
+
def parse(self, property, info, file_pair):
|
|
228
|
+
default = info["params"][3]["value"]
|
|
229
|
+
|
|
230
|
+
# Handle simple cases.
|
|
231
|
+
if default == "std::nullopt":
|
|
232
|
+
property["default"] = None
|
|
233
|
+
elif default == "{}":
|
|
234
|
+
pass
|
|
235
|
+
elif isinstance(default, PropertyBag):
|
|
236
|
+
property["default"] = default
|
|
237
|
+
elif re.search(r"^-?[0-9][0-9']*$", default): # integers (allow digit group separators)
|
|
238
|
+
property["default"] = int(re.sub(r"[^0-9-]", "", default))
|
|
239
|
+
elif re.search(r"^-?[0-9]+(\.[0-9]+)?$", default): # floats
|
|
240
|
+
property["default"] = float(re.sub(r"[^0-9.\-]", "", default))
|
|
241
|
+
elif re.search("^(true|false)$", default): # booleans
|
|
242
|
+
property["default"] = True if default == "true" else False
|
|
243
|
+
elif re.search(r"^\{[^:]+\}$", default): # string lists
|
|
244
|
+
property["default"] = [
|
|
245
|
+
normalize_string(s)
|
|
246
|
+
for s in re.sub(r"{([^}]+)}", r"\1", default).split(",")
|
|
247
|
+
]
|
|
248
|
+
else:
|
|
249
|
+
# File sizes.
|
|
250
|
+
matches = re.search("^([0-9]+)_(.)iB$", default)
|
|
251
|
+
if matches:
|
|
252
|
+
size = int(matches.group(1))
|
|
253
|
+
unit = matches.group(2)
|
|
254
|
+
if unit == "K":
|
|
255
|
+
size = size * 1024
|
|
256
|
+
elif unit == "M":
|
|
257
|
+
size = size * 1024**2
|
|
258
|
+
elif unit == "G":
|
|
259
|
+
size = size * 1024**3
|
|
260
|
+
elif unit == "T":
|
|
261
|
+
size = size * 1024**4
|
|
262
|
+
elif unit == "P":
|
|
263
|
+
size = size * 1024**5
|
|
264
|
+
property["default"] = size
|
|
265
|
+
elif re.search("^(https|/[^/])", default): # URLs and paths
|
|
266
|
+
property["default"] = default
|
|
267
|
+
else:
|
|
268
|
+
# For durations, enums, or other default initializations.
|
|
269
|
+
if not re.search("([0-9]|::|\\()", default):
|
|
270
|
+
property["default"] = default
|
|
271
|
+
else:
|
|
272
|
+
property["default"] = default
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class FriendlyDefaultTransformer:
|
|
276
|
+
"""
|
|
277
|
+
Transforms C++ default expressions into a more user-friendly format for docs.
|
|
278
|
+
Handles cases like:
|
|
279
|
+
- std::numeric_limits<uint64_t>::max()
|
|
280
|
+
- std::chrono::seconds(15min)
|
|
281
|
+
- std::vector<ss::sstring>{"basic"}
|
|
282
|
+
- std::chrono::milliseconds(10)
|
|
283
|
+
- std::nullopt
|
|
284
|
+
"""
|
|
285
|
+
def accepts(self, info, file_pair):
|
|
286
|
+
return info.get("params") and len(info["params"]) > 3
|
|
287
|
+
|
|
288
|
+
def parse(self, property, info, file_pair):
|
|
289
|
+
default = info["params"][3]["value"]
|
|
290
|
+
|
|
291
|
+
# Transform std::nullopt into None.
|
|
292
|
+
if "std::nullopt" in default:
|
|
293
|
+
property["default"] = None
|
|
294
|
+
return property
|
|
295
|
+
|
|
296
|
+
# Transform std::numeric_limits expressions.
|
|
297
|
+
if "std::numeric_limits" in default:
|
|
298
|
+
property["default"] = "Maximum value"
|
|
299
|
+
return property
|
|
300
|
+
|
|
301
|
+
# Transform std::chrono durations.
|
|
302
|
+
if "std::chrono" in default:
|
|
303
|
+
m = re.search(r"std::chrono::(\w+)\(([^)]+)\)", default)
|
|
304
|
+
if m:
|
|
305
|
+
unit = m.group(1)
|
|
306
|
+
value = m.group(2).strip()
|
|
307
|
+
property["default"] = f"{value} {unit}"
|
|
308
|
+
return property
|
|
309
|
+
|
|
310
|
+
# Transform std::vector defaults.
|
|
311
|
+
if "std::vector" in default:
|
|
312
|
+
m = re.search(r'\{([^}]+)\}', default)
|
|
313
|
+
if m:
|
|
314
|
+
contents = m.group(1).strip()
|
|
315
|
+
items = [item.strip(' "\'') for item in contents.split(',')]
|
|
316
|
+
property["default"] = items
|
|
317
|
+
return property
|
|
318
|
+
|
|
319
|
+
# Otherwise, leave the default as-is.
|
|
320
|
+
property["default"] = default
|
|
321
|
+
return property
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
class ExperimentalTransformer:
|
|
325
|
+
def accepts(self, info, file_pair):
|
|
326
|
+
return info.get("type") is not None and info["type"].startswith(("development_", "hidden_when_default_"))
|
|
327
|
+
def parse(self, property, info, file_pair):
|
|
328
|
+
property["is_experimental_property"] = True
|
|
329
|
+
return property
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
class AliasTransformer:
|
|
333
|
+
def accepts(self, info, file_pair):
|
|
334
|
+
if 'params' in info:
|
|
335
|
+
for param in info['params']:
|
|
336
|
+
if isinstance(param, dict) and 'value' in param:
|
|
337
|
+
value = param['value']
|
|
338
|
+
if isinstance(value, dict) and 'aliases' in value:
|
|
339
|
+
return True
|
|
340
|
+
return False
|
|
341
|
+
|
|
342
|
+
def parse(self, property, info, file_pair):
|
|
343
|
+
aliases = []
|
|
344
|
+
for param in info['params']:
|
|
345
|
+
value = param.get('value', {})
|
|
346
|
+
if isinstance(value, dict) and 'aliases' in value:
|
|
347
|
+
aliases_dict = value['aliases']
|
|
348
|
+
# Extract each alias, removing any surrounding braces or quotes.
|
|
349
|
+
aliases.extend(alias.strip('{}"') for alias in aliases_dict.values())
|
|
350
|
+
property['aliases'] = aliases
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
class EnterpriseTransformer:
|
|
354
|
+
def accepts(self, info, file_pair):
|
|
355
|
+
return bool(info.get('type') and 'enterprise' in info['type'])
|
|
356
|
+
|
|
357
|
+
def parse(self, property, info, file_pair):
|
|
358
|
+
if info['params'] is not None:
|
|
359
|
+
enterpriseValue = info['params'][0]['value']
|
|
360
|
+
property['enterprise_value'] = enterpriseValue
|
|
361
|
+
property['is_enterprise'] = True
|
|
362
|
+
del info['params'][0]
|
|
363
|
+
return property
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
class MetaParamTransformer:
|
|
367
|
+
def accepts(self, info, file_pair):
|
|
368
|
+
"""
|
|
369
|
+
Check if the given info contains parameters that include a meta{...} value.
|
|
370
|
+
"""
|
|
371
|
+
if 'params' in info:
|
|
372
|
+
for param in info['params']:
|
|
373
|
+
if isinstance(param, dict) and 'value' in param:
|
|
374
|
+
value = param['value']
|
|
375
|
+
if isinstance(value, str) and value.startswith("meta{"):
|
|
376
|
+
return True
|
|
377
|
+
return False
|
|
378
|
+
|
|
379
|
+
def parse(self, property, info, file_pair):
|
|
380
|
+
"""
|
|
381
|
+
Transform into a structured dictionary.
|
|
382
|
+
"""
|
|
383
|
+
if 'params' not in info or info['params'] is None:
|
|
384
|
+
return property
|
|
385
|
+
|
|
386
|
+
iterable_params = info['params']
|
|
387
|
+
for param in iterable_params:
|
|
388
|
+
if isinstance(param['value'], str) and param['value'].startswith("meta{"):
|
|
389
|
+
meta_content = param['value'].strip("meta{ }").strip()
|
|
390
|
+
meta_dict = {}
|
|
391
|
+
for item in meta_content.split(','):
|
|
392
|
+
item = item.strip()
|
|
393
|
+
if '=' in item:
|
|
394
|
+
key, value = item.split('=')
|
|
395
|
+
meta_dict[key.strip().replace('.', '')] = value.strip()
|
|
396
|
+
meta_dict['type'] = 'initializer_list' # Enforce required type
|
|
397
|
+
param['value'] = meta_dict
|