@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.
@@ -111,10 +111,10 @@ generate-docs: node-deps
111
111
  @# Use the enhanced properties file (with overrides) for documentation generation if it exists
112
112
  @if [ -f "$(TOOL_ROOT)/gen/$(TAG)-properties.json" ]; then \
113
113
  cd $(TOOL_ROOT) && \
114
- node generate-handlebars-docs.js "gen/$(TAG)-properties.json" "$(OUTPUT_AUTOGENERATED_DIR)"; \
114
+ node generate-handlebars-docs.js "gen/$(TAG)-properties.json" "$(OUTPUT_ASCIIDOC_DIR)"; \
115
115
  else \
116
116
  cd $(TOOL_ROOT) && \
117
- node generate-handlebars-docs.js "gen/properties-output.json" "$(OUTPUT_AUTOGENERATED_DIR)"; \
117
+ node generate-handlebars-docs.js "gen/properties-output.json" "$(OUTPUT_ASCIIDOC_DIR)"; \
118
118
  fi
119
119
  @echo "📄 Copying properties JSON files to $(OUTPUT_JSON_DIR)…"
120
120
  @if [ -f "$(TOOL_ROOT)/gen/$(TAG)-properties.json" ]; then \
@@ -60,7 +60,17 @@ NOTE: Some cluster properties require that you restart the cluster for any updat
60
60
  sectionTitle: 'Cluster configuration',
61
61
  groups: [
62
62
  {
63
- filter: (prop) => prop.config_scope === 'cluster' && !prop.is_deprecated
63
+ filter: (prop) => prop.config_scope === 'cluster' && !prop.is_deprecated && !(
64
+ prop.name && (
65
+ prop.name.includes('cloud_storage') ||
66
+ prop.name.includes('s3_') ||
67
+ prop.name.includes('azure_') ||
68
+ prop.name.includes('gcs_') ||
69
+ prop.name.includes('archival_') ||
70
+ prop.name.includes('remote_') ||
71
+ prop.name.includes('tiered_')
72
+ )
73
+ )
64
74
  }
65
75
  ],
66
76
  filename: 'cluster-properties.adoc'
@@ -269,7 +279,8 @@ function generateDeprecatedDocs(properties, outputDir) {
269
279
  };
270
280
 
271
281
  const output = template(data);
272
- const outputPath = path.join(outputDir, 'deprecated', 'partials', 'deprecated-properties.adoc');
282
+ // Navigate back from pages/properties to reference, then into partials/deprecated
283
+ const outputPath = path.join(path.dirname(path.dirname(outputDir)), 'partials', 'deprecated', 'deprecated-properties.adoc');
273
284
 
274
285
  fs.mkdirSync(path.dirname(outputPath), { recursive: true });
275
286
  fs.writeFileSync(outputPath, output, 'utf8');
