PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
Functions | Variables
generate_capability_inventory Namespace Reference

Functions

dict load_registry ()
 Load the capability family registry.
 
 literal (ast.AST node)
 Evaluate a literal AST node, additionally accepting the bare set() call that appears in the boundary-handler specs for empty parameter sets.
 
ast.Module module_syntax (str module)
 Parse a dotted module into one syntax tree, whether file or package.
 
dict[str, dict] python_dict_values (str module, str symbol)
 Read a public-surface dictionary from the CLI package without importing PETSc.
 
dict[str, dict] python_normalizer_values (str module, str symbol)
 Read the canonical selector strings accepted by a normalizer function.
 
str function_body (str path, str function)
 Return the source text of one C function, so extraction never spans the file.
 
dict python_membership_values (str module, str symbol)
 Read the accepted values from a normalizer that validates by set membership.
 
dict python_equality_chain_values (str module, str symbol)
 Read accepted values from a normalizer that compares against string literals.
 
dict[str, str] c_string_map_values (str path, str function)
 Extract the case-insensitive selector strings a C parser accepts.
 
dict[str, str] c_token_map_values (str path, str function, str variable, str field)
 Extract an exact-match token chain that assigns an enum to a context field.
 
set[str] c_switch_values (str path, str function, str prefix)
 Extract the enum constants a C factory switch dispatches on.
 
set[str] c_dispatch_values (str path, str function, str prefix)
 Extract the enum constants an if/else dispatch chain compares against.
 
set[str] c_enum_values (str path, str symbol)
 Extract the members of a C enum.
 
dict python_constant_values (str module, str symbol)
 Read the accepted values from a named module-level choice set.
 
dict collect (dict family)
 Build one family's inventory record from its declared sources.
 
None apply_metadata (list[dict] inventory, dict registry)
 Merge declared per-value metadata (status, alias target) into the inventory.
 
dict[str, int] classify (list[dict] inventory)
 Count selectable, alias, and latent values separately.
 
None apply_reachability (list[dict] inventory, dict registry)
 Mark declared values that no other family can actually satisfy as latent.
 
str entry_anchor (dict registry_entry, str value)
 Anchor name of the Tier-2 entry for one selector value.
 
str html_escape (str text)
 Escape text for inclusion in generated HTML.
 
set documented_entries (dict family, dict registry_entry)
 Values whose Tier-2 entry anchor is actually present on the family page.
 
str render_family (dict family, dict registry_entry)
 Render one family's value table as a Doxygen-includable HTML fragment.
 
Path family_fragment_path (str family_id)
 Path of the per-family includable fragment.
 
str render_evidence_matrix (list[dict] inventory, dict registry)
 Render the project-wide capability-by-evidence matrix as an HTML fragment.
 
str render_markdown (list[dict] inventory)
 Render the inventory as a Doxygen-includable Markdown fragment.
 
int main ()
 Generate the capability inventory artifacts.
 

Variables

 REPO_ROOT = Path(__file__).resolve().parents[2]
 
str REGISTRY_PATH = REPO_ROOT / "tests" / "tooling" / "capability_families.json"
 
str GENERATED_DIR = REPO_ROOT / "docs" / "generated"
 

Detailed Description

Extract the public capability inventory from executable sources and render it for the docs.

Function Documentation

◆ load_registry()

dict generate_capability_inventory.load_registry ( )

Load the capability family registry.

Returns
Parsed registry mapping.

Definition at line 19 of file generate_capability_inventory.py.

19def load_registry() -> dict:
20 """!
21 @brief Load the capability family registry.
22 @return Parsed registry mapping.
23 """
24 return json.loads(REGISTRY_PATH.read_text(encoding="utf-8"))
25
26
Here is the caller graph for this function:

◆ literal()

generate_capability_inventory.literal ( ast.AST  node)

Evaluate a literal AST node, additionally accepting the bare set() call that appears in the boundary-handler specs for empty parameter sets.

Parameters
[in]nodeParsed AST node.
Returns
The Python value the node denotes.

Definition at line 27 of file generate_capability_inventory.py.

27def literal(node: ast.AST):
28 """!
29 @brief Evaluate a literal AST node, additionally accepting the bare `set()` call
30 that appears in the boundary-handler specs for empty parameter sets.
31 @param[in] node Parsed AST node.
32 @return The Python value the node denotes.
33 """
34 if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == "set":
35 return set(literal(node.args[0])) if node.args else set()
36 if isinstance(node, ast.Dict):
37 return {literal(k): literal(v) for k, v in zip(node.keys, node.values)}
38 if isinstance(node, ast.Set):
39 return {literal(e) for e in node.elts}
40 if isinstance(node, (ast.List, ast.Tuple)):
41 return [literal(e) for e in node.elts]
42 return ast.literal_eval(node)
43
44
Here is the call graph for this function:
Here is the caller graph for this function:

◆ module_syntax()

ast.Module generate_capability_inventory.module_syntax ( str  module)

Parse a dotted module into one syntax tree, whether file or package.

A public surface may be a package: picurv_cli.storage exposes its constants through its __init__, but they are defined across its modules. Concatenating their bodies lets the readers below stay written against a single tree, which is what they mean by "the module".

Parameters
[in]moduleDotted module name.
Returns
Syntax tree covering every file the module resolves to.

Definition at line 45 of file generate_capability_inventory.py.

