@redpanda-data/docs-extensions-and-macros 4.9.1 → 4.10.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 +45 -1
- package/cli-utils/install-test-dependencies.sh +474 -106
- package/package.json +9 -3
- package/tools/bundle-openapi.js +814 -0
- package/tools/property-extractor/Makefile +2 -2
- package/tools/property-extractor/generate-handlebars-docs.js +15 -4
- package/tools/property-extractor/property_extractor.py +285 -40
- package/tools/property-extractor/transformers.py +240 -36
- package/tools/redpanda-connect/helpers/renderConnectFields.js +1 -1
|
@@ -12,12 +12,12 @@ logger = logging.getLogger(__name__)
|
|
|
12
12
|
# the centralized enterprise value processing logic without creating import cycles.
|
|
13
13
|
def get_process_enterprise_value():
|
|
14
14
|
"""
|
|
15
|
-
Lazily
|
|
15
|
+
Lazily load the centralized process_enterprise_value function from property_extractor.
|
|
16
16
|
|
|
17
|
-
Attempts to import
|
|
17
|
+
Attempts to import and return the `process_enterprise_value` callable; logs an error and returns `None` if the import fails.
|
|
18
18
|
|
|
19
19
|
Returns:
|
|
20
|
-
|
|
20
|
+
The `process_enterprise_value` callable if available, `None` otherwise.
|
|
21
21
|
"""
|
|
22
22
|
try:
|
|
23
23
|
from property_extractor import process_enterprise_value
|
|
@@ -27,6 +27,23 @@ def get_process_enterprise_value():
|
|
|
27
27
|
return None
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
def get_resolve_constexpr_identifier():
|
|
31
|
+
"""
|
|
32
|
+
Lazily import and return the `resolve_constexpr_identifier` function from `property_extractor`.
|
|
33
|
+
|
|
34
|
+
Attempts to import `resolve_constexpr_identifier` and return it to avoid circular-import issues.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Callable or None: The `resolve_constexpr_identifier` callable when available, otherwise `None`.
|
|
38
|
+
"""
|
|
39
|
+
try:
|
|
40
|
+
from property_extractor import resolve_constexpr_identifier
|
|
41
|
+
return resolve_constexpr_identifier
|
|
42
|
+
except ImportError as e:
|
|
43
|
+
logger.exception("Cannot import resolve_constexpr_identifier from property_extractor: %s", e)
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
|
|
30
47
|
class BasicInfoTransformer:
|
|
31
48
|
def accepts(self, info, file_pair):
|
|
32
49
|
"""
|
|
@@ -182,29 +199,59 @@ class TypeTransformer:
|
|
|
182
199
|
|
|
183
200
|
def get_cpp_type_from_declaration(self, declaration):
|
|
184
201
|
"""
|
|
185
|
-
Extract the inner type from
|
|
186
|
-
|
|
187
|
-
This method handles various C++ template types and extracts the core type T from:
|
|
188
|
-
- property<T> -> T
|
|
189
|
-
- std::optional<T> -> T
|
|
190
|
-
- std::vector<T> -> T
|
|
191
|
-
- one_or_many_property<T> -> T (Redpanda's flexible array type)
|
|
202
|
+
Extract the inner C++ type from wrapped declarations like `property<T>`, `std::optional<T>`, `std::vector<T>`, or `one_or_many_property<T>`.
|
|
192
203
|
|
|
193
|
-
|
|
194
|
-
to accept either a single value or an array of values in the configuration.
|
|
195
|
-
Examples:
|
|
196
|
-
- one_or_many_property<model::broker_endpoint> -> model::broker_endpoint
|
|
197
|
-
- one_or_many_property<endpoint_tls_config> -> endpoint_tls_config
|
|
204
|
+
Parses common wrapper templates and returns the unwrapped type name (for example, returns `model::broker_endpoint` from `one_or_many_property<model::broker_endpoint>`). The returned type is intended for downstream mapping to JSON schema types and default value resolution.
|
|
198
205
|
|
|
199
|
-
|
|
200
|
-
|
|
206
|
+
Returns:
|
|
207
|
+
raw_type (str): The extracted inner C++ type as a string, or a best-effort fragment of the declaration if a precise extraction cannot be performed.
|
|
201
208
|
"""
|
|
202
209
|
one_line_declaration = declaration.replace("\n", "").strip()
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
210
|
+
|
|
211
|
+
# Extract property template content with proper nesting handling
|
|
212
|
+
# This handles cases like property<std::vector<config::sasl_mechanisms_override>>
|
|
213
|
+
def extract_template_content(text, template_name):
|
|
214
|
+
"""
|
|
215
|
+
Extracts the inner contents of the first occurrence of a template with the given name, correctly handling nested angle brackets.
|
|
216
|
+
|
|
217
|
+
Parameters:
|
|
218
|
+
text (str): The string to search for the template.
|
|
219
|
+
template_name (str): The template name (e.g., "std::vector" or "property").
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
str or None: The substring inside the outermost angle brackets for the matched template (excluding the brackets),
|
|
223
|
+
or `None` if the template is not found or angle brackets are unbalanced.
|
|
224
|
+
"""
|
|
225
|
+
start_idx = text.find(f'{template_name}<')
|
|
226
|
+
if start_idx == -1:
|
|
227
|
+
return None
|
|
228
|
+
|
|
229
|
+
start_idx += len(f'{template_name}<')
|
|
230
|
+
bracket_count = 1
|
|
231
|
+
i = start_idx
|
|
232
|
+
|
|
233
|
+
while i < len(text) and bracket_count > 0:
|
|
234
|
+
if text[i] == '<':
|
|
235
|
+
bracket_count += 1
|
|
236
|
+
elif text[i] == '>':
|
|
237
|
+
bracket_count -= 1
|
|
238
|
+
i += 1
|
|
239
|
+
|
|
240
|
+
if bracket_count == 0:
|
|
241
|
+
return text[start_idx:i-1]
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
# Extract the content from property<...>
|
|
245
|
+
property_content = extract_template_content(one_line_declaration, 'property')
|
|
246
|
+
if property_content:
|
|
247
|
+
raw_type = property_content.split()[0].replace(",", "")
|
|
248
|
+
else:
|
|
249
|
+
# Fallback to original regex for simpler cases
|
|
250
|
+
raw_type = (
|
|
251
|
+
re.sub(r"^.*property<(.+)>.*", "\\1", one_line_declaration)
|
|
252
|
+
.split()[0]
|
|
253
|
+
.replace(",", "")
|
|
254
|
+
)
|
|
208
255
|
|
|
209
256
|
if self.OPTIONAL_PATTERN in raw_type:
|
|
210
257
|
raw_type = re.sub(".*std::optional<(.+)>.*", "\\1", raw_type)
|
|
@@ -223,6 +270,15 @@ class TypeTransformer:
|
|
|
223
270
|
return raw_type
|
|
224
271
|
|
|
225
272
|
def get_type_from_declaration(self, declaration):
|
|
273
|
+
"""
|
|
274
|
+
Map a C++ type declaration string to a simplified, user-facing type name.
|
|
275
|
+
|
|
276
|
+
Parameters:
|
|
277
|
+
declaration (str): C++ type declaration or template expression from which the effective type will be derived.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
str: A JSON-schema-friendly type name such as "integer", "number", "string", "string[]", or "boolean". If no mapping matches, returns the normalized/raw extracted C++ type.
|
|
281
|
+
"""
|
|
226
282
|
raw_type = self.get_cpp_type_from_declaration(declaration)
|
|
227
283
|
type_mapping = [ # (regex, type)
|
|
228
284
|
("^u(nsigned|int)", "integer"),
|
|
@@ -240,9 +296,29 @@ class TypeTransformer:
|
|
|
240
296
|
if re.search(m[0], raw_type):
|
|
241
297
|
return m[1]
|
|
242
298
|
|
|
299
|
+
# Handle specific user-unfriendly C++ types with descriptive alternatives
|
|
300
|
+
# Map complex C++ config types to user-friendly JSON schema types
|
|
301
|
+
user_friendly_types = {
|
|
302
|
+
"config::sasl_mechanisms_override": "object",
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if raw_type in user_friendly_types:
|
|
306
|
+
return user_friendly_types[raw_type]
|
|
307
|
+
|
|
243
308
|
return raw_type
|
|
244
309
|
|
|
245
310
|
def parse(self, property, info, file_pair):
|
|
311
|
+
"""
|
|
312
|
+
Set the property's "type" field to the JSON schema type derived from the C++ declaration.
|
|
313
|
+
|
|
314
|
+
Parameters:
|
|
315
|
+
property (dict): Mutable property bag to be updated.
|
|
316
|
+
info (dict): Parsed property metadata; its "declaration" field is used to determine the type.
|
|
317
|
+
file_pair: Unused here; present for transformer interface compatibility.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
property (dict): The same property bag with "type" set to the derived type string.
|
|
321
|
+
"""
|
|
246
322
|
property["type"] = self.get_type_from_declaration(info["declaration"])
|
|
247
323
|
return property
|
|
248
324
|
|
|
@@ -394,42 +470,170 @@ class FriendlyDefaultTransformer:
|
|
|
394
470
|
|
|
395
471
|
# Class-level constants for pattern matching in default values
|
|
396
472
|
ARRAY_PATTERN_STD_VECTOR = "std::vector"
|
|
473
|
+
SSTRING_CONSTRUCTOR_PATTERN = r'ss::sstring\{([a-zA-Z_][a-zA-Z0-9_]*)\}'
|
|
474
|
+
VECTOR_INITIALIZER_PATTERN = r'std::vector<[^>]+>\s*\{(.*)\}$'
|
|
475
|
+
CHRONO_PATTERN = r"std::chrono::(\w+)\(([^)]+)\)"
|
|
476
|
+
|
|
477
|
+
def __init__(self):
|
|
478
|
+
"""
|
|
479
|
+
Initialize the transformer and set up a placeholder for a lazily-loaded resolver.
|
|
480
|
+
|
|
481
|
+
Sets self._resolver to None to indicate the resolver has not been loaded yet.
|
|
482
|
+
"""
|
|
483
|
+
self._resolver = None
|
|
397
484
|
|
|
398
485
|
def accepts(self, info, file_pair):
|
|
486
|
+
"""
|
|
487
|
+
Determine whether the transformer should run for the given property info by checking for a fourth parameter.
|
|
488
|
+
|
|
489
|
+
Parameters:
|
|
490
|
+
info (dict): Parsed property metadata; expects a "params" list when present.
|
|
491
|
+
file_pair (tuple): Source/implementation file pair (unused by this check).
|
|
492
|
+
|
|
493
|
+
Returns:
|
|
494
|
+
`true` if `info["params"]` exists and contains at least four items, `false` otherwise.
|
|
495
|
+
"""
|
|
399
496
|
return info.get("params") and len(info["params"]) > 3
|
|
400
497
|
|
|
498
|
+
def _get_resolver(self):
|
|
499
|
+
"""
|
|
500
|
+
Lazily load and cache the constexpr identifier resolver.
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
callable or None: The resolver function if available, or `None` if it could not be loaded.
|
|
504
|
+
"""
|
|
505
|
+
if self._resolver is None:
|
|
506
|
+
resolver = get_resolve_constexpr_identifier()
|
|
507
|
+
self._resolver = resolver if resolver else False
|
|
508
|
+
return self._resolver if self._resolver is not False else None
|
|
509
|
+
|
|
510
|
+
def _resolve_identifier(self, identifier):
|
|
511
|
+
"""
|
|
512
|
+
Resolve a constexpr identifier to its corresponding string value.
|
|
513
|
+
|
|
514
|
+
Parameters:
|
|
515
|
+
identifier (str): Identifier to resolve (for example, "scram" or "gssapi").
|
|
516
|
+
|
|
517
|
+
Returns:
|
|
518
|
+
str or None: The resolved string value if successful, `None` when the identifier is invalid or cannot be resolved.
|
|
519
|
+
"""
|
|
520
|
+
if not identifier or not isinstance(identifier, str):
|
|
521
|
+
logger.warning(f"Invalid identifier for resolution: {identifier}")
|
|
522
|
+
return None
|
|
523
|
+
|
|
524
|
+
resolver = self._get_resolver()
|
|
525
|
+
if resolver:
|
|
526
|
+
try:
|
|
527
|
+
return resolver(identifier)
|
|
528
|
+
except (AttributeError, TypeError, ValueError) as e:
|
|
529
|
+
logger.debug(f"Failed to resolve identifier '{identifier}': {e}")
|
|
530
|
+
except Exception as e:
|
|
531
|
+
logger.exception(f"Unexpected error resolving identifier '{identifier}': {e}")
|
|
532
|
+
|
|
533
|
+
return None
|
|
534
|
+
|
|
535
|
+
def _process_sstring_constructor(self, item):
|
|
536
|
+
"""
|
|
537
|
+
Convert an ss::sstring{identifier} constructor string to its resolved value when possible.
|
|
538
|
+
|
|
539
|
+
If the input matches the ss::sstring{...} pattern, attempts to resolve the enclosed identifier and returns the resolved string. If resolution fails, returns the raw identifier. If the input does not match the pattern or is falsy, returns it unchanged.
|
|
540
|
+
|
|
541
|
+
Parameters:
|
|
542
|
+
item (str): The constructor expression or string to process.
|
|
543
|
+
|
|
544
|
+
Returns:
|
|
545
|
+
str: The resolved string when resolution succeeds, the extracted identifier if resolution fails, or the original input if it does not match.
|
|
546
|
+
"""
|
|
547
|
+
if not item:
|
|
548
|
+
return item
|
|
549
|
+
|
|
550
|
+
match = re.match(self.SSTRING_CONSTRUCTOR_PATTERN, item)
|
|
551
|
+
if not match:
|
|
552
|
+
return item
|
|
553
|
+
|
|
554
|
+
identifier = match.group(1)
|
|
555
|
+
resolved = self._resolve_identifier(identifier)
|
|
556
|
+
|
|
557
|
+
if resolved:
|
|
558
|
+
logger.debug(f"Resolved ss::sstring{{{identifier}}} -> '{resolved}'")
|
|
559
|
+
return resolved
|
|
560
|
+
|
|
561
|
+
# Log warning but continue with original identifier
|
|
562
|
+
logger.warning(f"Could not resolve identifier '{identifier}' in ss::sstring constructor")
|
|
563
|
+
return identifier
|
|
564
|
+
|
|
565
|
+
def _parse_vector_contents(self, contents):
|
|
566
|
+
"""
|
|
567
|
+
Parse a comma-separated std::vector initializer string into a list of cleaned, processed items.
|
|
568
|
+
|
|
569
|
+
Parameters:
|
|
570
|
+
contents (str): The inner contents of a vector initializer (e.g. '\"a\", ss::sstring{ID}, \"b\"'); may be empty or None.
|
|
571
|
+
|
|
572
|
+
Returns:
|
|
573
|
+
list: Ordered list of processed, unquoted items with empty entries omitted.
|
|
574
|
+
"""
|
|
575
|
+
if not contents:
|
|
576
|
+
return []
|
|
577
|
+
|
|
578
|
+
# Split by comma and process each item
|
|
579
|
+
raw_items = [contents] if ',' not in contents else contents.split(',')
|
|
580
|
+
|
|
581
|
+
processed_items = []
|
|
582
|
+
for item in raw_items:
|
|
583
|
+
item = item.strip(' "\'')
|
|
584
|
+
if item: # Skip empty items
|
|
585
|
+
processed_item = self._process_sstring_constructor(item)
|
|
586
|
+
processed_items.append(processed_item)
|
|
587
|
+
|
|
588
|
+
return processed_items
|
|
589
|
+
|
|
401
590
|
def parse(self, property, info, file_pair):
|
|
591
|
+
"""
|
|
592
|
+
Convert a C++ default expression into a JSON-friendly value and store it on the property under the "default" key.
|
|
593
|
+
|
|
594
|
+
Parameters:
|
|
595
|
+
property (dict): Property dictionary to modify; updated in place with a "default" entry.
|
|
596
|
+
info (dict): Parsed property information; the default expression is expected at info["params"][3]["value"].
|
|
597
|
+
file_pair: File pair context (ignored by this function).
|
|
598
|
+
|
|
599
|
+
Returns:
|
|
600
|
+
dict: The modified property dictionary with a normalized "default" value.
|
|
601
|
+
"""
|
|
402
602
|
default = info["params"][3]["value"]
|
|
603
|
+
|
|
604
|
+
# Handle null/empty defaults
|
|
605
|
+
if not default:
|
|
606
|
+
return property
|
|
403
607
|
|
|
404
|
-
# Transform std::nullopt into None
|
|
608
|
+
# Transform std::nullopt into None
|
|
405
609
|
if "std::nullopt" in default:
|
|
406
610
|
property["default"] = None
|
|
407
611
|
return property
|
|
408
612
|
|
|
409
|
-
# Transform std::numeric_limits expressions
|
|
613
|
+
# Transform std::numeric_limits expressions
|
|
410
614
|
if "std::numeric_limits" in default:
|
|
411
615
|
property["default"] = "Maximum value"
|
|
412
616
|
return property
|
|
413
617
|
|
|
414
|
-
# Transform std::chrono durations
|
|
618
|
+
# Transform std::chrono durations
|
|
415
619
|
if "std::chrono" in default:
|
|
416
|
-
|
|
417
|
-
if
|
|
418
|
-
unit =
|
|
419
|
-
value =
|
|
620
|
+
match = re.search(self.CHRONO_PATTERN, default)
|
|
621
|
+
if match:
|
|
622
|
+
unit = match.group(1)
|
|
623
|
+
value = match.group(2).strip()
|
|
420
624
|
property["default"] = f"{value} {unit}"
|
|
421
625
|
return property
|
|
422
626
|
|
|
423
|
-
# Transform std::vector defaults
|
|
627
|
+
# Transform std::vector defaults
|
|
424
628
|
if self.ARRAY_PATTERN_STD_VECTOR in default:
|
|
425
|
-
|
|
426
|
-
if
|
|
427
|
-
contents =
|
|
428
|
-
items =
|
|
629
|
+
vector_match = re.search(self.VECTOR_INITIALIZER_PATTERN, default)
|
|
630
|
+
if vector_match:
|
|
631
|
+
contents = vector_match.group(1).strip()
|
|
632
|
+
items = self._parse_vector_contents(contents)
|
|
429
633
|
property["default"] = items
|
|
430
634
|
return property
|
|
431
635
|
|
|
432
|
-
#
|
|
636
|
+
# For all other cases, leave the default as-is
|
|
433
637
|
property["default"] = default
|
|
434
638
|
return property
|
|
435
639
|
|
|
@@ -566,4 +770,4 @@ class MetaParamTransformer:
|
|
|
566
770
|
key, value = item.split('=')
|
|
567
771
|
meta_dict[key.strip().replace('.', '')] = value.strip()
|
|
568
772
|
meta_dict['type'] = 'initializer_list' # Enforce required type
|
|
569
|
-
param['value'] = meta_dict
|
|
773
|
+
param['value'] = meta_dict
|
|
@@ -64,7 +64,7 @@ module.exports = function renderConnectFields(children, prefix = '') {
|
|
|
64
64
|
|
|
65
65
|
// --- Interpolation support notice ---
|
|
66
66
|
const interpolationNotice = 'This field supports xref:configuration:interpolation.adoc#bloblang-queries[interpolation functions].';
|
|
67
|
-
if (child.
|
|
67
|
+
if (child.interpolated === true) {
|
|
68
68
|
// Only add if not already present (case-insensitive)
|
|
69
69
|
const descLower = desc.toLowerCase();
|
|
70
70
|
if (!descLower.includes('interpolation functions')) {
|