ci(wiki): render option tables, pin pandoc, add visible auto-gen note
Improve the generated wiki pages: - normalize definition-style option blocks (*name*: / typeof: / default:) into scdoc tables, so every module renders options as a clean table like the table-style pages (Bluetooth, Network, ... no longer a wall of text) - drop pandoc's empty leading table-header row so the real header shows - pin pandoc to 3.5 in CI (the distro 2.x man reader mangled lists into blockquotes); matches local output - prepend a visible "auto-generated from master, do not edit here" note to every page (was an invisible HTML comment) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+102
-6
@@ -35,9 +35,74 @@ def sh(cmd, stdin=None):
|
|||||||
).stdout
|
).stdout
|
||||||
|
|
||||||
|
|
||||||
|
DEF_START = re.compile(r"^\s*\*(.+?)\*:\s*(?:\+\+)?\s*$")
|
||||||
|
INDENT = re.compile(r"^(?:\t| {2,})")
|
||||||
|
FIELD = re.compile(r"^(typeof|default)\s*:\s*(.*)$", re.IGNORECASE)
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_option_blocks(text):
|
||||||
|
"""Rewrite definition-style option blocks into scdoc table syntax.
|
||||||
|
|
||||||
|
Some man pages document options as:
|
||||||
|
|
||||||
|
*name*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: foo ++
|
||||||
|
Description...
|
||||||
|
|
||||||
|
which pandoc renders as a flat wall of text. Convert consecutive such
|
||||||
|
blocks into the same scdoc table syntax the table-style pages already use
|
||||||
|
(Option / Typeof / Default / Description), so every page renders as a
|
||||||
|
clean table. Table-style pages are left untouched (they never match).
|
||||||
|
Nested (indented) sub-option blocks become their own table. Fenced code
|
||||||
|
blocks are passed through verbatim.
|
||||||
|
"""
|
||||||
|
lines = text.split("\n")
|
||||||
|
out, i, n, in_code = [], 0, len(lines), False
|
||||||
|
while i < n:
|
||||||
|
if lines[i].lstrip().startswith("```"):
|
||||||
|
in_code = not in_code
|
||||||
|
out.append(lines[i])
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
# A block starts with `*name*:` and its first indented line is `typeof:`.
|
||||||
|
m = DEF_START.match(lines[i])
|
||||||
|
nxt = lines[i + 1] if i + 1 < n else ""
|
||||||
|
if in_code or not (m and INDENT.match(nxt)
|
||||||
|
and nxt.strip().lower().startswith("typeof")):
|
||||||
|
out.append(lines[i])
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
rows = []
|
||||||
|
while i < n and DEF_START.match(lines[i]):
|
||||||
|
name = DEF_START.match(lines[i]).group(1)
|
||||||
|
i += 1
|
||||||
|
typ = default = ""
|
||||||
|
desc = []
|
||||||
|
while i < n and INDENT.match(lines[i]):
|
||||||
|
cell = re.sub(r"\s*\+\+\s*$", "", lines[i].strip())
|
||||||
|
f = FIELD.match(cell)
|
||||||
|
if f and f.group(1).lower() == "typeof":
|
||||||
|
typ = f.group(2).strip()
|
||||||
|
elif f and f.group(1).lower() == "default":
|
||||||
|
default = f.group(2).strip()
|
||||||
|
elif cell:
|
||||||
|
desc.append(cell)
|
||||||
|
i += 1
|
||||||
|
rows.append((name, typ, default, " ".join(desc)))
|
||||||
|
if i < n and lines[i].strip() == "" and i + 1 < n and DEF_START.match(lines[i + 1]):
|
||||||
|
i += 1 # swallow blank line between two blocks, continue the run
|
||||||
|
out += ["[- *Option*", ":- *Typeof*", ":- *Default*", ":- *Description*"]
|
||||||
|
for name, typ, default, desc in rows:
|
||||||
|
out += [f"|[ *{name}*", f":[ {typ}", f":[ {default}", f":[ {desc}"]
|
||||||
|
out.append("")
|
||||||
|
return "\n".join(out)
|
||||||
|
|
||||||
|
|
||||||
def man_to_gfm(scd_path):
|
def man_to_gfm(scd_path):
|
||||||
"""scdoc -> roff -> pandoc gfm, as raw markdown text."""
|
"""scdoc -> roff -> pandoc gfm, as raw markdown text."""
|
||||||
roff = sh(["scdoc"], stdin=open(scd_path, "rb").read())
|
src = normalize_option_blocks(open(scd_path, encoding="utf-8").read())
|
||||||
|
roff = sh(["scdoc"], stdin=src.encode("utf-8"))
|
||||||
gfm = sh(["pandoc", "-f", "man", "-t", "gfm", "--wrap=none"], stdin=roff)
|
gfm = sh(["pandoc", "-f", "man", "-t", "gfm", "--wrap=none"], stdin=roff)
|
||||||
return gfm.decode("utf-8")
|
return gfm.decode("utf-8")
|
||||||
|
|
||||||
@@ -63,21 +128,52 @@ def demote(md, levels=1):
|
|||||||
return "\n".join(out)
|
return "\n".join(out)
|
||||||
|
|
||||||
|
|
||||||
|
EMPTY_ROW = re.compile(r"^\|(?:\s*\|)+\s*$")
|
||||||
|
SEP_ROW = re.compile(r"^\|[\s:\-|]+\|\s*$")
|
||||||
|
|
||||||
|
|
||||||
|
def fix_tables(md):
|
||||||
|
"""Drop pandoc's empty leading header row so the real first row is the header.
|
||||||
|
|
||||||
|
scdoc's `[-` header markers make pandoc emit an empty header row followed by
|
||||||
|
the actual header as the first body row. Detect `| | |` + separator and
|
||||||
|
remove them, promoting the next row to the header with the same alignment.
|
||||||
|
"""
|
||||||
|
lines = md.splitlines()
|
||||||
|
out, i = [], 0
|
||||||
|
while i < len(lines):
|
||||||
|
if (EMPTY_ROW.match(lines[i]) and i + 2 < len(lines)
|
||||||
|
and SEP_ROW.match(lines[i + 1]) and lines[i + 2].startswith("|")):
|
||||||
|
out.append(lines[i + 2]) # real header
|
||||||
|
out.append(lines[i + 1]) # reuse the separator (keeps alignment)
|
||||||
|
i += 3
|
||||||
|
else:
|
||||||
|
out.append(lines[i])
|
||||||
|
i += 1
|
||||||
|
return "\n".join(out)
|
||||||
|
|
||||||
|
|
||||||
def pretty(basename):
|
def pretty(basename):
|
||||||
"""waybar-sway-mode -> sway/mode (submodule heading for aggregated pages)."""
|
"""waybar-sway-mode -> sway/mode (submodule heading for aggregated pages)."""
|
||||||
return basename.removeprefix("waybar-").replace("-", "/", 1)
|
return basename.removeprefix("waybar-").replace("-", "/", 1)
|
||||||
|
|
||||||
|
|
||||||
|
REPO = "https://github.com/Alexays/Waybar"
|
||||||
|
|
||||||
|
|
||||||
def build_page(man_dir, sources, page, extras_dir):
|
def build_page(man_dir, sources, page, extras_dir):
|
||||||
parts = []
|
parts = []
|
||||||
banner = ("<!-- AUTOGENERATED from " + ", ".join(f"man/{s}.5.scd" for s in sources)
|
srclinks = ", ".join(f"[`man/{s}.5.scd`]({REPO}/blob/master/man/{s}.5.scd)"
|
||||||
+ " by .github/wiki/generate.py — DO NOT EDIT HERE."
|
for s in sources)
|
||||||
" Edit the man page(s); the wiki syncs automatically. -->")
|
note = ("> [!NOTE]\n"
|
||||||
parts.append(banner)
|
f"> This page is **auto-generated from {srclinks}** on the `master` branch.\n"
|
||||||
|
"> Do not edit it here — changes will be overwritten on the next sync.\n"
|
||||||
|
"> To update it, edit the man page(s) and open a PR.")
|
||||||
|
parts.append(note)
|
||||||
aggregated = len(sources) > 1
|
aggregated = len(sources) > 1
|
||||||
for src in sources:
|
for src in sources:
|
||||||
scd = os.path.join(man_dir, src + ".5.scd")
|
scd = os.path.join(man_dir, src + ".5.scd")
|
||||||
body = strip_sections(man_to_gfm(scd))
|
body = fix_tables(strip_sections(man_to_gfm(scd)))
|
||||||
if aggregated:
|
if aggregated:
|
||||||
parts.append(f"\n# {pretty(src)}\n\n" + demote(body))
|
parts.append(f"\n# {pretty(src)}\n\n" + demote(body))
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -31,7 +31,16 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install scdoc and pandoc
|
- name: Install scdoc and pandoc
|
||||||
run: sudo apt-get update && sudo apt-get install -y scdoc pandoc
|
# pandoc is pinned: the distro package can be an old 2.x whose man reader
|
||||||
|
# mangles lists into blockquotes. Keep this in sync with local tooling.
|
||||||
|
env:
|
||||||
|
PANDOC_VERSION: "3.5"
|
||||||
|
run: |
|
||||||
|
sudo apt-get update && sudo apt-get install -y scdoc
|
||||||
|
curl -fsSL -o /tmp/pandoc.deb \
|
||||||
|
"https://github.com/jgm/pandoc/releases/download/${PANDOC_VERSION}/pandoc-${PANDOC_VERSION}-1-amd64.deb"
|
||||||
|
sudo dpkg -i /tmp/pandoc.deb
|
||||||
|
pandoc --version | head -1
|
||||||
|
|
||||||
- name: Clone the wiki
|
- name: Clone the wiki
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
Reference in New Issue
Block a user