105 lines
3.1 KiB
Python
Executable File
105 lines
3.1 KiB
Python
Executable File
#!/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]
|
|
REPO_ROOT = ROOT.parent
|
|
|
|
|
|
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 output/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()
|