45def module_syntax(module: str) -> ast.Module:
46 """!
47 @brief Parse a dotted module into one syntax tree, whether file or package.
48
49 @details A public surface may be a package: `picurv_cli.storage` exposes its
50 constants through its `__init__`, but they are defined across its
51 modules. Concatenating their bodies lets the readers below stay written
52 against a single tree, which is what they mean by "the module".
53 @param[in] module Dotted module name.
54 @return Syntax tree covering every file the module resolves to.
55 """
56 relative = Path(*module.split("."))
57 package = REPO_ROOT / relative
58 if package.is_dir():
59 combined = ast.Module(body=[], type_ignores=[])
60 for child in sorted(package.glob("*.py")):
61 if child.name == "__init__.py":
62 continue
63 combined.body.extend(ast.parse(child.read_text(encoding="utf-8")).body)
64 return combined
65 return ast.parse((REPO_ROOT / relative.with_suffix(".py")).read_text(encoding="utf-8"))
66
67
Here is the caller graph for this function:

◆ python_dict_values()

dict[str, dict] generate_capability_inventory.python_dict_values ( str  module,
str  symbol 
)

Read a public-surface dictionary from the CLI package without importing PETSc.

Parameters
[in]moduleDotted module name.
[in]symbolModule-level dictionary name.
Returns
Mapping of selector value to its declared parameter contract.

Definition at line 68 of file generate_capability_inventory.py.

68def python_dict_values(module: str, symbol: str) -> dict[str, dict]:
69 """!
70 @brief Read a public-surface dictionary from the CLI package without importing PETSc.
71 @param[in] module Dotted module name.
72 @param[in] symbol Module-level dictionary name.
73 @return Mapping of selector value to its declared parameter contract.
74 """
75 tree = module_syntax(module)
76 for node in tree.body:
77 if not isinstance(node, ast.Assign):
78 continue
79 targets = [t.id for t in node.targets if isinstance(t, ast.Name)]
80 if symbol not in targets:
81 continue
82 raw = literal(node.value)
83 result: dict[str, dict] = {}
84 for key, value in raw.items():
85 if isinstance(value, dict):
86 result[key] = {
87 "types": sorted(value.get("types", [])),
88 "required_params": sorted(value.get("required_params", [])),
89 "optional_params": sorted(value.get("optional_params", [])),
90 }
91 else:
92 result[key] = {"maps_to": value}
93 return result
94 raise RuntimeError(f"{symbol} not found in {module}")
95
96
Here is the call graph for this function:
Here is the caller graph for this function:

◆ python_normalizer_values()

dict[str, dict] generate_capability_inventory.python_normalizer_values ( str  module,
str  symbol 
)

Read the canonical selector strings accepted by a normalizer function.

Parameters
[in]moduleDotted module name.
[in]symbolNormalizer function name.
Returns
Mapping of canonical value to the runtime token it maps to.

Definition at line 97 of file generate_capability_inventory.py.

97def python_normalizer_values(module: str, symbol: str) -> dict[str, dict]:
98 """!
99 @brief Read the canonical selector strings accepted by a normalizer function.
100 @param[in] module Dotted module name.
101 @param[in] symbol Normalizer function name.
102 @return Mapping of canonical value to the runtime token it maps to.
103 """
104 tree = module_syntax(module)
105 for node in ast.walk(tree):
106 if isinstance(node, ast.FunctionDef) and node.name == symbol:
107 for inner in ast.walk(node):
108 if isinstance(inner, ast.Dict) and inner.keys:
109 try:
110 mapping = ast.literal_eval(inner)
111 except ValueError:
112 continue
113 if all(isinstance(k, str) for k in mapping):
114 return {k: {"maps_to": v} for k, v in mapping.items()}
115 raise RuntimeError(f"no canonical mapping found in {module}.{symbol}")
116
117
Here is the call graph for this function:
Here is the caller graph for this function:

◆ function_body()

str generate_capability_inventory.function_body ( str  path,
str  function 
)

Return the source text of one C function, so extraction never spans the file.

Parameters
[in]pathRepository-relative C source path.
[in]functionFunction name to isolate.
Returns
Source text of that function body.
Exceptions
RuntimeErrorwhen the function cannot be located.

Definition at line 118 of file generate_capability_inventory.py.

118def function_body(path: str, function: str) -> str:
119 """!
120 @brief Return the source text of one C function, so extraction never spans the file.
121 @param[in] path Repository-relative C source path.
122 @param[in] function Function name to isolate.
123 @return Source text of that function body.
124 @throws RuntimeError when the function cannot be located.
125 """
126 text = (REPO_ROOT / path).read_text(encoding="utf-8")
127 match = re.search(rf"^[A-Za-z_][\w \*]*\b{re.escape(function)}\s*\‍(", text, re.M)
128 if not match:
129 raise RuntimeError(f"function {function} not found in {path}")
130 index = text.index("{", match.end() - 1)
131 depth = 0
132 for offset in range(index, len(text)):
133 if text[offset] == "{":
134 depth += 1
135 elif text[offset] == "}":
136 depth -= 1
137 if depth == 0:
138 return text[index : offset + 1]
139 raise RuntimeError(f"unbalanced braces while reading {function} in {path}")
140
141
Here is the caller graph for this function:

◆ python_membership_values()

dict generate_capability_inventory.python_membership_values ( str  module,
str  symbol 
)

Read the accepted values from a normalizer that validates by set membership.

Some normalizers check if value not in {...} rather than mapping through a dict. The accepted set is still the public surface, so it is extracted the same way rather than being hand-listed.

Parameters
[in]moduleDotted module name.
[in]symbolNormalizer function name.
Returns
Mapping of accepted value to the token it resolves to.

Definition at line 142 of file generate_capability_inventory.py.

