155 lines
5.5 KiB
Python
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())
|