#!/usr/bin/env python3 """Produce status documentation for all apps.""" import argparse import json import pathlib import re from datetime import datetime from typing import Dict, List ROOT = pathlib.Path(__file__).resolve().parents[1] def load_catalog() -> List[Dict[str, str]]: catalog_path = ROOT / "apps" / "catalog.json" return json.loads(catalog_path.read_text(encoding="utf-8")) def load_manifest(app_dir: pathlib.Path) -> Dict[str, object]: manifest_path = app_dir / "CloudronManifest.json" if not manifest_path.exists(): return {} try: return json.loads(manifest_path.read_text(encoding="utf-8")) except json.JSONDecodeError: return {} def detect_status(app_dir: pathlib.Path) -> str: manifest = load_manifest(app_dir) start_script = (app_dir / "start.sh").read_text(encoding="utf-8") if (app_dir / "start.sh").exists() else "" placeholders = 0 if "TODO" in json.dumps(manifest): placeholders += 1 if "not implemented" in start_script: placeholders += 1 if placeholders == 0: return "ready" if placeholders == 1: return "in-progress" return "todo" def render_table(rows: List[Dict[str, str]]) -> str: header = "| Slug | Title | Version | Status | Issue |\n| --- | --- | --- | --- | --- |" lines = [header] for row in rows: lines.append( f"| {row['slug']} | {row['title']} | {row['version']} | {row['status']} | {row['issue']} |" ) return "\n".join(lines) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Generate docs/APP_STATUS.md") parser.add_argument( "--preserve-timestamp", action="store_true", help="Reuse the existing timestamp if the status file already exists", ) return parser.parse_args() def extract_existing_timestamp(path: pathlib.Path) -> str | None: if not path.exists(): return None match = re.search(r"_Updated: ([0-9T:-]+Z)_", path.read_text(encoding="utf-8")) if match: return match.group(1) return None def main() -> None: args = parse_args() catalog = load_catalog() rows: List[Dict[str, str]] = [] for entry in catalog: slug = entry["slug"] app_dir = ROOT / "apps" / slug manifest = load_manifest(app_dir) rows.append({ "slug": slug, "title": entry["title"], "version": manifest.get("version", "0.1.0"), "status": detect_status(app_dir), "issue": entry.get("issue", "") }) timestamp = datetime.utcnow().isoformat(timespec='seconds') + "Z" if args.preserve_timestamp: existing = extract_existing_timestamp(ROOT / "docs" / "APP_STATUS.md") if existing: timestamp = existing output = ["# Application Status", "", f"_Updated: {timestamp}_", "", render_table(rows)] status_path = ROOT / "docs" / "APP_STATUS.md" status_path.write_text("\n".join(output) + "\n", encoding="utf-8") print(f"Updated {status_path.relative_to(ROOT)}") if __name__ == "__main__": main()