From 6bfa01a13783665702e8fadee11ef219b3b96dab Mon Sep 17 00:00:00 2001 From: Julien Danjou Date: Mon, 29 Jun 2026 19:07:09 +0200 Subject: [PATCH] chore: remove Python from the repository tooling The action's runtime is pure shell, but its dev/CI tooling still pulled in Python: a uv-run generate-doc.py for the README inputs table, and a pip-installed semgrep + yamllint in CI. Remove it all so the repo matches its own "No Python or toolchain is required" promise. - generate-doc.py is rewritten as pure bash + awk in generate-doc.sh: it parses action.yml and rewrites the inputs table between the AUTO-DOC-INPUT markers. Output is byte-identical to the previous generator (verified on both BSD awk and Ubuntu's mawk), so the autodoc drift check still passes. awk emits the finished Markdown rows directly, avoiding a TSV/`read` round-trip whose IFS-whitespace tab handling would have collapsed an empty Default column. - CI linters: drop actions/setup-python and `pip install semgrep yamllint`; run yamllint and semgrep from their pinned docker images, matching the existing actionlint step. semgrep sets an explicit entrypoint so a digest bump can't break the invocation. Renovate manages the tags + digests. - autodoc job: drop the uv setup step; it now runs the pure-bash script. No Python, uv, or pip remains in the repo. Co-Authored-By: Claude Opus 4.8 (1M context) Change-Id: I82858ba068c90064336fe309a9c8996a9899dea8 --- .github/workflows/ci.yaml | 30 +++++---- generate-doc.py | 74 ---------------------- generate-doc.sh | 125 +++++++++++++++++++++++++++++++++++++- 3 files changed, 137 insertions(+), 92 deletions(-) delete mode 100644 generate-doc.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5660f69..2c020b4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -20,22 +20,25 @@ jobs: - name: Checkout 🛎️ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - name: Setup Python 🔧 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: 3.14.5 - - name: Check workflow files uses: docker://rhysd/actionlint:1.7.12@sha256:b1934ee5f1c509618f2508e6eb47ee0d3520686341fec936f3b79331f9315667 with: args: -color - - name: Test 🔍 - run: | - # nosemgrep: generic.ci.security.use-frozen-lockfile.use-frozen-lockfile-pip - pip install semgrep yamllint - semgrep --config=auto --error - yamllint . + # yamllint and semgrep run from their published images (no host Python), + # matching the actionlint step above. Renovate manages the tags + digests. + - name: Lint YAML 🔍 + uses: docker://cytopia/yamllint:1@sha256:596fb19eb71e55ba5b2fa56d8c18a615ec82adc8d3bf2d73918cb78c8f3240fb + with: + args: . + + - name: Security scan 🔒 + uses: docker://semgrep/semgrep:1.167.0@sha256:06938c1f365d3f67b8cedd8bc117607ae64253f88a0e768e9da9408548927dd6 + with: + # Set the entrypoint explicitly: this image has no ENTRYPOINT, and + # pinning it keeps the step correct if a digest bump reintroduces one. + entrypoint: semgrep + args: --config=auto --error autodoc: timeout-minutes: 5 @@ -44,11 +47,6 @@ jobs: - name: Checkout uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - - name: Install uv - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 - with: - enable-cache: false - - name: Regenerate documentation run: ./generate-doc.sh diff --git a/generate-doc.py b/generate-doc.py deleted file mode 100644 index 2676568..0000000 --- a/generate-doc.py +++ /dev/null @@ -1,74 +0,0 @@ -# /// script -# requires-python = ">=3.11" -# dependencies = ["pyyaml"] -# /// -"""Generate the README Inputs table from action.yml. - -Replaces tj-actions/auto-doc: parses the action's inputs and rewrites the -GitHub-flavoured Markdown table between the AUTO-DOC-INPUT markers in README.md. -""" - -import pathlib -import re - -import yaml - -ROOT = pathlib.Path(__file__).parent -ACTION = ROOT / "action.yml" -README = ROOT / "README.md" -START = "" -END = "" - - -def render_description(text: str) -> str: - """Render an action.yml description as a single Markdown table cell. - - Wrapped prose lines are joined with spaces; `*`-prefixed lines (e.g. the - list of actions) become `
`-separated bullets so they render as a list - inside the cell rather than a run of literal asterisks. - """ - parts: list[str] = [] - for raw in text.strip().splitlines(): - line = raw.strip() - if not line: - continue - if line.startswith("* "): - parts.append("
• " + line[2:].strip()) - elif parts: - parts[-1] += " " + line - else: - parts.append(line) - return "".join(parts) - - -def render_table(inputs: dict) -> str: - rows = [ - "| Input | Type | Required | Default | Description |", - "| --- | --- | --- | --- | --- |", - ] - for name in sorted(inputs): - spec = inputs[name] or {} - required = "true" if spec.get("required") else "false" - default = spec.get("default") - default_cell = f"`{default}`" if default not in (None, "") else "" - description = render_description(str(spec.get("description", ""))) - rows.append(f"| `{name}` | string | {required} | {default_cell} | {description} |") - return "\n".join(rows) - - -def main() -> None: - action = yaml.safe_load(ACTION.read_text(encoding="utf-8")) - table = render_table(action.get("inputs") or {}) - block = f"{START}\n\n{table}\n\n{END}" - - readme = README.read_text(encoding="utf-8") - pattern = re.escape(START) + r".*?" + re.escape(END) - if not re.search(pattern, readme, flags=re.DOTALL): - raise SystemExit("AUTO-DOC-INPUT markers not found in README.md") - - new = re.sub(pattern, lambda _: block, readme, flags=re.DOTALL) - README.write_text(new, encoding="utf-8", newline="\n") - - -if __name__ == "__main__": - main() diff --git a/generate-doc.sh b/generate-doc.sh index feb77bb..80a22af 100755 --- a/generate-doc.sh +++ b/generate-doc.sh @@ -1,7 +1,128 @@ #!/bin/bash +# Generate the README Inputs table from action.yml. +# +# Replaces tj-actions/auto-doc: parses the action's inputs and rewrites the +# GitHub-flavoured Markdown table between the AUTO-DOC-INPUT markers in +# README.md. Pure bash + awk so the repo needs no Python/uv toolchain. + set -euo pipefail -command -v uv >/dev/null 2>&1 || { echo "uv is not installed: https://docs.astral.sh/uv/" >&2; exit 1; } +ROOT="$(cd "$(dirname "$0")" && pwd)" +ACTION="${ROOT}/action.yml" +README="${ROOT}/README.md" +START="" +END="" + +if ! grep -qF "${START}" "${README}" || ! grep -qF "${END}" "${README}"; then + echo "AUTO-DOC-INPUT markers not found in README.md" >&2 + exit 1 +fi + +# Render the README inputs table from action.yml. awk emits one finished +# Markdown row per input; only the constrained GitHub Action inputs schema is +# handled (2-space input names, 4-space properties, optional `|`/`>` +# block-scalar descriptions). +rows="$( + awk ' + function trim(s){ sub(/^[ \t]+/, "", s); sub(/[ \t]+$/, "", s); return s } + # unwrap a scalar: strip matching surrounding quotes (inside which "#" is + # literal), otherwise strip an inline " # comment" from a plain scalar. + function scalar(s){ + s = trim(s) + if (s ~ /^".*"$/ || s ~ /^'"'"'.*'"'"'$/) return substr(s, 2, length(s) - 2) + sub(/[ \t]+#.*$/, "", s) + return trim(s) + } + # render a (newline-joined) description into a single Markdown table cell: + # wrapped prose joins with spaces; "* " lines become
-separated bullets. + function render(raw, nl, i, line, arr, parts, np, out){ + np = 0; nl = split(raw, arr, "\n") + for (i = 1; i <= nl; i++) { + line = trim(arr[i]) + if (line == "") continue + if (substr(line, 1, 2) == "* ") parts[++np] = "
\342\200\242 " trim(substr(line, 3)) + else if (np > 0) parts[np] = parts[np] " " line + else parts[++np] = line + } + out = "" + for (i = 1; i <= np; i++) out = out parts[i] + return out + } + # emit the finished Markdown row; the downstream sort orders rows by the + # input name that follows the identical "| `" prefix. + function flush( defcell){ + if (cur == "") return + defcell = (def == "") ? "" : "`" def "`" + printf "| `%s` | string | %s | %s | %s |\n", cur, req, defcell, render(desc) + } + + BEGIN { in_inputs = 0; collecting = 0; cur = "" } + { + line = $0; sub(/\r$/, "", line) + p = match(line, /[^ ]/); ind = (p ? p - 1 : length(line)) + blank = (line ~ /^[ \t]*$/) + + if (collecting) { + if (blank) { desc = desc "\n"; next } + if (ind > blockind) { desc = desc (desc == "" ? "" : "\n") line; next } + collecting = 0 # dedent: fall through and reprocess this line + } + + if (blank) next + if (line ~ /^[ \t]*#/) next # full-line comment + + if (ind == 0) { # top-level key + flush(); cur = "" + in_inputs = (line ~ /^inputs:[ \t]*$/) ? 1 : 0 + next + } + if (!in_inputs) next + + if (ind == 2) { # new input name + flush() + key = line; sub(/:.*$/, "", key); cur = trim(key) + req = "false"; def = ""; desc = "" + next + } + if (ind >= 4 && cur != "") { # input property + prop = trim(line) + if (prop ~ /^description:/) { + val = prop; sub(/^description:[ \t]*/, "", val) + if (val ~ /^[|>]/) { collecting = 1; blockind = ind; desc = "" } + else desc = scalar(val) + } else if (prop ~ /^default:/) { + val = prop; sub(/^default:[ \t]*/, "", val); def = scalar(val) + } else if (prop ~ /^required:/) { + val = prop; sub(/^required:[ \t]*/, "", val); val = tolower(scalar(val)) + req = (val == "true" || val == "yes" || val == "on") ? "true" : "false" + } + next + } + } + END { flush() } + ' "${ACTION}" | LC_ALL=C sort +)" + +# Assemble the table; the rows already carry their Markdown formatting. +table="| Input | Type | Required | Default | Description | +| --- | --- | --- | --- | --- |" +if [ -n "${rows}" ]; then + table="${table} +${rows}" +fi -exec uv run "$(dirname "$0")/generate-doc.py" +# Splice the rendered block between the markers (inclusive). The block is read +# from a file rather than passed via `awk -v`, which rejects embedded newlines. +tmp="$(mktemp)" +blockfile="$(mktemp)" +trap 'rm -f "${tmp}" "${blockfile}"' EXIT +printf '%s\n\n%s\n\n%s\n' "${START}" "${table}" "${END}" > "${blockfile}" +awk -v start="${START}" -v end="${END}" -v blockfile="${blockfile}" ' + index($0, start) { while ((getline l < blockfile) > 0) print l; close(blockfile); skip = 1; next } + skip && index($0, end) { skip = 0; next } + skip { next } + { print } +' "${README}" > "${tmp}" +mv "${tmp}" "${README}" +# tmp + blockfile are cleaned by the EXIT trap above.