Public Access
Keys: one table file for the device's help panel and the website's key tables (#72)
Every screen's keys are constant tables in lib/core/src/app_keys.h (52 of them, each under an `// id: Title` comment). An App's help() picks the table of the state it is in. site/tools/gen_dev_docs.py reads the same file and writes site/data/keys.toml; the `keys` shortcode shows a screen's tables on its guide page, and /guide/keys/ shows all of them. The Site job fails when the data file is out of date or a page asks for a table that doesn't exist, and now also runs when app_keys.h changes. A key added to an App shows up on the website without anyone editing a page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
@@ -9,6 +9,8 @@ Zola cannot read a file outside its own folder, so the pages are generated and c
|
||||
milestones/ one page for each docs/milestones/*.md
|
||||
build/build-and-test, build/flash sections of README.md
|
||||
debug/commands the commands the firmware's `help` prints (parsed from src/main.cpp), then README's table of them
|
||||
site/data/keys.toml every screen's keys, from lib/core/src/app_keys.h: what the help panel (Fn+h) shows on the
|
||||
device, for the `keys` shortcode of the user guide (issue #72)
|
||||
Every generated page says where it comes from; edit that file, not the page. Hand-written pages sit next to them.
|
||||
"""
|
||||
import json
|
||||
@@ -139,8 +141,38 @@ def safe_mode_commands():
|
||||
return exact, prefix
|
||||
|
||||
|
||||
def key_tables():
|
||||
"""Every table of lib/core/src/app_keys.h: [(id, title, [(keys, action), ...])], in the file's order."""
|
||||
src = (REPO / "lib" / "core" / "src" / "app_keys.h").read_text()
|
||||
|
||||
def text(literal): # a C string literal's contents: \xHH bytes are UTF-8
|
||||
raw = re.sub(r"\\x([0-9A-Fa-f]{2})", lambda m: chr(int(m.group(1), 16)), literal).replace('\\"', '"')
|
||||
return raw.encode("latin-1").decode("utf-8")
|
||||
|
||||
tables = []
|
||||
for m in re.finditer(r"// ([a-z0-9-]+): ([^\n]+)\ninline constexpr KeyHelp k\w+\[\] = \{\n(.*?)\n\};", src, re.S):
|
||||
rows = [(text(k), text(a)) for k, a in re.findall(r'\{"((?:[^"\\]|\\.)*)", "((?:[^"\\]|\\.)*)"\},', m.group(3))]
|
||||
if len(rows) != len([l for l in m.group(3).splitlines() if l.strip()]):
|
||||
sys.exit(f"gen_dev_docs: a row of `{m.group(1)}` in app_keys.h isn't in the form {{\"keys\", \"action\"}},")
|
||||
tables.append((m.group(1), m.group(2).strip(), rows))
|
||||
declared = len(re.findall(r"^inline constexpr KeyHelp k\w+\[\]", src, re.M))
|
||||
if len(tables) != declared or len({t[0] for t in tables}) != len(tables):
|
||||
sys.exit(f"gen_dev_docs: app_keys.h has {declared} tables, {len(tables)} with an `// id: Title` comment and a unique id")
|
||||
return tables
|
||||
|
||||
|
||||
def keys_toml():
|
||||
out = ["# Generated by site/tools/gen_dev_docs.py from lib/core/src/app_keys.h: the keys of every screen, as the",
|
||||
"# help panel (Fn+h) lists them on the device. Edit that header, not this file.", ""]
|
||||
for ident, title, rows in key_tables():
|
||||
out += ["[[scope]]", f"id = {json.dumps(ident)}", f"title = {json.dumps(title, ensure_ascii=False)}", "rows = ["]
|
||||
out += [f" [{json.dumps(k, ensure_ascii=False)}, {json.dumps(a, ensure_ascii=False)}]," for k, a in rows]
|
||||
out += ["]", ""]
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def build():
|
||||
pages = {}
|
||||
pages = {"../../data/keys.toml": keys_toml()}
|
||||
|
||||
for path in sorted((REPO / "docs" / "adr").glob("*.md")):
|
||||
title, body = title_and_body(path.read_text())
|
||||
@@ -179,9 +211,25 @@ def build():
|
||||
return pages
|
||||
|
||||
|
||||
def unknown_scopes():
|
||||
"""Scopes a page asks the `keys` shortcode for that app_keys.h doesn't have."""
|
||||
known = {t[0] for t in key_tables()}
|
||||
bad = []
|
||||
for path in sorted((SITE / "content").rglob("*.md")):
|
||||
for call in re.findall(r"keys\(scopes=\[([^\]]*)\]", path.read_text()):
|
||||
for ident in re.findall(r'"([^"]+)"', call):
|
||||
if ident not in known:
|
||||
bad.append(f"{path.relative_to(SITE)}: no key table `{ident}` in lib/core/src/app_keys.h")
|
||||
return bad
|
||||
|
||||
|
||||
def main():
|
||||
check = "--check" in sys.argv
|
||||
pages = build()
|
||||
for problem in unknown_scopes():
|
||||
print("gen_dev_docs:", problem)
|
||||
if unknown_scopes():
|
||||
sys.exit(1)
|
||||
stale = []
|
||||
for rel, text in sorted(pages.items()):
|
||||
path = OUT / rel
|
||||
@@ -193,10 +241,10 @@ def main():
|
||||
path.write_text(text)
|
||||
if check:
|
||||
for rel in stale:
|
||||
print(f"gen_dev_docs: content/dev/{rel} is out of date: run site/tools/gen_dev_docs.py and commit the result")
|
||||
print(f"gen_dev_docs: {len(pages)} pages, {len(stale)} out of date")
|
||||
print(f"gen_dev_docs: {os.path.normpath(os.path.join('site/content/dev', rel))} is out of date: run site/tools/gen_dev_docs.py and commit the result")
|
||||
print(f"gen_dev_docs: {len(pages)} files, {len(stale)} out of date")
|
||||
sys.exit(1 if stale else 0)
|
||||
print(f"gen_dev_docs: {len(pages)} pages, {len(stale)} written")
|
||||
print(f"gen_dev_docs: {len(pages)} files, {len(stale)} written")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user