142def python_membership_values(module: str, symbol: str) -> dict:
143 """!
144 @brief Read the accepted values from a normalizer that validates by set membership.
145
146 @details Some normalizers check `if value not in {...}` rather than mapping through a
147 dict. The accepted set is still the public surface, so it is extracted the
148 same way rather than being hand-listed.
149 @param[in] module Dotted module name.
150 @param[in] symbol Normalizer function name.
151 @return Mapping of accepted value to the token it resolves to.
152 """
153 tree = module_syntax(module)
154 for node in ast.walk(tree):
155 if not (isinstance(node, ast.FunctionDef) and node.name == symbol):
156 continue
157 collected: dict = {}
158 for inner in ast.walk(node):
159 if isinstance(inner, ast.Set):
160 try:
161 members = {literal(element) for element in inner.elts}
162 except (ValueError, TypeError):
163 continue
164 if all(isinstance(member, str) for member in members):
165 for member in members:
166 collected.setdefault(member, {"maps_to": member})
167 if collected:
168 return collected
169 raise RuntimeError(f"no membership set found in {module}.{symbol}")
170
171
Here is the call graph for this function:
Here is the caller graph for this function:

◆ python_equality_chain_values()

dict generate_capability_inventory.python_equality_chain_values ( str  module,
str  symbol 
)

Read accepted values from a normalizer that compares against string literals.

A third normalizer shape: if normalized == "ucat": ... elif ... == "ucont". The compared literals are the public surface, so they are extracted rather than hand-listed, keeping the inventory tied to the code.

Parameters
[in]moduleDotted module name.
[in]symbolNormalizer function name.
Returns
Mapping of accepted value to itself.

Definition at line 172 of file generate_capability_inventory.py.

172def python_equality_chain_values(module: str, symbol: str) -> dict:
173 """!
174 @brief Read accepted values from a normalizer that compares against string literals.
175
176 @details A third normalizer shape: `if normalized == "ucat": ... elif ... == "ucont"`.
177 The compared literals are the public surface, so they are extracted rather
178 than hand-listed, keeping the inventory tied to the code.
179 @param[in] module Dotted module name.
180 @param[in] symbol Normalizer function name.
181 @return Mapping of accepted value to itself.
182 """
183 tree = module_syntax(module)
184 for node in ast.walk(tree):
185 if not (isinstance(node, ast.FunctionDef) and node.name == symbol):
186 continue
187 collected: dict = {}
188 for inner in ast.walk(node):
189 if not isinstance(inner, ast.Compare):
190 continue
191 if not any(isinstance(op, (ast.Eq, ast.NotEq)) for op in inner.ops):
192 continue
193 for comparator in inner.comparators:
194 if isinstance(comparator, ast.Constant) and isinstance(comparator.value, str):
195 if comparator.value:
196 collected.setdefault(comparator.value, {"maps_to": comparator.value})
197 if collected:
198 return collected
199 raise RuntimeError(f"no equality chain found in {module}.{symbol}")
200
201
Here is the call graph for this function:
Here is the caller graph for this function:

◆ c_string_map_values()

dict[str, str] generate_capability_inventory.c_string_map_values ( str  path,
str  function 
)

Extract the case-insensitive selector strings a C parser accepts.

Parameters
[in]pathRepository-relative C source path.
[in]functionParser function name.
Returns
Mapping of accepted selector string to the enum constant it selects.

Definition at line 202 of file generate_capability_inventory.py.

202def c_string_map_values(path: str, function: str) -> dict[str, str]:
203 """!
204 @brief Extract the case-insensitive selector strings a C parser accepts.
205 @param[in] path Repository-relative C source path.
206 @param[in] function Parser function name.
207 @return Mapping of accepted selector string to the enum constant it selects.
208 """
209 body = function_body(path, function)
210 pairs = re.findall(
211 r'strcasecmp\‍(\s*str\s*,\s*"([^"]+)"\s*\‍)\s*==\s*0\s*\‍)\s*\*handler_out\s*=\s*([A-Z][A-Z0-9_]+)',
212 body,
213 )
214 return dict(pairs)
215
216
Here is the call graph for this function:
Here is the caller graph for this function:

◆ c_token_map_values()

dict[str, str] generate_capability_inventory.c_token_map_values ( str  path,
str  function,
str  variable,
str  field 
)

Extract an exact-match token chain that assigns an enum to a context field.

Handles the strcmp(buf, "TOKEN") == 0 ... field = ENUM; shape used for generated PETSc option tokens, including chains where several tokens share one assignment (an alias arm).

Parameters
[in]pathRepository-relative C source path.
[in]functionEnclosing function name.
[in]variableName of the char buffer holding the option value.
[in]fieldAssigned context field, for example mom_solver_type.
Returns
Mapping of accepted token to the enum constant it selects.

Definition at line 217 of file generate_capability_inventory.py.

217def c_token_map_values(path: str, function: str, variable: str, field: str) -> dict[str, str]:
218 """!
219 @brief Extract an exact-match token chain that assigns an enum to a context field.
220
221 Handles the `strcmp(buf, "TOKEN") == 0 ... field = ENUM;` shape used for
222 generated PETSc option tokens, including chains where several tokens share one
223 assignment (an alias arm).
224 @param[in] path Repository-relative C source path.
225 @param[in] function Enclosing function name.
226 @param[in] variable Name of the char buffer holding the option value.
227 @param[in] field Assigned context field, for example `mom_solver_type`.
228 @return Mapping of accepted token to the enum constant it selects.
229 """
230 body = function_body(path, function)
231 mapping: dict[str, str] = {}
232 pattern = re.compile(
233 r'((?:strcmp\‍(\s*' + re.escape(variable) + r'\s*,\s*"[^"]+"\s*\‍)\s*==\s*0\s*\|?\|?\s*)+)'
234 r"[^{]*\{[^}]*?\b" + re.escape(field) + r"\s*=\s*([A-Z][A-Z0-9_]+)\s*;",
235 re.S,
236 )
237 for arm, enum in pattern.findall(body):
238 for token in re.findall(r'"([^"]+)"', arm):
239 mapping[token] = enum
240 return mapping
241
242
Here is the call graph for this function:
Here is the caller graph for this function:

◆ c_switch_values()

