Expand prompt routing map
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user