Files
fn_registry/python/functions/infra/export_hub_manifest.py
T
egutierrez 7913116a8e chore: auto-commit (129 archivos)
- .claude/agents/fn-analizador/SKILL.md
- .claude/agents/fn-constructor/SKILL.md
- .claude/agents/fn-executor/SKILL.md
- .claude/agents/fn-mejorador/SKILL.md
- .claude/agents/fn-orquestador/SKILL.md
- .claude/agents/fn-recopilador/SKILL.md
- .claude/commands/app.md
- .claude/commands/compile.md
- .claude/commands/cpp-app.md
- .claude/commands/create_functions.md
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 22:23:12 +02:00

144 lines
4.6 KiB
Python

"""export_hub_manifest — genera el TSV sidecar para app_hub_launcher."""
from __future__ import annotations
import os
import sqlite3
import sys
from pathlib import Path
from typing import Any
def _read_frontmatter(md_path: Path) -> dict[str, Any]:
"""Parse YAML frontmatter from a .md file. Returns {} on any error."""
try:
import yaml # PyYAML — available in python/.venv
text = md_path.read_text(encoding="utf-8")
if not text.startswith("---"):
return {}
# Find the closing ---
end = text.find("\n---", 3)
if end == -1:
return {}
yaml_block = text[3:end].strip()
data = yaml.safe_load(yaml_block)
return data if isinstance(data, dict) else {}
except Exception as exc:
print(f"[export_hub_manifest] WARN: could not parse {md_path}: {exc}", file=sys.stderr)
return {}
def _snake_to_display(name: str) -> str:
"""Convert snake_case name to Title Case With Spaces.
Examples:
graph_explorer -> Graph Explorer
dag_engine_ui -> Dag Engine Ui
app_hub_launcher -> App Hub Launcher
"""
return " ".join(part.capitalize() for part in name.split("_"))
def export_hub_manifest(out_path: str, *, registry_root: str | None = None) -> dict:
"""Generate TSV sidecar manifest for app_hub_launcher.
Queries registry.db for all cpp/imgui apps, reads their app.md
frontmatter to extract name, description and accent color, then
writes a UTF-8 TSV to out_path.
Args:
out_path: Destination path for the TSV manifest file.
registry_root: Path to the fn_registry root directory.
Defaults to FN_REGISTRY_ROOT env var or repo root derived from file location.
Returns:
{"ok": True, "count": N, "out_path": "<abs_path>"}
"""
root = Path(
registry_root
or os.environ.get("FN_REGISTRY_ROOT")
or Path(__file__).resolve().parents[3]
).resolve()
db_path = root / "registry.db"
if not db_path.exists():
raise FileNotFoundError(f"registry.db not found at {db_path}")
con = sqlite3.connect(str(db_path))
con.row_factory = sqlite3.Row
try:
rows = con.execute(
"SELECT id, name, dir_path FROM apps WHERE lang='cpp' AND framework='imgui' ORDER BY name"
).fetchall()
finally:
con.close()
DEFAULT_ACCENT = "#64748b"
TSV_HEADER = "name\tdisplay_name\tdescription\taccent_hex\n"
lines: list[str] = [TSV_HEADER]
count = 0
for row in rows:
app_name: str = row["name"]
dir_path: str = row["dir_path"]
# Derive defaults in case app.md is missing / malformed
display_name = _snake_to_display(app_name)
description = ""
accent_hex = DEFAULT_ACCENT
md_path = root / dir_path / "app.md"
if md_path.exists():
fm = _read_frontmatter(md_path)
if fm:
description = fm.get("description", "") or ""
icon_block = fm.get("icon")
if isinstance(icon_block, dict):
accent_hex = icon_block.get("accent", DEFAULT_ACCENT) or DEFAULT_ACCENT
else:
print(
f"[export_hub_manifest] WARN: empty/malformed frontmatter in {md_path}",
file=sys.stderr,
)
else:
print(
f"[export_hub_manifest] WARN: app.md missing for {app_name} at {md_path}",
file=sys.stderr,
)
# Sanitize: TSV values must not contain tabs or newlines
def clean(s: str) -> str:
return s.replace("\t", " ").replace("\n", " ").replace("\r", "")
lines.append(
f"{clean(app_name)}\t{clean(display_name)}\t{clean(description)}\t{clean(accent_hex)}\n"
)
count += 1
out = Path(out_path).resolve()
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text("".join(lines), encoding="utf-8")
return {"ok": True, "count": count, "out_path": str(out)}
if __name__ == "__main__":
import argparse
import json
parser = argparse.ArgumentParser(
description="Export hub manifest TSV for app_hub_launcher."
)
parser.add_argument("out_path", help="Destination .tsv file path")
parser.add_argument(
"--registry-root",
default=None,
help="Path to fn_registry root (default: FN_REGISTRY_ROOT env or repo root derived from file location)",
)
args = parser.parse_args()
result = export_hub_manifest(args.out_path, registry_root=args.registry_root)
print(json.dumps(result, indent=2))