set[str] generate_capability_inventory.c_switch_values ( str  path,
str  function,
str  prefix 
)

Extract the enum constants a C factory switch dispatches on.

Parameters
[in]pathRepository-relative C source path.
[in]functionEnclosing function name.
[in]prefixEnum constant prefix.
Returns
Set of dispatched enum constants.

Definition at line 243 of file generate_capability_inventory.py.

243def c_switch_values(path: str, function: str, prefix: str) -> set[str]:
244 """!
245 @brief Extract the enum constants a C factory switch dispatches on.
246 @param[in] path Repository-relative C source path.
247 @param[in] function Enclosing function name.
248 @param[in] prefix Enum constant prefix.
249 @return Set of dispatched enum constants.
250 """
251 body = function_body(path, function)
252 return set(re.findall(rf"case\s+({re.escape(prefix)}[A-Z0-9_]+)\s*:", body))
253
254
Here is the call graph for this function:
Here is the caller graph for this function:

◆ c_dispatch_values()

set[str] generate_capability_inventory.c_dispatch_values ( str  path,
str  function,
str  prefix 
)

Extract the enum constants an if/else dispatch chain compares against.

Parameters
[in]pathRepository-relative C source path.
[in]functionEnclosing function name.
[in]prefixEnum constant prefix.
Returns
Set of enum constants the dispatch acts on.

Definition at line 255 of file generate_capability_inventory.py.

255def c_dispatch_values(path: str, function: str, prefix: str) -> set[str]:
256 """!
257 @brief Extract the enum constants an if/else dispatch chain compares against.
258 @param[in] path Repository-relative C source path.
259 @param[in] function Enclosing function name.
260 @param[in] prefix Enum constant prefix.
261 @return Set of enum constants the dispatch acts on.
262 """
263 body = function_body(path, function)
264 return set(re.findall(rf"==\s*({re.escape(prefix)}[A-Z0-9_]+)", body))
265
266
Here is the call graph for this function:
Here is the caller graph for this function:

◆ c_enum_values()

set[str] generate_capability_inventory.c_enum_values ( str  path,
str  symbol 
)

Extract the members of a C enum.

Parameters
[in]pathRepository-relative header path.
[in]symbolEnum type name.
Returns
Set of enum member names.

Definition at line 267 of file generate_capability_inventory.py.

267def c_enum_values(path: str, symbol: str) -> set[str]:
268 """!
269 @brief Extract the members of a C enum.
270 @param[in] path Repository-relative header path.
271 @param[in] symbol Enum type name.
272 @return Set of enum member names.
273 """
274 text = (REPO_ROOT / path).read_text(encoding="utf-8")
275 match = re.search(r"typedef\s+enum\s*\{([^{}]*)\}\s*" + re.escape(symbol) + r"\s*;", text, re.S)
276 if not match:
277 raise RuntimeError(f"enum {symbol} not found in {path}")
278 # Split on commas rather than requiring one member per line: a single-line enum
279 # would otherwise be silently under-extracted, which is the failure class these
280 # extractors exist to eliminate.
281 members = set()
282 body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
283 body = re.sub(r"//[^\n]*", "", body)
284 for chunk in body.split(","):
285 name = re.match(r"\s*([A-Z][A-Z0-9_]*)\s*(?:=|$)", chunk)
286 if name:
287 members.add(name.group(1))
288 return members
289
290
Here is the caller graph for this function:

◆ python_constant_values()

dict generate_capability_inventory.python_constant_values ( str  module,
str  symbol 
)

Read the accepted values from a named module-level choice set.

The preferred shape. A choice set written as an inline literal at its point of use is invisible to the census, so the rule is that it must be a named module-level constant - a tuple, list, set, or dict of strings. A dict maps each accepted spelling to what it resolves to; a sequence maps each value to itself.

Parameters
[in]moduleDotted module name.
[in]symbolConstant name.
Returns
Mapping of accepted value to the token it resolves to.

Definition at line 291 of file generate_capability_inventory.py.

291def python_constant_values(module: str, symbol: str) -> dict:
292 """!
293 @brief Read the accepted values from a named module-level choice set.
294
295 @details The preferred shape. A choice set written as an inline literal at its point
296 of use is invisible to the census, so the rule is that it must be a named
297 module-level constant - a tuple, list, set, or dict of strings. A dict maps
298 each accepted spelling to what it resolves to; a sequence maps each value
299 to itself.
300 @param[in] module Dotted module name.
301 @param[in] symbol Constant name.
302 @return Mapping of accepted value to the token it resolves to.
303 """
304 tree = module_syntax(module)
305 for node in tree.body:
306 if not isinstance(node, ast.Assign):
307 continue
308 if not any(isinstance(target, ast.Name) and target.id == symbol
309 for target in node.targets):
310 continue
311 value = literal(node.value)
312 if isinstance(value, dict):
313 if not all(isinstance(k, str) for k in value):
314 raise RuntimeError(f"{module}.{symbol} is not keyed by strings")
315 # A dict of strings declares spellings and what each resolves to. A dict
316 # whose values are anything else - a per-value specification, say - declares
317 # the keys as the choice set and carries its own detail alongside.
318 if all(isinstance(v, str) for v in value.values()):
319 return {k: {"maps_to": v} for k, v in value.items()}
320 return {k: {"maps_to": k} for k in value}
321 if isinstance(value, (tuple, list, set, frozenset)):
322 members = list(value)
323 if not all(isinstance(member, str) for member in members):
324 raise RuntimeError(f"{module}.{symbol} is not a sequence of strings")
325 return {member: {"maps_to": member} for member in members}
326 raise RuntimeError(f"{module}.{symbol} is not a choice set")
327 raise RuntimeError(f"no module-level constant {symbol} in {module}")
328
329
Head of a generic C-style linked list.
Definition variables.h:475
Here is the call graph for this function:
Here is the caller graph for this function:

