Files
2026-06-26 12:22:25 +02:00

155 lines
5.5 KiB
Python

#!/usr/bin/env python3
"""Print a lightweight audit for the prompt routing map.
This intentionally avoids importing the ComfyUI node package. It parses Python
and JSON files directly, so it can run in a plain shell without ComfyUI loaded.
"""
from __future__ import annotations
import ast
import json
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
def _literal_or_none(node: ast.AST) -> Any:
try:
return ast.literal_eval(node)
except Exception:
return None
def _assignment_dict(path: Path, name: str) -> dict[str, Any]:
tree = ast.parse(path.read_text(encoding="utf-8"))
for node in tree.body:
if not isinstance(node, ast.Assign):
continue
if not any(isinstance(target, ast.Name) and target.id == name for target in node.targets):
continue
value = _literal_or_none(node.value)
return value if isinstance(value, dict) else {}
return {}
def _class_return_names(path: Path) -> dict[str, tuple[str, ...]]:
tree = ast.parse(path.read_text(encoding="utf-8"))
result: dict[str, tuple[str, ...]] = {}
for node in tree.body:
if not isinstance(node, ast.ClassDef) or not node.name.startswith("SxCP"):
continue
for item in node.body:
if not isinstance(item, ast.Assign):
continue
if not any(isinstance(target, ast.Name) and target.id == "RETURN_NAMES" for target in item.targets):
continue
value = _literal_or_none(item.value)
if isinstance(value, tuple) and all(isinstance(part, str) for part in value):
result[node.name] = value
return result
def _category_summary(path: Path) -> dict[str, Any]:
data = json.loads(path.read_text(encoding="utf-8"))
categories = data.get("categories") or []
subcategory_count = 0
item_template_count = 0
for category in categories:
subcategories = category.get("subcategories") or []
subcategory_count += len(subcategories)
for subcategory in subcategories:
item_template_count += len(subcategory.get("item_templates") or [])
for item in subcategory.get("items") or []:
if isinstance(item, dict):
item_template_count += len(item.get("item_templates") or [])
return {
"categories": len(categories),
"subcategories": subcategory_count,
"item_templates": item_template_count,
"scene_pools": len(data.get("scene_pools") or {}),
"expression_pools": len(data.get("expression_pools") or {}),
"composition_pools": len(data.get("composition_pools") or {}),
"pool_extensions": len(data.get("pool_extensions") or {}),
}
def _pool_names(path: Path, key: str) -> list[str]:
data = json.loads(path.read_text(encoding="utf-8"))
pools = data.get(key) or {}
return sorted(pools) if isinstance(pools, dict) else []
def print_table(headers: tuple[str, ...], rows: list[tuple[Any, ...]]) -> None:
widths = [len(header) for header in headers]
for row in rows:
for index, value in enumerate(row):
widths[index] = max(widths[index], len(str(value)))
print("| " + " | ".join(header.ljust(widths[index]) for index, header in enumerate(headers)) + " |")
print("| " + " | ".join("-" * width for width in widths) + " |")
for row in rows:
print("| " + " | ".join(str(value).ljust(widths[index]) for index, value in enumerate(row)) + " |")
def main() -> int:
init_path = ROOT / "__init__.py"
loop_path = ROOT / "loop_nodes.py"
display = _assignment_dict(init_path, "NODE_DISPLAY_NAME_MAPPINGS")
loop_display = _assignment_dict(loop_path, "LOOP_NODE_DISPLAY_NAME_MAPPINGS")
display.update(loop_display)
returns = _class_return_names(init_path)
returns.update(_class_return_names(loop_path))
print("# Node Display Map")
node_rows = []
for class_name, display_name in sorted(display.items(), key=lambda item: str(item[1])):
return_names = ", ".join(returns.get(class_name, ()))
node_rows.append((display_name, class_name, return_names or "(dynamic or unnamed)"))
print_table(("Display name", "Class", "Return names"), node_rows)
print("\n# Category JSON Summary")
category_rows = []
for path in sorted((ROOT / "categories").glob("*.json")):
summary = _category_summary(path)
category_rows.append(
(
path.name,
summary["categories"],
summary["subcategories"],
summary["item_templates"],
summary["scene_pools"],
summary["expression_pools"],
summary["composition_pools"],
summary["pool_extensions"],
)
)
print_table(
(
"File",
"Categories",
"Subcategories",
"Item templates",
"Scene pools",
"Expression pools",
"Composition pools",
"Extensions",
),
category_rows,
)
print("\n# Named Pool Inventory")
pool_rows = []
for path in sorted((ROOT / "categories").glob("*.json")):
for key in ("scene_pools", "expression_pools", "composition_pools"):
names = _pool_names(path, key)
if names:
pool_rows.append((path.name, key, len(names), ", ".join(names[:8]) + (" ..." if len(names) > 8 else "")))
print_table(("File", "Pool type", "Count", "First names"), pool_rows)
return 0
if __name__ == "__main__":
raise SystemExit(main())