Files
AstronautCheetah--frame-tweaks/dev/gen-docs.py
T
AstronautCheetah 61f5644a34 refactor: tweaks as packs, packs/official/<id>/, each described by its README.md's YAML front matter
The official tweaks move into packs/official/ and become data: everything the installer, the
installer window, the docs and the tests knew about a tweak is now in the front matter of its
README.md (title, category, tier, summary, what/why, author, source, dependencies, icon, Run
label, processes to stop, settings, known issue, wallet apps, the docs' changes/access/undo,
offline test). components.tsv, every meta and processes file, the "# label:" lines and the
window's icon, known-issue and wallet tables are gone; pack.md orders categories and tweaks.

- lib/manifest.py: a dependency-free reader for a strict YAML subset. It refuses what YAML
  readers disagree on (yes/no/on/off, dates, 010, anchors, tags, duplicate keys, plain values
  continuing on the next line) instead of guessing; tests compare it with PyYAML on every
  manifest. Also a writer.
- lib/sft.py loads packs: the official one, then ~/.local/share/frame-tweaks/packs/* and
  FRAME_TWEAKS_PACKS. A user's pack is used only once trusted (hash of its files; a changed pack
  needs trusting again); a tweak with an id already taken is used only with `replaces: true`;
  missing dependencies, newer pack api and invalid manifests are reported, not loaded.
  `./install.sh mods [trust|untrust PACK | check FOLDER]`.
- Scripts find their files through $TWEAK_DIR and other tweaks through tweak_dir (the installer
  passes FRAME_TWEAKS_DIRS); they source the helpers through $FRAME_TWEAKS_LIB. The installer
  runs every script with bash, so a pack's scripts needn't be executable. Arrow Pad's
  "release stuck modifiers after stopping it" is its own stopped.sh hook.
- Tweak icons live in their tweak's folder; the window shows them from there.
- dev/test-tweaks.sh: the offline set and what uninstall may keep come from manifests; a
  tweak's offline dependencies are installed before the baseline instead of listing their files.
- Docs links, CI, pin tools and tests follow the new paths.
2026-10-14 08:11:17 -04:00

100 lines
3.9 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Generate the docs that list tweaks, from the official pack (packs/official: each tweak's
README.md front matter, in pack.md's order):
README.md the Core / Extras / Helpers tables
SECURITY.md the "What each tweak changes on your system" rows
<tweak>/README an "At a glance" box (survives updates, what it changes, how to undo)
Generated parts sit between <!-- generated:NAME --> and <!-- /generated:NAME --> markers.
dev/gen-docs.py rewrite them
dev/gen-docs.py --check fail if anything is out of date (CI)"""
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "lib"))
import sft # noqa: E402
OFFICIAL = "packs/official"
def tweaks():
out = []
for d in sft.read_pack(ROOT / OFFICIAL)[1]:
c, problems = sft.read_tweak(d)
if not c:
sys.exit("\n".join(problems))
out.append((c["id"], {"emoji": c["emoji"], "name": c["title"], "group": c["tier"], "what": c["what"],
"why": c["why"], "changes": c["changes"], "access": c["access"],
"survives-updates": c["survives_updates"], "undo": c["undo"],
"experimental": c["experimental"]}))
return out
def link(i, name):
"""[**Claude Code**](packs/official/claude-code/) (command line): bold name, plain parenthetical."""
m = re.match(r"(.*?)( \(.*\))?$", name)
return f"[**{m[1]}**]({OFFICIAL}/{i}/){m[2] or ''}"
def readme_table(rows, group):
lines = ["| | Tweak | What you get | Why (on the Frame) |", "|---|---|---|---|"]
lines += [f"| {m['emoji']} | {link(i, m['name'])}{' 🧪' if m.get('experimental') else ''} | {m['what']} | {m['why']} |"
for i, m in rows if m["group"] == group]
return "\n".join(lines)
def security_rows(rows):
return "\n".join(f"| {i} | {m['changes']} | {m['access']} |" for i, m in rows)
def glance(m):
lines = ["> **At a glance**", ">",
f"> - **Survives SteamOS updates:** {m['survives-updates']}",
f"> - **Changes:** {m['changes']}"]
if m.get("experimental"):
lines.append(f"> - **🧪 Experimental:** {m['experimental']}")
if m["access"] not in ("–", "-"):
lines.append(f"> - **Unusual access:** {m['access']}")
lines.append(f"> - **Undo:** {m['undo']}")
return "\n".join(lines)
def put(text, name, body, path):
begin, end = f"<!-- generated:{name} (dev/gen-docs.py, from each tweak's meta) -->", f"<!-- /generated:{name} -->"
block = f"{begin}\n{body}\n{end}"
pat = re.compile(re.escape(begin.split(" (")[0]) + r".*?" + re.escape(end), re.S)
if pat.search(text):
return pat.sub(lambda _: block, text)
if name == "glance": # first run: after the title and first paragraph
parts = text.split("\n\n", 2)
return "\n\n".join(parts[:2] + [block] + parts[2:]) if len(parts) == 3 else text + "\n\n" + block + "\n"
sys.exit(f"{path}: no {begin.split(' (')[0]} marker")
def main():
check = "--check" in sys.argv
rows = tweaks()
targets = {ROOT / "README.md": [(g, readme_table(rows, g)) for g in ("core", "extras", "helper")],
ROOT / "SECURITY.md": [("security", security_rows(rows))]}
for i, m in rows:
targets[ROOT / OFFICIAL / i / "README.md"] = [("glance", glance(m))]
stale = []
for path, parts in targets.items():
old = path.read_text()
new = old
for name, body in parts:
new = put(new, name, body, path)
if new != old:
stale.append(str(path.relative_to(ROOT)))
if not check:
path.write_text(new)
if check and stale:
sys.exit("out of date (run dev/gen-docs.py): " + ", ".join(stale))
print("docs up to date" if not stale else "updated: " + ", ".join(stale))
if __name__ == "__main__":
main()