◆ collect()

dict generate_capability_inventory.collect ( dict  family)

Build one family's inventory record from its declared sources.

Parameters
[in]familyFamily registry entry.
Returns
Inventory record for the family.

Definition at line 330 of file generate_capability_inventory.py.

330def collect(family: dict) -> dict:
331 """!
332 @brief Build one family's inventory record from its declared sources.
333 @param[in] family Family registry entry.
334 @return Inventory record for the family.
335 """
336 surface = family["public_surface"]
337 if surface["kind"] == "python_dict":
338 values = python_dict_values(surface["module"], surface["symbol"])
339 elif surface["kind"] == "python_normalizer":
340 values = python_normalizer_values(surface["module"], surface["symbol"])
341 elif surface["kind"] == "python_membership":
342 values = python_membership_values(surface["module"], surface["symbol"])
343 elif surface["kind"] == "python_equality_chain":
344 values = python_equality_chain_values(surface["module"], surface["symbol"])
345 elif surface["kind"] == "python_constant":
346 values = python_constant_values(surface["module"], surface["symbol"])
347 else:
348 raise RuntimeError(f"unknown public_surface kind: {surface['kind']}")
349
350 parity = []
351 for source in family.get("parity_sources", []):
352 kind = source["kind"]
353 if kind == "c_string_map":
354 found = c_string_map_values(source["path"], source["function"])
355 elif kind == "c_token_map":
356 found = c_token_map_values(
357 source["path"], source["function"], source["variable"], source["field"]
358 )
359 elif kind == "c_switch":
360 found = c_switch_values(source["path"], source["function"], source["prefix"])
361 elif kind == "c_dispatch":
362 found = c_dispatch_values(source["path"], source["function"], source["prefix"])
363 elif kind == "c_enum":
364 found = c_enum_values(source["path"], source["symbol"])
365 else:
366 raise RuntimeError(f"unknown parity source kind: {kind}")
367 if isinstance(found, dict):
368 parity.append({"source": source, "values": sorted(found), "mapping": found})
369 else:
370 parity.append({"source": source, "values": sorted(found)})
371
372 return {
373 "id": family["id"],
374 "title": family["title"],
375 "selector": family["selector"],
376 "family_page": family["family_page"],
377 "public_values": values,
378 "parity": parity,
379 }
380
381
Here is the call graph for this function:
Here is the caller graph for this function:

◆ apply_metadata()

None generate_capability_inventory.apply_metadata ( list[dict]  inventory,
dict  registry 
)

Merge declared per-value metadata (status, alias target) into the inventory.

Parameters
[in,out]inventoryCollected family records.
[in]registryParsed registry mapping.
Returns
None.

Definition at line 382 of file generate_capability_inventory.py.

382def apply_metadata(inventory: list[dict], registry: dict) -> None:
383 """!
384 @brief Merge declared per-value metadata (status, alias target) into the inventory.
385 @param[in,out] inventory Collected family records.
386 @param[in] registry Parsed registry mapping.
387 @return None.
388 """
389 entries = {entry["id"]: entry for entry in registry["families"]}
390 for family in inventory:
391 declared = entries[family["id"]].get("value_metadata", {})
392 for name, spec in family["public_values"].items():
393 meta = declared.get(name, {})
394 if meta.get("alias_of"):
395 spec["alias_of"] = meta["alias_of"]
396 if meta.get("spelling_of"):
397 spec["spelling_of"] = meta["spelling_of"]
398 # A spelling has no status of its own: it inherits the canonical value's,
399 # so a defective capability cannot look supported under another name. A
400 # deprecated alias keeps its own status - being retired is a fact about the
401 # alias, not about what it resolves to.
402 spelling_target = meta.get("spelling_of")
403 if spelling_target:
404 spec["status"] = declared.get(spelling_target, {}).get("status", "supported")
405 elif meta.get("status"):
406 spec["status"] = meta["status"]
407
408
Here is the caller graph for this function:

◆ classify()

dict[str, int] generate_capability_inventory.classify ( list[dict]  inventory)

Count selectable, alias, and latent values separately.

A single total conflates three different things: what a user can choose, what is only kept readable for old configs, and what is declared but unreachable.

Parameters
[in]inventoryCollected family records.
Returns
Mapping of category to count.

Definition at line 409 of file generate_capability_inventory.py.

409def classify(inventory: list[dict]) -> dict[str, int]:
410 """!
411 @brief Count selectable, alias, and latent values separately.
412
413 A single total conflates three different things: what a user can choose, what is
414 only kept readable for old configs, and what is declared but unreachable.
415 @param[in] inventory Collected family records.
416 @return Mapping of category to count.
417 """
418 counts = {"selectable": 0, "spelling": 0, "alias": 0, "latent": 0}
419 for family in inventory:
420 for spec in family["public_values"].values():
421 if spec.get("reachability") == "latent":
422 counts["latent"] += 1
423 elif spec.get("alias_of"):
424 counts["alias"] += 1
425 elif spec.get("spelling_of"):
426 counts["spelling"] += 1
427 else:
428 counts["selectable"] += 1
429 return counts
430
431
Here is the caller graph for this function:

◆ apply_reachability()

None generate_capability_inventory.apply_reachability ( list[dict]  inventory,
dict  registry 
)

Mark declared values that no other family can actually satisfy as latent.

A boundary type is only selectable if some public handler accepts it. Listing a type no handler supports advertises a capability every complete configuration would be rejected for.

Parameters
[in,out]inventoryCollected family records.
[in]registryParsed registry mapping.
Returns
None.

Definition at line 432 of file generate_capability_inventory.py.

