gogcli/scripts/gen-command-reference.sh
2026-05-04 07:25:56 +01:00

216 lines
6.6 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
BIN="${GOG_BIN:-$ROOT_DIR/bin/gog}"
OUT="${1:-}"
PY="${PYTHON:-python3}"
DOCS_DIR="$ROOT_DIR/docs"
if ! command -v "$PY" >/dev/null 2>&1; then
PY="python"
fi
if [ ! -x "$BIN" ]; then
make -C "$ROOT_DIR" build >/dev/null
fi
schema_file="$(mktemp "${TMPDIR:-/tmp}/gog-schema-XXXXXX.json")"
trap 'rm -f "$schema_file"' EXIT
"$BIN" schema --json >"$schema_file"
"$PY" - "$schema_file" "$OUT" "$DOCS_DIR" <<'PY'
import json
import os
import re
import shutil
import sys
schema_path, out_path, docs_dir = sys.argv[1:4]
with open(schema_path, "r", encoding="utf-8") as f:
schema = json.load(f)
root = schema.get("command") or {}
def first_line(value):
return (value or "").strip().splitlines()[0] if (value or "").strip() else ""
def canonical_tokens(path):
return [part for part in (path or "").split() if not (part.startswith("(") and part.endswith(")"))]
def canonical_path(command):
tokens = canonical_tokens(command.get("path") or command.get("name") or "")
return " ".join(tokens)
def command_slug(command):
path = canonical_path(command)
slug = re.sub(r"[^a-z0-9]+", "-", path.lower()).strip("-")
return slug or "gog"
def command_label(command):
path = command.get("path") or command.get("name") or ""
usage = command.get("usage") or ""
prefix = path.removeprefix("gog ").strip()
suffix = usage
if prefix and usage.startswith(prefix):
suffix = usage[len(prefix):].strip()
return path if not suffix else f"{path} {suffix}"
def walk(command, parent=None, depth=0):
command["_parent"] = parent
command["_depth"] = depth
yield command
for child in command.get("subcommands") or []:
yield from walk(child, command, depth + 1)
commands = list(walk(root))
slug_to_command = {}
for command in commands:
base = command_slug(command)
slug = base
suffix = 2
while slug in slug_to_command:
slug = f"{base}-{suffix}"
suffix += 1
command["_slug"] = slug
command["_page"] = f"commands/{slug}.md"
slug_to_command[slug] = command
def md_escape(value):
return (value or "").replace("|", "\\|").replace("\n", "<br>")
def link(command, label=None):
return f"[{label or canonical_path(command)}]({command['_slug']}.md)"
def command_reference():
lines = [
"# Command Reference",
"",
"Generated from `gog schema --json`.",
"",
]
for command in commands:
label = command_label(command)
if not label:
continue
summary = first_line(command.get("help"))
indent = " " * max(command.get("_depth", 0), 0)
target = f"commands/{command['_slug']}.md"
if summary:
lines.append(f"{indent}- [`{label}`]({target}) - {summary}")
else:
lines.append(f"{indent}- [`{label}`]({target})")
lines.append("")
return "\n".join(lines)
def command_page(command):
parent = command.get("_parent")
children = command.get("subcommands") or []
flags = command.get("flags") or []
args = command.get("arguments") or []
title = canonical_path(command)
lines = [
f"# `{title}`",
"",
"> Generated from `gog schema --json`. Do not edit this page by hand; run `make docs-commands`.",
"",
]
if command.get("help"):
lines.extend([command.get("help").strip(), ""])
lines.extend(["## Usage", "", "```bash", f"{command_label(command)}", "```", ""])
if parent:
lines.extend(["## Parent", "", f"- {link(parent)}", ""])
if children:
lines.extend(["## Subcommands", ""])
for child in children:
summary = first_line(child.get("help"))
item = f"- {link(child)}"
if summary:
item += f" - {summary}"
lines.append(item)
lines.append("")
if args:
lines.extend(["## Arguments", "", "| Name | Help |", "| --- | --- |"])
for arg in args:
lines.append(f"| `{md_escape(arg.get('name'))}` | {md_escape(arg.get('help'))} |")
lines.append("")
if flags:
lines.extend(["## Flags", "", "| Flag | Type | Default | Help |", "| --- | --- | --- | --- |"])
for flag in flags:
names = []
if flag.get("short"):
names.append(f"`-{flag['short']}`")
names.append(f"`--{flag.get('name')}`")
for alias in flag.get("aliases") or []:
names.append(f"`--{alias}`")
default = flag.get("default") if flag.get("has_default") else ""
lines.append(
f"| {'<br>'.join(names)} | `{md_escape(flag.get('type'))}` | {md_escape(default)} | {md_escape(flag.get('help'))} |"
)
lines.append("")
lines.extend(["## See Also", ""])
if parent:
lines.append(f"- {link(parent)}")
lines.append("- [Command index](README.md)")
lines.append("")
return "\n".join(lines)
def command_index():
top = [c for c in commands if c.get("_depth") == 1]
lines = [
"# Commands",
"",
"Every `gog` command has a generated docs page. The source of truth is the live CLI schema; run `make docs-commands` after changing command names, flags, help text, aliases, or arguments.",
"",
f"Generated pages: {len(commands)}.",
"",
"## Top-level Commands",
"",
]
for command in top:
summary = first_line(command.get("help"))
item = f"- {link(command)}"
if summary:
item += f" - {summary}"
lines.append(item)
lines.extend(["", "## All Commands", ""])
for command in commands:
summary = first_line(command.get("help"))
indent = " " * max(command.get("_depth", 0), 0)
item = f"{indent}- {link(command)}"
if summary:
item += f" - {summary}"
lines.append(item)
lines.append("")
return "\n".join(lines)
if out_path:
with open(out_path, "w", encoding="utf-8") as f:
f.write(command_reference())
else:
print(command_reference())
commands_dir = os.path.join(docs_dir, "commands")
shutil.rmtree(commands_dir, ignore_errors=True)
os.makedirs(commands_dir, exist_ok=True)
with open(os.path.join(commands_dir, "README.md"), "w", encoding="utf-8") as f:
f.write(command_index())
for command in commands:
with open(os.path.join(commands_dir, f"{command['_slug']}.md"), "w", encoding="utf-8") as f:
f.write(command_page(command))
print(f"generated {len(commands)} command pages in {os.path.relpath(commands_dir, os.getcwd())}", file=sys.stderr)
PY