@@ -332,7 +343,7 @@ function generateAllDocs(inputFile, outputDir) {
332
343
 
333
344
  // Generate each type of documentation
334
345
  for (const [type, config] of Object.entries(PROPERTY_CONFIG)) {
335
- const count = generatePropertyDocs(properties, config, path.join(outputDir, 'pages'));
346
+ const count = generatePropertyDocs(properties, config, outputDir);
336
347
  totalProperties += count;
337
348
 
338
349
  if (type === 'broker') totalBrokerProperties = count;
@@ -342,7 +353,7 @@ function generateAllDocs(inputFile, outputDir) {
342
353
  }
343
354
 
344
355
  // Generate deprecated properties documentation
345
- const deprecatedCount = generateDeprecatedDocs(properties, path.join(outputDir, 'pages'));
356
+ const deprecatedCount = generateDeprecatedDocs(properties, outputDir);
346
357
 
347
358
  // Generate summary file
348
359
  const allPropertiesContent = Object.keys(properties).sort().join('\n');
@@ -117,6 +117,24 @@ def process_enterprise_value(enterprise_str):
117
117
  """
118
118
  enterprise_str = enterprise_str.strip()
119
119
 
120
+ # Handle special SASL mechanism function names
121
+ if enterprise_str == "is_enterprise_sasl_mechanism":
122
+ # Dynamically look up enterprise SASL mechanisms from source
123
+ enterprise_mechanisms = get_enterprise_sasl_mechanisms()
124
+ if enterprise_mechanisms:
125
+ return enterprise_mechanisms
126
+ else:
127
+ # Fallback to known values if lookup fails
128
+ return ["GSSAPI", "OAUTHBEARER"]
129
+ elif enterprise_str == "is_enterprise_sasl_mechanisms_override":
130
+ # Get the enterprise mechanisms dynamically for a more accurate description
131
+ enterprise_mechanisms = get_enterprise_sasl_mechanisms()
132
+ if enterprise_mechanisms:
133
+ mechanism_list = ", ".join(enterprise_mechanisms)
134
+ return f"Any override containing enterprise mechanisms ({mechanism_list})."
135
+ else:
136
+ return "Any override containing enterprise mechanisms."
137
+
120
138
  # FIRST: Handle std::vector initialization patterns (highest priority)
121
139
  # This must come before enum processing because vectors can contain enums
122
140
  # Tolerate optional whitespace around braces
@@ -196,19 +214,15 @@ def process_enterprise_value(enterprise_str):
196
214
 
197
215
  def resolve_cpp_function_call(function_name):
198
216
  """
199
- Resolve certain small, known C++ zero-argument functions to their literal return values by searching Redpanda source files.
217
+ Resolve a small set of known zero-argument C++ functions to their literal string return values by scanning a local Redpanda source tree.
200
218
 
201
- This function looks up predefined search patterns for well-known functions (currently a small set under `model::*`), locates a local Redpanda source tree from several commonly used paths, and scans the listed files (and, if needed, the broader model directory) for a regex match that captures the string returned by the function. If a match is found the captured string is returned; if the source tree cannot be found or no match is located the function returns None.
219
+ Searches predefined files and regex patterns for the specified fully-qualified function name (e.g., "model::kafka_audit_logging_topic") and returns the captured string if found; returns None when no match or when the Redpanda source tree cannot be located.
202
220
 
203
221
  Parameters:
204
- function_name (str): Fully-qualified C++ function name to resolve (e.g., "model::kafka_audit_logging_topic").
222
+ function_name (str): Fully-qualified C++ function name to resolve.
205
223
 
206
224
  Returns:
207
- str or None: The resolved literal string returned by the C++ function, or None when unresolved (source not found or no matching pattern).
208
-
209
- Notes:
210
- - The function performs filesystem I/O and regex-based source searching; it does not raise on read errors but logs and continues.
211
- - Only a small, hard-coded set of function names/patterns is supported; unknown names immediately return None.
225
+ str or None: The literal string returned by the C++ function when resolved, or `None` if unresolved.
212
226
  """
213
227
  # Map function names to likely search patterns and file locations
214
228
  search_patterns = {
@@ -322,7 +336,174 @@ def resolve_cpp_function_call(function_name):
322
336
  return None
323
337
 
324
338
 
339
+ def resolve_constexpr_identifier(identifier):
340
+ """
341
+ Resolve a constexpr identifier from Redpanda source code to its literal string value.
342
+
343
+ Searches common Redpanda source locations for constexpr string or string_view definitions matching the given identifier and returns the literal if found.
344
+
345
+ Parameters:
346
+ identifier (str): The identifier name to resolve (e.g., "scram").
347
+
348
+ Returns:
349
+ str or None: The resolved literal string value if found, otherwise `None`.
350
+ """
351
+ # Try to find the Redpanda source directory
352
+ redpanda_source_paths = [
353
+ 'tmp/redpanda', # Current directory
354
+ '../tmp/redpanda', # Parent directory
355
+ 'tools/property-extractor/tmp/redpanda', # From project root
356
+ os.path.join(os.getcwd(), 'tools', 'property-extractor', 'tmp', 'redpanda')
357
+ ]
358
+
359
+ redpanda_source = None
360
+ for path in redpanda_source_paths:
361
+ if os.path.exists(path):
362
+ redpanda_source = path
363
+ break
364
+
365
+ if not redpanda_source:
366
+ logger.debug(f"Could not find Redpanda source directory to resolve identifier: {identifier}")
367
+ return None
368
+
369
+ # Pattern to match constexpr string_view definitions
370
+ # Matches: inline constexpr std::string_view scram{"SCRAM"};
371
+ patterns = [
372
+ rf'inline\s+constexpr\s+std::string_view\s+{re.escape(identifier)}\s*\{{\s*"([^"]+)"\s*\}}',
373
+ rf'constexpr\s+std::string_view\s+{re.escape(identifier)}\s*\{{\s*"([^"]+)"\s*\}}',
374
+ rf'inline\s+constexpr\s+auto\s+{re.escape(identifier)}\s*=\s*"([^"]+)"',
375
+ rf'constexpr\s+auto\s+{re.escape(identifier)}\s*=\s*"([^"]+)"',
376
+ rf'static\s+constexpr\s+std::string_view\s+{re.escape(identifier)}\s*\{{\s*"([^"]+)"\s*\}}',
377
+ rf'static\s+inline\s+constexpr\s+std::string_view\s+{re.escape(identifier)}\s*\{{\s*"([^"]+)"\s*\}}',
378
+ ]
379
+
380
+ # Search recursively through the config directory and other common locations
381
+ search_dirs = [
382
+ os.path.join(redpanda_source, 'src', 'v', 'config'),
383
+ os.path.join(redpanda_source, 'src', 'v', 'kafka'),
384
+ os.path.join(redpanda_source, 'src', 'v', 'security'),
385
+ os.path.join(redpanda_source, 'src', 'v', 'pandaproxy'),
386
+ ]
387
+
388
+ for search_dir in search_dirs:
389
+ if not os.path.exists(search_dir):
390
+ continue
391
+
392
+ # Walk through the directory recursively
393
+ for root, dirs, files in os.walk(search_dir):
394
+ for file in files:
395
+ # Check both .h and .cc files since definitions can be in either
396
+ if file.endswith(('.h', '.cc', '.hpp', '.cpp')):
397
+ file_path = os.path.join(root, file)
398
+ try:
399
+ with open(file_path, 'r', encoding='utf-8') as f:
400
+ content = f.read()
401
+
402
+ # Try each pattern
403
+ for pattern in patterns:
404
+ match = re.search(pattern, content, re.MULTILINE)
405
+ if match:
406
+ resolved_value = match.group(1)
407
+ logger.debug(f"Resolved identifier '{identifier}' -> '{resolved_value}' from {file_path}")
408
+ return resolved_value
409
+
410
+ except (FileNotFoundError, PermissionError, OSError, UnicodeDecodeError) as e:
411
+ logger.debug(f"Error reading {file_path}: {e}")
412
+ continue
413
+
414
+ logger.debug(f"Could not resolve identifier: {identifier}")
415
+ return None
416
+
417
+
418
+ def get_enterprise_sasl_mechanisms():
419
+ """
420
+ Locate and resolve enterprise SASL mechanisms declared in Redpanda's sasl_mechanisms.h.
421
+
422
+ Searches known Redpanda source locations for an inline constexpr definition of enterprise_sasl_mechanisms,
423
+ extracts the identifiers, and resolves each identifier to its literal string value where possible; unresolved
424
+ identifiers are converted to an uppercase fallback.
425
+
426
+ Returns:
427
+ list or None: List of enterprise SASL mechanism strings (e.g., ["GSSAPI", "OAUTHBEARER"]),
428
+ or `None` if the lookup fails.
429
+ """
430
+ # Try to find the Redpanda source directory
431
+ redpanda_source_paths = [
432
+ 'tmp/redpanda', # Current directory
433
+ '../tmp/redpanda', # Parent directory
434
+ 'tools/property-extractor/tmp/redpanda', # From project root
435
+ os.path.join(os.getcwd(), 'tools', 'property-extractor', 'tmp', 'redpanda')
436
+ ]
437
+
438
+ redpanda_source = None
439
+ for path in redpanda_source_paths:
440
+ if os.path.exists(path):
441
+ redpanda_source = path
442
+ break
443
+
444
+ if not redpanda_source:
445
+ logger.debug("Could not find Redpanda source directory to resolve enterprise SASL mechanisms")
446
+ return None
447
+
448
+ # Look for the enterprise_sasl_mechanisms definition in sasl_mechanisms.h
449
+ sasl_mechanisms_file = os.path.join(redpanda_source, 'src', 'v', 'config', 'sasl_mechanisms.h')
450
+
451
+ if not os.path.exists(sasl_mechanisms_file):
452
+ logger.debug(f"sasl_mechanisms.h not found at {sasl_mechanisms_file}")
453
+ return None
454
+
455
+ try:
456
+ with open(sasl_mechanisms_file, 'r', encoding='utf-8') as f:
457
+ content = f.read()
458
+
459
+ # Pattern to match the enterprise_sasl_mechanisms array definition
460
+ # inline constexpr auto enterprise_sasl_mechanisms = std::to_array<std::string_view>({gssapi, oauthbearer});
461
+ pattern = r'inline\s+constexpr\s+auto\s+enterprise_sasl_mechanisms\s*=\s*std::to_array<[^>]+>\s*\(\s*\{\s*([^}]+)\s*\}\s*\)'
462
+
463
+ match = re.search(pattern, content, re.MULTILINE | re.DOTALL)
464
+ if match:
465
+ # Extract the identifiers from the array (e.g., "gssapi, oauthbearer")
466
+ identifiers_str = match.group(1).strip()
467
+
468
+ # Split by comma and clean up whitespace
469
+ identifiers = [id.strip() for id in identifiers_str.split(',') if id.strip()]
470
+
471
+ # Resolve each identifier to its actual string value
472
+ mechanisms = []
473
+ for identifier in identifiers:
474
+ resolved_value = resolve_constexpr_identifier(identifier)
475
+ if resolved_value:
476
+ mechanisms.append(resolved_value)
477
+ else:
478
+ logger.debug(f"Could not resolve SASL mechanism identifier: {identifier}")
479
+ # Fallback: use the identifier name in uppercase
480
+ mechanisms.append(identifier.upper())
481
+
482
+ if mechanisms:
483
+ logger.debug(f"Resolved enterprise SASL mechanisms: {mechanisms}")
484
+ return mechanisms
485
+ else:
486
+ logger.debug("Could not find enterprise_sasl_mechanisms definition in sasl_mechanisms.h")
487
+ return None
488
+
489
+ except (OSError, UnicodeDecodeError, re.error) as e:
490
+ logger.debug(f"Error reading {sasl_mechanisms_file}: {e}")
491
+ return None
492
+
493
+
325
494
  def validate_paths(options):
495
+ """
496
+ Validate that required file-system paths referenced by `options` exist and exit the process on failure.
497
+
498
+ Checks:
499
+ - Verifies `options.path` exists; logs an error and exits with status code 1 if it does not.
500
+ - If `options.definitions` is provided, verifies that file exists; logs an error and exits with status code 1 if it does not.
501
+
502
+ Parameters:
503
+ options: An object with at least the attributes:
504
+ - path (str): Path to the input source directory or file.
505
+ - definitions (Optional[str]): Path to the type definitions file (may be None or empty).
506
+ """
326
507
  path = options.path
327
508
 
328
509
  if not os.path.exists(path):
@@ -713,21 +894,16 @@ def add_config_scope(properties):
713
894
 
714
895
  def resolve_type_and_default(properties, definitions):
715
896
  """
716
- Resolve JSON Schema types and expand C++-style default values for all properties.
897
+ Normalize property types and expand C++-style default values into JSON-compatible Python structures.
717
898
 
718
- This function:
719
- - Resolves type references found in `properties` against `definitions` (supports "$ref" and direct type names) and normalizes property "type" to a JSON Schema primitive ("object", "string", "integer", "boolean", "array", "number") with sensible fallbacks.
720
- - Expands C++ constructor/initializer syntax and common C++ patterns appearing in default values into JSON-compatible Python values (e.g., nested constructor calls -> dicts, initializer lists -> lists, `std::nullopt` -> None, enum-like tokens -> strings).
721
- - Ensures array-typed properties (including one_or_many_property cases) have array defaults: single-object defaults are wrapped into a one-element list and "{}" string defaults become [].
722
- - Updates array item type information when item types reference definitions.
723
- - Applies a final pass to convert any remaining C++-patterned defaults and to transform any `enterprise_value` strings via process_enterprise_value.
899
+ This function resolves type references in each property against the provided definitions (supports "$ref" and direct type names), normalizes property "type" to a JSON Schema primitive when possible, expands C++ constructor/initializer and common C++ literal patterns found in "default" values into Python primitives/objects/lists, ensures array-typed properties have array defaults (including handling one_or_many_property cases), updates array item type information when item types reference definitions, and converts any `enterprise_value` strings via process_enterprise_value.
724
900
 
725
901
  Parameters:
726
- properties (dict): Mapping of property names to property metadata dictionaries. Each property may include keys like "type", "default", "items", and "enterprise_value".
727
- definitions (dict): Mapping of type names to JSON Schema definition dictionaries used to resolve $ref targets and to infer property shapes when expanding constructors.
902
+ properties (dict): Mapping of property names to metadata dictionaries. Relevant keys that may be modified include "type", "default", "items", and "enterprise_value".
903
+ definitions (dict): Mapping of definition names to JSON Schema definition dictionaries used to resolve $ref targets and to infer shapes for expanding constructor-style defaults.
728
904
 
729
905
  Returns:
730
- dict: The same `properties` mapping after in-place normalization and expansion of types and defaults.
906
+ dict: The same `properties` mapping after in-place normalization and expansion of types, defaults, item types, and enterprise values.
731
907
  """
732
908
  import ast
733
909
  import re
@@ -742,20 +918,42 @@ def resolve_type_and_default(properties, definitions):
742
918
  return defn
743
919
 
744
920
  def parse_constructor(s):
745
- """Parse C++ constructor syntax into type name and arguments."""
921
+ """
922
+ Parse a C++-style constructor or initializer expression into its type name and argument list.
923
+
924
+ Parses input forms such as `Type(arg1, arg2)`, `Type{arg1, arg2}`, or plain literals/enum-like tokens. For string literals the returned argument is a Python string value; for integer literals the returned argument is an int. Nested constructors and nested brace/paren groups are preserved as argument tokens.
925
+
926
+ Parameters:
927
+ s (str): The C++ expression to parse.
928
+
929
+ Returns:
930
+ tuple:
931
+ - type_name (str|None): The parsed type name for constructor forms, or `None` when `s` is a primitive literal or enum-like token.
932
+ - args (list): A list of argument tokens; tokens are raw strings for complex/nested arguments, Python `str` for quoted string literals, or `int` for integer literals.
933
+ """
746
934
  s = s.strip()
935
+ original_s = s
747
936
  if s.startswith("{") and s.endswith("}"):
748
937
  s = s[1:-1].strip()
938
+
939
+ # Try parentheses syntax first: type_name(args)
749
940
  match = re.match(r'([a-zA-Z0-9_:]+)\((.*)\)', s)
750
- if not match:
751
- # Primitive or enum
752
- if s.startswith('"') and s.endswith('"'):
753
- return None, [ast.literal_eval(s)]
754
- try:
755
- return None, [int(s)]
756
- except Exception:
757
- return None, [s]
758
- type_name, arg_str = match.groups()
941
+ if match:
942
+ type_name, arg_str = match.groups()
943
+ else:
944
+ # Try curly brace syntax: type_name{args}
945
+ match = re.match(r'([a-zA-Z0-9_:]+)\{(.*)\}', s)
946
+ if match:
947
+ type_name, arg_str = match.groups()
948
+ else:
949
+ # Primitive or enum
950
+ if s.startswith('"') and s.endswith('"'):
951
+ return None, [ast.literal_eval(s)]
952
+ try:
953
+ return None, [int(s)]
954
+ except ValueError:
955
+ return None, [s]
956
+
759
957
  args = []
760
958
  depth = 0
761
959
  current = ''
@@ -768,9 +966,9 @@ def resolve_type_and_default(properties, definitions):
768
966
  args.append(current.strip())
769
967
  current = ''
770
968
  else:
771
- if c == '(' and not in_string:
969
+ if c in '({' and not in_string:
772
970
  depth += 1
773
- elif c == ')' and not in_string:
971
+ elif c in ')}' and not in_string:
774
972
  depth -= 1
775
973
  current += c
776
974
  if current.strip():
@@ -779,13 +977,18 @@ def resolve_type_and_default(properties, definitions):
779
977
 
780
978
  def process_cpp_patterns(arg_str):
781
979
  """
782
- Process specific C++ patterns to user-friendly values.
980
+ Convert a C++-style expression string into a JSON-friendly literal representation.
783
981
 
784
- Handles:
785
- - net::unresolved_address("127.0.0.1", 9092) -> expands based on type definition
982
+ This function recognises common C++ patterns produced by the extractor and maps them to values suitable for JSON schema defaults and examples. Handled cases include:
786
983
  - std::nullopt -> null
787
- - fips_mode_flag::disabled -> "disabled"
788
- - model::kafka_audit_logging_topic() -> dynamically looked up from source
984
+ - zero-argument functions (e.g., model::kafka_audit_logging_topic()) resolved from source when possible
985
+ - enum tokens (e.g., fips_mode_flag::disabled -> "disabled")
986
+ - constexpr identifiers and simple string constructors resolved to their literal strings when available
987
+ - known default constructors and truncated type names mapped to sensible defaults (e.g., duration -> 0, path -> "")
988
+ - simple heuristics for unknown constructors and concatenated expressions
989
+
990
+ Returns:
991
+ processed (str): A string representing the JSON-ready value (for example: '"value"', 'null', '0', or the original input when no mapping applied).
789
992
  """
790
993
  arg_str = arg_str.strip()
791
994
 
@@ -808,6 +1011,24 @@ def resolve_type_and_default(properties, definitions):
808
1011
  enum_value = enum_match.group(1)
809
1012
  return f'"{enum_value}"'
810
1013
 
1014
+ # Handle constexpr identifier resolution (such as scram -> "SCRAM")
1015
+ # Check if this is a simple identifier that might be a constexpr variable
1016
+ if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', arg_str):
1017
+ resolved_value = resolve_constexpr_identifier(arg_str)
1018
+ if resolved_value is not None:
1019
+ return f'"{resolved_value}"'
1020
+
1021
+ # Handle string constructor patterns like ss::sstring{identifier}
1022
+ sstring_match = re.match(r'ss::sstring\{([a-zA-Z_][a-zA-Z0-9_]*)\}', arg_str)
1023
+ if sstring_match:
1024
+ identifier = sstring_match.group(1)
1025
+ resolved_value = resolve_constexpr_identifier(identifier)
1026
+ if resolved_value is not None:
1027
+ return f'"{resolved_value}"'
1028
+ else:
1029
+ # Fallback to the identifier itself
1030
+ return f'"{identifier}"'
1031
+
811
1032
  # Handle default constructors and their default values
812
1033
  # This handles cases where C++ default constructors are used but should map to specific values
813
1034
 
@@ -861,12 +1082,23 @@ def resolve_type_and_default(properties, definitions):
861
1082
 
862
1083
  def expand_default(type_name, default_str):
863
1084
  """
864
- Expand C++ default values into structured JSON objects.
1085
+ Convert a C++-style default initializer into a JSON-serializable Python value.
865
1086
 
866
- For array types with initializer list syntax like:
867
- {model::broker_endpoint(net::unresolved_address("127.0.0.1", 9644))}
1087
+ This expands C++ constructor and initializer-list syntax into Python primitives, dictionaries, and lists suitable for JSON output. Supported transformations include:
1088
+ - String constructors and quoted literals → Python str.
1089
+ - Integer and boolean literals → Python int and bool.
1090
+ - Object constructors (Type(arg1, arg2) or Type{...}) → dict mapping constructor arguments to the object's properties when a corresponding type definition exists.
1091
+ - Nested constructors → nested dicts with their fields expanded.
1092
+ - Array initializer lists (e.g., {Type(...), Type(...)}) → Python list with each element expanded.
1093
+ - Special-case mappings for known type patterns (for example, an address-type constructor expanded into {"address", "port"} when the target type expects that shape).
1094
+ If a default cannot be resolved or the type is an enum, the original input is returned unchanged; the string "null" is converted to None. If default_str is not a string, it is returned as-is.
868
1095
 
869
- This creates: [{address: "127.0.0.1", port: 9644}]
1096
+ Parameters:
1097
+ type_name (str): The resolved type name for the default value (e.g., "model::broker_endpoint" or a primitive type like "string").
1098
+ default_str (str | any): The C++ default expression to expand, or a non-string value already decoded.
1099
+
1100
+ Returns:
1101
+ The expanded Python representation of the default: a dict for objects, a list for arrays, a primitive (str/int/bool), None for null, or the original value/string when expansion is not possible.
870
1102
  """
871
1103
  # Handle non-string defaults
872
1104
  if not isinstance(default_str, str):
@@ -883,6 +1115,19 @@ def resolve_type_and_default(properties, definitions):
883
1115
  return ast.literal_eval(processed)
884
1116
  else:
885
1117
  return processed
1118
+
1119
+ # Handle string type with constructor syntax (e.g., ss::sstring{scram})
1120
+ if type_name == "string" and ("{" in default_str or "(" in default_str):
1121
+ tname, args = parse_constructor(default_str)
1122
+ if tname and args:
1123
+ # For string constructors, resolve the first argument and return it as the string value
1124
+ first_arg = args[0] if args else ""
1125
+ # Apply C++ pattern processing to resolve identifiers
1126
+ processed_arg = process_cpp_patterns(first_arg)
1127
+ if processed_arg.startswith('"') and processed_arg.endswith('"'):
1128
+ return ast.literal_eval(processed_arg) # Remove quotes
1129
+ else:
1130
+ return processed_arg
886
1131
 
887
1132
  type_def = resolve_definition_type(definitions.get(type_name, {}))
888
1133
  if "enum" in type_def:
@@ -1619,4 +1864,4 @@ def main():
1619
1864
  sys.exit(1)
1620
1865
 
1621
1866
  if __name__ == "__main__":
1622
- main()
1867
+ main()