432def apply_reachability(inventory: list[dict], registry: dict) -> None:
433 """!
434 @brief Mark declared values that no other family can actually satisfy as latent.
435
436 A boundary type is only selectable if some public handler accepts it. Listing a
437 type no handler supports advertises a capability every complete configuration
438 would be rejected for.
439 @param[in,out] inventory Collected family records.
440 @param[in] registry Parsed registry mapping.
441 @return None.
442 """
443 by_id = {family["id"]: family for family in inventory}
444 for entry in registry["families"]:
445 spec = entry.get("reachable_from")
446 if not spec:
447 continue
448 provider = by_id.get(spec["family"])
449 target = by_id.get(entry["id"])
450 if provider is None or target is None:
451 raise RuntimeError(f"reachable_from names an unknown family: {spec['family']}")
452 reachable: set[str] = set()
453 for value in provider["public_values"].values():
454 reachable.update(value.get(spec["field"], []))
455 for name, value in target["public_values"].items():
456 resolved = value.get("maps_to", name)
457 # Reachability is an independent axis. Overwriting `status` here destroyed
458 # the declared lifecycle, which silently disabled lifecycle enforcement for
459 # every family using reachable_from.
460 is_reachable = resolved in reachable
461 value["reachable"] = is_reachable
462 value["reachability"] = "reachable" if is_reachable else "latent"
463
464
Here is the caller graph for this function:

◆ entry_anchor()

str generate_capability_inventory.entry_anchor ( dict  registry_entry,
str  value 
)

Anchor name of the Tier-2 entry for one selector value.

Parameters
[in]registry_entryRegistry entry carrying the anchor prefix.
[in]valuePublic selector value.
Returns
Anchor name.

Definition at line 465 of file generate_capability_inventory.py.

465def entry_anchor(registry_entry: dict, value: str) -> str:
466 """!
467 @brief Anchor name of the Tier-2 entry for one selector value.
468 @param[in] registry_entry Registry entry carrying the anchor prefix.
469 @param[in] value Public selector value.
470 @return Anchor name.
471 """
472 return registry_entry["entry_anchor_prefix"] + re.sub(r"[^a-z0-9]+", "_", value.lower()).strip("_")
473
474
Here is the caller graph for this function:

◆ html_escape()

str generate_capability_inventory.html_escape ( str  text)

Escape text for inclusion in generated HTML.

Parameters
[in]textRaw text.
Returns
Escaped text.

Definition at line 475 of file generate_capability_inventory.py.

475def html_escape(text: str) -> str:
476 """!
477 @brief Escape text for inclusion in generated HTML.
478 @param[in] text Raw text.
479 @return Escaped text.
480 """
481 return text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
482
483
Here is the caller graph for this function:

◆ documented_entries()

set generate_capability_inventory.documented_entries ( dict  family,
dict  registry_entry 
)

Values whose Tier-2 entry anchor is actually present on the family page.

Generated tables must not link to an entry that does not exist: a deferred or latent value has no anchor, and a dead in-page link is worse than plain text.

Parameters
[in]familyCollected family record.
[in]registry_entryRegistry entry naming the family page.
Returns
Set of value names that have an entry.

Definition at line 484 of file generate_capability_inventory.py.

484def documented_entries(family: dict, registry_entry: dict) -> set:
485 """!
486 @brief Values whose Tier-2 entry anchor is actually present on the family page.
487
488 Generated tables must not link to an entry that does not exist: a deferred or
489 latent value has no anchor, and a dead in-page link is worse than plain text.
490 @param[in] family Collected family record.
491 @param[in] registry_entry Registry entry naming the family page.
492 @return Set of value names that have an entry.
493 """
494 page = REPO_ROOT / "docs" / "pages" / f"{registry_entry['family_page']}.md"
495 if not page.is_file():
496 return set()
497 text = page.read_text(encoding="utf-8")
498 anchors = set(re.findall(r"^@anchor\s+([A-Za-z0-9_]+)\s*$", text, re.M))
499 return {
500 name
501 for name in family["public_values"]
502 if entry_anchor(registry_entry, name) in anchors
503 }
504
505
Here is the call graph for this function:
Here is the caller graph for this function:

◆ render_family()

str generate_capability_inventory.render_family ( dict  family,
dict  registry_entry 
)

Render one family's value table as a Doxygen-includable HTML fragment.

HTML rather than Markdown because Doxygen's plain include command inserts Markdown verbatim as a code block, while its HTML include command inserts real markup. Each value links to its Tier-2 entry so the inventory is a route into the documentation, not a dead list.

Parameters
[in]familyCollected family record.
[in]registry_entryRegistry entry for the same family.
Returns
HTML fragment text.

Definition at line 506 of file generate_capability_inventory.py.

