diff --git a/scripts/setup_manifest.py b/scripts/setup_manifest.py new file mode 100755 index 0000000..1e88da6 --- /dev/null +++ b/scripts/setup_manifest.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +""" +Utility commands for setup.sh to read module manifest metadata. +""" + +import json +import sys +from pathlib import Path +from typing import Iterable, List + + +def load_manifest(path: str) -> dict: + manifest_path = Path(path) + if not manifest_path.is_file(): + print(f"ERROR: Module manifest not found at {manifest_path}", file=sys.stderr) + sys.exit(1) + try: + return json.loads(manifest_path.read_text()) + except json.JSONDecodeError as exc: + print(f"ERROR: Failed to parse manifest {manifest_path}: {exc}", file=sys.stderr) + sys.exit(1) + + +def iter_modules(manifest: dict) -> Iterable[dict]: + modules = manifest.get("modules") or [] + for entry in modules: + if isinstance(entry, dict) and entry.get("key"): + yield entry + + +def unique_preserve_order(values: Iterable[str]) -> List[str]: + seen = set() + ordered: List[str] = [] + for value in values: + if not value: + continue + if value not in seen: + seen.add(value) + ordered.append(value) + return ordered + + +def clean(value: str) -> str: + if value is None: + return "-" + text = str(value).replace("\t", " ").replace("\n", " ").strip() + return text if text else "-" + + +def cmd_keys(manifest_path: str) -> None: + manifest = load_manifest(manifest_path) + for entry in iter_modules(manifest): + print(entry["key"]) + + +def cmd_metadata(manifest_path: str) -> None: + manifest = load_manifest(manifest_path) + for entry in iter_modules(manifest): + key = entry["key"] + name = clean(entry.get("name", key)) + needs_build = "1" if entry.get("needs_build") else "0" + module_type = clean(entry.get("type", "")) + status = clean(entry.get("status", "active")) + block_reason = clean(entry.get("block_reason", "")) + requires = unique_preserve_order(entry.get("requires") or []) + requires_csv = ",".join(requires) if requires else "-" + notes = clean(entry.get("notes", "")) + description = clean(entry.get("description", "")) + category = clean(entry.get("category", "")) + print( + "\t".join( + [ + key, + name, + needs_build, + module_type if module_type != "" else "-", + status, + block_reason, + requires_csv, + notes, + description, + category, + ] + ) + ) + + +def cmd_sorted_keys(manifest_path: str) -> None: + manifest = load_manifest(manifest_path) + modules = list(iter_modules(manifest)) + modules.sort( + key=lambda item: ( + str(item.get("type", "")), + str(item.get("name", item.get("key", ""))).lower(), + ) + ) + for entry in modules: + print(entry["key"]) + + +COMMAND_MAP = { + "keys": cmd_keys, + "metadata": cmd_metadata, + "sorted-keys": cmd_sorted_keys, +} + + +def main(argv: List[str]) -> int: + if len(argv) != 3: + print(f"Usage: {argv[0]} ", file=sys.stderr) + return 1 + + command = argv[1] + manifest_path = argv[2] + handler = COMMAND_MAP.get(command) + if handler is None: + valid = ", ".join(sorted(COMMAND_MAP)) + print(f"Unknown command '{command}'. Valid commands: {valid}", file=sys.stderr) + return 1 + + handler(manifest_path) + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/setup.sh b/setup.sh index 82dd073..5555f30 100755 --- a/setup.sh +++ b/setup.sh @@ -349,6 +349,7 @@ EOF # ============================== MODULE_MANIFEST_PATH="$SCRIPT_DIR/config/modules.json" +MODULE_MANIFEST_HELPER="$SCRIPT_DIR/scripts/setup_manifest.py" ENV_TEMPLATE_FILE="$SCRIPT_DIR/.env.template" declare -a MODULE_KEYS=() @@ -394,25 +395,17 @@ load_module_manifest_metadata() { echo "ERROR: Module manifest not found at $MODULE_MANIFEST_PATH" >&2 exit 1 fi + if [ ! -x "$MODULE_MANIFEST_HELPER" ]; then + echo "ERROR: Manifest helper not found or not executable at $MODULE_MANIFEST_HELPER" >&2 + exit 1 + fi if ! command -v python3 >/dev/null 2>&1; then echo "ERROR: python3 is required to read $MODULE_MANIFEST_PATH" >&2 exit 1 fi mapfile -t MODULE_KEYS < <( - python3 - "$MODULE_MANIFEST_PATH" <<'PY' -import json, sys -from pathlib import Path - -manifest_path = Path(sys.argv[1]) -manifest = json.loads(manifest_path.read_text()) -modules = manifest.get("modules", []) -for entry in modules: - key = entry.get("key") - if not key: - continue - print(key) -PY + python3 "$MODULE_MANIFEST_HELPER" keys "$MODULE_MANIFEST_PATH" ) if [ ${#MODULE_KEYS[@]} -eq 0 ]; then @@ -438,55 +431,10 @@ PY MODULE_DESCRIPTION_MAP["$key"]="$description" MODULE_CATEGORY_MAP["$key"]="$category" KNOWN_MODULE_LOOKUP["$key"]=1 - done < <( - python3 - "$MODULE_MANIFEST_PATH" <<'PY' -import json, sys -from pathlib import Path - -manifest_path = Path(sys.argv[1]) -manifest = json.loads(manifest_path.read_text()) - -def clean(value): - if value is None or value == "": - return "-" - return str(value).replace("\t", " ").replace("\n", " ").strip() - -for entry in manifest.get("modules", []): - key = entry.get("key") - if not key: - continue - name = clean(entry.get("name", key)) - needs_build = "1" if entry.get("needs_build") else "0" - module_type = clean(entry.get("type", "")) or "-" - status = clean(entry.get("status", "active")) - block_reason = clean(entry.get("block_reason", "")) - requires = entry.get("requires") or [] - ordered = [] - for dep in list(requires): - if dep and dep not in ordered: - ordered.append(dep) - requires_csv = ",".join(ordered) if ordered else "-" - notes = clean(entry.get("notes", "")) - description = clean(entry.get("description", "")) - category = clean(entry.get("category", "")) - print("\t".join([key, name, needs_build, module_type, status, block_reason, requires_csv, notes, description, category])) -PY - ) + done < <(python3 "$MODULE_MANIFEST_HELPER" metadata "$MODULE_MANIFEST_PATH") mapfile -t MODULE_KEYS_SORTED < <( - python3 - "$MODULE_MANIFEST_PATH" <<'PY' -import json, sys -from pathlib import Path - -manifest_path = Path(sys.argv[1]) -manifest = json.loads(manifest_path.read_text()) -modules = manifest.get("modules", []) -sorted_keys = sorted(modules, key=lambda m: (str(m.get("type", "")), str(m.get("name", m.get("key"))).lower())) -for entry in sorted_keys: - key = entry.get("key") - if key: - print(key) -PY + python3 "$MODULE_MANIFEST_HELPER" sorted-keys "$MODULE_MANIFEST_PATH" ) }