506def render_family(family: dict, registry_entry: dict) -> str:
507 """!
508 @brief Render one family's value table as a Doxygen-includable HTML fragment.
509
510 HTML rather than Markdown because Doxygen's plain include command inserts Markdown
511 verbatim as a code block, while its HTML include command inserts real markup. Each value links to its
512 Tier-2 entry so the inventory is a route into the documentation, not a dead list.
513 @param[in] family Collected family record.
514 @param[in] registry_entry Registry entry for the same family.
515 @return HTML fragment text.
516 """
517 values = family["public_values"]
518 documented_values = documented_entries(family, registry_entry)
519 has_params = any("required_params" in v for v in values.values())
520 latent_present = any(
521 v.get("reachability") == "latent"
522 or v.get("alias_of")
523 or v.get("spelling_of")
524 or v.get("status", "supported") != "supported"
525 for v in values.values()
526 )
527
528 out = [
529 f"<!-- GENERATED FILE - do not edit by hand. Family: {family['id']}.",
530 " Regenerate with: make docs-inventory -->",
531 '<table class="markdownTable">',
532 "<tr>",
533 ]
534 headers = ["Value", "Applies to", "Required parameters", "Optional parameters"] if has_params \
535 else ["Value", "Maps to"]
536 if latent_present:
537 headers.append("Status")
538 out += [f'<th class="markdownTableHeadNone">{h}</th>' for h in headers]
539 out.append("</tr>")
540
541 for name, spec in sorted(values.items()):
542 cells = []
543 label = f"<code>{html_escape(name)}</code>"
544 # A spelling has no entry of its own: link it to the canonical value's entry.
545 # A value whose entry is absent (latent, or deliberately deferred) must not
546 # link at all rather than link into nothing.
547 target = spec.get("spelling_of") or name
548 documented = target in documented_values
549 if documented and spec.get("reachability") != "latent":
550 label = f'<a href="#{entry_anchor(registry_entry, target)}">{label}</a>'
551 cells.append(label)
552 if has_params:
553 cells.append(", ".join(f"<code>{html_escape(x)}</code>" for x in spec.get("types", [])) or "-")
554 cells.append(
555 ", ".join(f"<code>{html_escape(x)}</code>" for x in spec.get("required_params", [])) or "none"
556 )
557 cells.append(
558 ", ".join(f"<code>{html_escape(x)}</code>" for x in spec.get("optional_params", [])) or "none"
559 )
560 else:
561 cells.append(f"<code>{html_escape(str(spec.get('maps_to', '-')))}</code>")
562 if latent_present:
563 if spec.get("reachability") == "latent":
564 cells.append("<b>latent - not selectable</b>")
565 elif spec.get("alias_of"):
566 cells.append(
567 "<b>deprecated</b> - alias of <code>"
568 + html_escape(str(spec["alias_of"]))
569 + "</code>"
570 )
571 elif spec.get("spelling_of"):
572 cells.append(
573 "accepted spelling of <code>"
574 + html_escape(str(spec["spelling_of"]))
575 + "</code>"
576 )
577 else:
578 # Show the lifecycle status, not merely that the value can be typed. A
579 # known-defective capability reading "selectable" is the failure this
580 # column exists to prevent.
581 status = spec.get("status", "supported")
582 cells.append(
583 f"<b>{html_escape(status)}</b>" if status != "supported" else "supported"
584 )
585 out.append("<tr>" + "".join(f'<td class="markdownTableBodyNone">{c}</td>' for c in cells) + "</tr>")
586
587 out.append("</table>")
588 return "\n".join(out) + "\n"
589
590
Here is the call graph for this function:
Here is the caller graph for this function:

◆ family_fragment_path()

Path generate_capability_inventory.family_fragment_path ( str  family_id)

Path of the per-family includable fragment.

Parameters
[in]family_idFamily identifier.
Returns
Fragment path under the generated directory.

Definition at line 591 of file generate_capability_inventory.py.

591def family_fragment_path(family_id: str) -> Path:
592 """!
593 @brief Path of the per-family includable fragment.
594 @param[in] family_id Family identifier.
595 @return Fragment path under the generated directory.
596 """
597 return GENERATED_DIR / f"capability_inventory_{family_id.replace('.', '_')}.html"
598
599
Here is the caller graph for this function:

◆ render_evidence_matrix()

str generate_capability_inventory.render_evidence_matrix ( list[dict]  inventory,
dict  registry 
)

Render the project-wide capability-by-evidence matrix as an HTML fragment.

A scientist deciding whether a result is credible needs to see, in one place, what confidence the project claims for each capability. An empty row is a real answer - it says "implemented only".

Parameters
[in]inventoryCollected family records.
[in]registryParsed registry mapping.
Returns
HTML fragment text.

Definition at line 600 of file generate_capability_inventory.py.

600def render_evidence_matrix(inventory: list[dict], registry: dict) -> str:
601 """!
602 @brief Render the project-wide capability-by-evidence matrix as an HTML fragment.
603
604 A scientist deciding whether a result is credible needs to see, in one place,
605 what confidence the project claims for each capability. An empty row is a real
606 answer - it says "implemented only".
607 @param[in] inventory Collected family records.
608 @param[in] registry Parsed registry mapping.
609 @return HTML fragment text.
610 """
611 facets = registry["evidence_facets"]
612 order = ["unit", "integration", "analytical", "benchmark", "reference", "production"]
613 entries = {e["id"]: e for e in registry["families"]}
614 out = [
615 "<!-- GENERATED FILE - do not edit by hand. Regenerate with: make docs-inventory -->",
616 '<table class="markdownTable">',
617 "<tr>",
618 '<th class="markdownTableHeadNone">Capability</th>',
619 '<th class="markdownTableHeadNone">Family</th>',
620 ]
621 out += [f'<th class="markdownTableHeadNone">{html_escape(facets[f])}</th>' for f in order]
622 out.append("</tr>")
623 for family in inventory:
624 meta = entries[family["id"]].get("value_metadata", {})
625 for name, spec in sorted(family["public_values"].items()):
626 if spec.get("spelling_of") or spec.get("reachability") == "latent":
627 continue
628 have = dict(meta.get(name, {}).get("evidence", {}) or {})
629 label = f"<code>{html_escape(name)}</code>"
630 if name in documented_entries(family, entries[family["id"]]):
631 anchor = entry_anchor(entries[family["id"]], name)
632 label = f'<a href="{family["family_page"]}.html#{anchor}">{label}</a>'
633 cells = [label, f"<code>{html_escape(family['id'])}</code>"]
634 cells += [
635 (
636 '<span title="'
637 + html_escape("; ".join(have[f]))
638 + '">&#10003;</span>'
639 )
640 if f in have
641 else "&#8212;"
642 for f in order
643 ]
644 out.append("<tr>" + "".join(f'<td class="markdownTableBodyNone">{c}</td>' for c in cells) + "</tr>")
645 out.append("</table>")
646 return "\n".join(out) + "\n"
647
648
Here is the call graph for this function:
Here is the caller graph for this function:

◆ render_markdown()

str generate_capability_inventory.render_markdown ( list[dict]  inventory)

Render the inventory as a Doxygen-includable Markdown fragment.

Parameters
[in]inventoryCollected family records.
Returns
Markdown text.

Definition at line 649 of file generate_capability_inventory.py.

649def render_markdown(inventory: list[dict]) -> str:
650 """!
651 @brief Render the inventory as a Doxygen-includable Markdown fragment.
652 @param[in] inventory Collected family records.
653 @return Markdown text.
654 """
655 lines = [
656 "<!-- GENERATED FILE - do not edit by hand.",
657 " Regenerate with: make docs-inventory",
658 " Source of truth: the Python validation layer named per family below. -->",
659 "",
660 ]
661 for family in inventory:
662 lines.append(f"### {family['title']}")
663 lines.append("")
664 lines.append(f"Selector: `{family['selector']}`")
665 lines.append("")
666 has_params = any("required_params" in v for v in family["public_values"].values())
667 if has_params:
668 lines.append("| Value | Applies to | Required parameters | Optional parameters |")
669 lines.append("|---|---|---|---|")
670 for name, spec in sorted(family["public_values"].items()):
671 types = ", ".join(f"`{t}`" for t in spec.get("types", [])) or "-"
672 req = ", ".join(f"`{p}`" for p in spec.get("required_params", [])) or "none"
673 opt = ", ".join(f"`{p}`" for p in spec.get("optional_params", [])) or "none"
674 lines.append(f"| `{name}` | {types} | {req} | {opt} |")
675 else:
676 lines.append("| Value | Maps to |")
677 lines.append("|---|---|")
678 for name, spec in sorted(family["public_values"].items()):
679 lines.append(f"| `{name}` | `{spec.get('maps_to', '-')}` |")
680 lines.append("")
681 return "\n".join(lines)
682
683
Here is the caller graph for this function:

◆ main()

int generate_capability_inventory.main ( )

Generate the capability inventory artifacts.

Returns
Process status code.

Definition at line 684 of file generate_capability_inventory.py.

684def main() -> int:
685 """!
686 @brief Generate the capability inventory artifacts.
687 @return Process status code.
688 """
689 parser = argparse.ArgumentParser(description="Generate the public capability inventory.")
690 parser.add_argument("--check", action="store_true", help="Fail if generated output is stale.")
691 args = parser.parse_args()
692
693 registry = load_registry()
694 entries = {f["id"]: f for f in registry["families"]}
695 inventory = [collect(family) for family in registry["families"]]
696 apply_metadata(inventory, registry)
697 apply_reachability(inventory, registry)
698 snapshot = json.dumps(inventory, indent=2, sort_keys=True) + "\n"
699 markdown = render_markdown(inventory)
700
701 GENERATED_DIR.mkdir(parents=True, exist_ok=True)
702 json_path = GENERATED_DIR / "capability_inventory.json"
703 md_path = GENERATED_DIR / "capability_inventory.md"
704
705 managed = {json_path: snapshot, md_path: markdown}
706 managed[GENERATED_DIR / "evidence_matrix.html"] = render_evidence_matrix(inventory, registry)
707 for family in inventory:
708 managed[family_fragment_path(family["id"])] = render_family(family, entries[family["id"]])
709
710 # Orphan detection is scoped to the files THIS generator owns. Other generators
711 # write into the same directory; claiming their output would report false orphans.
712 owned_prefixes = ("capability_inventory", "evidence_matrix")
713 existing = {
714 path
715 for path in GENERATED_DIR.iterdir()
716 if path.is_file() and path.name.startswith(owned_prefixes)
717 }
718 orphans = sorted(existing - set(managed))
719
720 if args.check:
721 problems = [
722 f"stale: {path.relative_to(REPO_ROOT)}"
723 for path, content in managed.items()
724 if not path.is_file() or path.read_text(encoding="utf-8") != content
725 ]
726 problems += [f"orphan: {path.relative_to(REPO_ROOT)} (no family produces it)" for path in orphans]
727 if problems:
728 print("Generated capability inventory is out of date:", file=sys.stderr)
729 for problem in problems:
730 print(f" {problem}", file=sys.stderr)
731 print("\nRegenerate with: make docs-inventory", file=sys.stderr)
732 return 1
733 print(f"Capability inventory is current ({len(inventory)} families, {len(managed)} managed files).")
734 return 0
735
736 for path, content in managed.items():
737 path.write_text(content, encoding="utf-8")
738 for path in orphans:
739 path.unlink()
740 print(f"Removed orphaned generated file: {path.relative_to(REPO_ROOT)}")
741 counts = classify(inventory)
742 print(
743 f"Wrote capability inventory: {len(inventory)} families, "
744 f"{counts['selectable']} canonical, {counts['spelling']} accepted spelling, "
745 f"{counts['alias']} deprecated alias, {counts['latent']} latent; "
746 f"{len(managed)} managed files."
747 )
748 return 0
749
750
int main(int argc, char **argv)
Entry point for the postprocessor executable.
Here is the call graph for this function:
Here is the caller graph for this function:

Variable Documentation

◆ REPO_ROOT

generate_capability_inventory.REPO_ROOT = Path(__file__).resolve().parents[2]

Definition at line 14 of file generate_capability_inventory.py.

◆ REGISTRY_PATH

str generate_capability_inventory.REGISTRY_PATH = REPO_ROOT / "tests" / "tooling" / "capability_families.json"

Definition at line 15 of file generate_capability_inventory.py.

◆ GENERATED_DIR

str generate_capability_inventory.GENERATED_DIR = REPO_ROOT / "docs" / "generated"

Definition at line 16 of file generate_capability_inventory.py.