e3097df6a4
`docs/METHOD.md` asks each deviation to name the reference fact and why benilla differs, and `docs/DEVIATIONS.md` is generated from those comments. The generator wrote only a comment's lead: its first sentence, extended to at least 70 characters. A comment that gives its reason first lost the reference fact from the list, as the Warden entry did after its reason was restored. The list now writes each comment's whole paragraph, the one the reason check already reads. The lead and sentence helpers are gone with it. Tested by regenerating the list: every entry keeps its full paragraph, and the Warden entry shows the reference's handling of `SMSG_WARDEN_DATA` again. Fixes #2880 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
442 lines
22 KiB
Bash
Executable File
442 lines
22 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Generate docs/MAP.md, the map of what is built, and docs/DEVIATIONS.md, where benilla differs from
|
|
# 1.12.1, both from what is on disk.
|
|
#
|
|
# Regenerated and committed at every land; run it by hand only to look, never edit the output. No
|
|
# timestamps, so an identical tree gives an identical file. No `pipefail`/`-e`: a grep that finds
|
|
# nothing (a single-file lib, a crate with no bins) must not abort the generator. Bash and POSIX
|
|
# awk only (macOS's awk is the floor): no gawk extensions, no new dependencies.
|
|
set -u
|
|
cd "$(dirname "$0")/.."
|
|
|
|
# `--check` runs every stage but writes the docs into a scratch folder, for the gates.
|
|
out=docs
|
|
if [ "${1:-}" = "--check" ]; then out=$(mktemp -d "${TMPDIR:-/tmp}/genmap-out.XXXXXX"); trap 'rm -rf "$out"' EXIT; fi
|
|
|
|
# Extract a quoted `key = "value"` field from a Cargo.toml.
|
|
field() { grep -m1 "^$2" "$1" 2>/dev/null | sed -E "s/^$2[[:space:]]*=[[:space:]]*\"//; s/\".*$//"; }
|
|
|
|
{
|
|
echo "# benilla — generated map"
|
|
echo
|
|
echo "> **GENERATED** by \`scripts/genmap.sh\` from the code — do not edit by hand; rerun the script"
|
|
echo "> when what's built changes. *What-is* only: the *what-changed* is in git. No timestamp —"
|
|
echo "> identical code yields an identical map."
|
|
echo
|
|
|
|
echo "## Crates"
|
|
echo
|
|
for ct in crates/*/Cargo.toml; do
|
|
name=$(field "$ct" name)
|
|
desc=$(field "$ct" description)
|
|
# Crates here write `bevy.workspace = true`: the trailing class accepts `.`, whitespace and `=`
|
|
# (`bevy = { … }`, `bevy="…"` too) and still rejects a `bevyfoo` crate.
|
|
if grep -qE '^(bevy|bevy_egui|avian3d)[.[:space:]=]' "$ct"; then bevy="Bevy"; else bevy="no Bevy"; fi
|
|
echo "- **$name** ($bevy) — ${desc:-—}"
|
|
done
|
|
echo
|
|
|
|
echo "## App subsystems — Bevy plugins in load order (\`crates/benilla-app/src/lib.rs\`)"
|
|
echo
|
|
# Accept bare (`add_plugins(FooPlugin)`) and path-qualified (`add_plugins(foo::FooPlugin)`)
|
|
# registrations; print the plugin type name either way.
|
|
grep -oE 'add_plugins\(([a-z_]+::)*[A-Za-z_]+Plugins?' crates/benilla-app/src/lib.rs \
|
|
| sed -E 's/add_plugins\(//; s/([a-z_]+::)+//' | awk '!seen[$0]++ {print "- " $0}'
|
|
echo
|
|
|
|
# `lib.rs` adds each dev group in one line, so their members are read out of `dev.rs`.
|
|
echo "### Behind the \`dev\` feature (\`crates/benilla-app/src/dev.rs\` — compiled out by \`--no-default-features\`)"
|
|
echo
|
|
grep -oE 'add_plugins\(([a-z_]+::)*[A-Za-z_]+Plugins?' crates/benilla-app/src/dev.rs \
|
|
| sed -E 's/add_plugins\(//; s/([a-z_]+::)+//' | awk '!seen[$0]++ {print "- " $0}'
|
|
echo
|
|
|
|
echo "## Modules (top-level, per crate)"
|
|
echo
|
|
for lib in crates/*/src/lib.rs crates/*/src/main.rs; do
|
|
[ -f "$lib" ] || continue
|
|
crate=$(echo "$lib" | sed -E 's@crates/([^/]+)/.*@\1@')
|
|
mods=$(grep -E '^[[:space:]]*(pub(\([a-z]+\))? )?mod [a-z_0-9]+;' "$lib" \
|
|
| sed -E 's/.*mod ([a-z_0-9]+);.*/\1/' | sort | tr '\n' ' ')
|
|
[ -n "$mods" ] && echo "- **$crate** ($(basename "$lib")): $mods"
|
|
done
|
|
echo
|
|
|
|
echo "## CLI binaries"
|
|
echo
|
|
# Both forms cargo accepts: `src/bin/<name>.rs` and `src/bin/<name>/main.rs`.
|
|
for b in crates/*/src/bin/*.rs crates/*/src/bin/*/main.rs; do
|
|
[ -f "$b" ] || continue
|
|
crate=$(echo "$b" | sed -E 's@crates/([^/]+)/.*@\1@')
|
|
case "$b" in
|
|
*/main.rs) name=$(basename "$(dirname "$b")") ;;
|
|
*) name=$(basename "$b" .rs) ;;
|
|
esac
|
|
echo "- \`$name\` (in $crate)"
|
|
done | LC_ALL=C sort
|
|
echo
|
|
|
|
# Shaders are embedded and addressed by their owning crate, so the crate is part of the name.
|
|
echo "## WGSL shaders (\`crates/*/src/shaders/\`, embedded)"
|
|
echo
|
|
for s in crates/*/src/shaders/*.wgsl; do
|
|
[ -f "$s" ] || continue
|
|
crate=$(echo "$s" | sed -E 's@crates/([^/]+)/.*@\1@' | tr - _)
|
|
echo "- \`embedded://$crate/shaders/$(basename "$s")\`"
|
|
done
|
|
echo
|
|
|
|
echo "## Instruments — \`\$WOW_*\` switches (env-var read sites)"
|
|
echo
|
|
echo "> The headless/dev instrument fleet, discovered from the code: every \`WOW_*\` env var"
|
|
echo "> some \`.rs\` reads, and where it's read — the doc comment at the read site is the"
|
|
# Name no dev keys here: they are listed once, in the debug panel's footer.
|
|
echo "> semantics. (The in-window surfaces — the Ctrl+Shift dev-chord overlays — are plugins"
|
|
echo "> above; this indexes the switches that don't announce themselves.)"
|
|
echo
|
|
# Match any quoted "WOW_*" literal: reads also go through helpers (`knob("WOW_FX_AGE", …)`), and
|
|
# doc comments write the unquoted form. The probe registry `capture/probe_env.rs` is a table,
|
|
# not a read site, so it is left out.
|
|
grep -rHoE '"WOW_[A-Z0-9_]+"' crates --include='*.rs' 2>/dev/null \
|
|
| grep -v '^crates/benilla-app/src/capture/probe_env\.rs:' \
|
|
| sed -E 's@^crates/@@; s/:"/\t/; s/"$//' \
|
|
| awk -F'\t' '{print $2 "\t" $1}' | sort -u \
|
|
| awk -F'\t' '$1 != v { if (v) print line; v = $1; line = "- `" $1 "` — " $2; next }
|
|
{ line = line ", " $2 }
|
|
END { if (v) print line }'
|
|
echo
|
|
|
|
# Every script, with the first sentence of its own header.
|
|
echo "## Scripts (\`scripts/\`)"
|
|
echo
|
|
for f in scripts/*.py scripts/*.sh; do
|
|
[ -f "$f" ] || continue
|
|
# The summary: the header's first non-empty content after the shebang, comment or docstring
|
|
# markers stripped, joined until its first blank line, cut at a sentence end or ~150 chars.
|
|
summary=$(LC_ALL=C awk '
|
|
NR == 1 && /^#!/ { next }
|
|
/^@echo off/ { next }
|
|
{ line = $0
|
|
sub(/^[[:space:]]*(r?"""|#|\/\/|rem)[[:space:]]?/, "", line)
|
|
if (line ~ /^[[:space:]]*$/) { if (got) exit; else next }
|
|
if (line ~ /^"""/) exit
|
|
got = 1; out = out (out ? " " : "") line }
|
|
END { print out }' "$f" | sed -E 's/\*\*//g; s/^[A-Za-z0-9_.\/-]+ (—|--) //; s/^([^.]*[.!?])([[:space:]]|$).*/\1/; s/^(.{150}).+/\1…/')
|
|
echo "- \`${f#scripts/}\` — $summary"
|
|
done
|
|
echo
|
|
|
|
} > "$out/MAP.md"
|
|
|
|
echo "genmap: wrote $out/MAP.md"
|
|
|
|
# ── docs/DEVIATIONS.md ─────────────────────────────────────────────────────────────────────────────
|
|
# Every place benilla writes down that it differs from 1.12.1: the `Deviates` rows of the CVar table,
|
|
# and each `Deviation:` comment in the crates (Rust and WGSL `//`, Lua `--`, XML `<!-- -->`). A
|
|
# deviation's wording is read from where it lives, so the list cannot drift from the code.
|
|
tmp=$(mktemp -d "${TMPDIR:-/tmp}/genmap.XXXXXX") || exit 1
|
|
trap 'rm -rf "$tmp"; [ "$out" = docs ] || rm -rf "$out"' EXIT
|
|
|
|
# Test code is left out: a file under `tests/`, named `tests.rs` or `*_tests.rs`, or declared by a
|
|
# `#[cfg(test)] mod x;` (listed here, resolved the way rustc does, `#[path]` included), and a
|
|
# `#[cfg(test)] mod x { … }` body (skipped in the extractor below, which closes it at the first `}`
|
|
# in the module's own column; rustfmt keeps it there).
|
|
grep -rlE --include='*.rs' 'cfg\(test\)' crates 2>/dev/null | LC_ALL=C sort | xargs awk '
|
|
FNR == 1 { pend = 0; path = ""; base = FILENAME; sub(/[^\/]*$/, "", base)
|
|
stem = FILENAME; sub(/.*\//, "", stem); sub(/\.rs$/, "", stem)
|
|
own = (stem == "mod" || stem == "lib" || stem == "main") ? base : base stem "/" }
|
|
{ t = $0; sub(/^[ \t]+/, "", t) }
|
|
t == "#[cfg(test)]" { pend = 1; path = ""; next }
|
|
pend && t ~ /^#\[path = "/ { path = t; sub(/^#\[path = "/, "", path); sub(/".*/, "", path); next }
|
|
pend && t ~ /^#\[/ { next }
|
|
pend {
|
|
pend = 0
|
|
if (t ~ /^(pub(\([a-z]+\))? )?mod [A-Za-z_0-9]+;/) {
|
|
name = t; sub(/^(pub(\([a-z]+\))? )?mod /, "", name); sub(/;.*/, "", name)
|
|
if (path != "") print base path
|
|
else { print own name ".rs"; print own name "/mod.rs" }
|
|
}
|
|
}' > "$tmp/testmods" 2>/dev/null
|
|
|
|
# Stage 1: one TSV row per `Deviation:` comment: crate, module, path in the crate, line, sentence.
|
|
LC_ALL=C grep -rIlE 'Deviation:' crates 2>/dev/null | LC_ALL=C sort | grep -vE '/tests/|(^|/)tests\.rs$|_tests\.rs$' \
|
|
| xargs env LC_ALL=C awk -v list="$tmp/testmods" '
|
|
function indent_of(s, t) { t = s; sub(/[^ \t].*$/, "", t); return length(t) }
|
|
function trim(s) { sub(/^[ \t]+/, "", s); sub(/[ \t]+$/, "", s); return s }
|
|
# Where a `Deviation:` label starts in a comment, 0 if none: one inside backticks is a mention.
|
|
function label_at(c, at, off, rest) {
|
|
off = 0; rest = c
|
|
while ((at = index(rest, "Deviation:")) > 0) {
|
|
if (substr(rest, at - 1, 1) != "`" || at == 1) return off + at
|
|
off += at + 9; rest = substr(rest, at + 10)
|
|
}
|
|
return 0
|
|
}
|
|
function module_of(r, rest, m) {
|
|
if (r ~ /^src\//) {
|
|
rest = substr(r, 5)
|
|
if (index(rest, "/")) { sub(/\/.*/, "", rest); return rest }
|
|
sub(/\.[^.]*$/, "", rest)
|
|
return (rest == "lib" || rest == "main") ? "(crate root)" : rest
|
|
}
|
|
if (r ~ /^assets\//) { rest = substr(r, 8); m = "assets"; if (index(rest, "/")) { sub(/\/.*/, "", rest); m = m "/" rest } return m }
|
|
sub(/\/.*/, "", r); return r
|
|
}
|
|
function process(f, n, i, j, s, t, ext, xin, skipping, tindent, pend, p, q, c, kind, code, marker, text, lab, cr, rel, first, cont, full) {
|
|
n = 0; delete L; delete K; delete C; delete Q; delete XI; delete T
|
|
while ((getline s < f) > 0) L[++n] = s
|
|
close(f)
|
|
ext = f; sub(/.*\./, "", ext)
|
|
if (ext != "rs" && ext != "wgsl" && ext != "xml" && ext != "lua") return
|
|
cr = f; sub(/^crates\//, "", cr); sub(/\/.*/, "", cr)
|
|
rel = f; sub(/^crates\/[^\/]*\//, "", rel)
|
|
xin = 0; skipping = 0; pend = 0
|
|
for (i = 1; i <= n; i++) {
|
|
s = L[i]; t = s; sub(/^[ \t]+/, "", t)
|
|
T[i] = 0
|
|
if (ext == "rs") {
|
|
if (skipping) { T[i] = 1; if (indent_of(s) == tindent && t ~ /^}/) skipping = 0 }
|
|
else if (t == "#[cfg(test)]") pend = 1
|
|
else if (pend) {
|
|
if (t !~ /^#\[/) {
|
|
pend = 0
|
|
if (t ~ /^(pub(\([a-z]+\))? )?mod [A-Za-z_0-9]+ *\{[ \t]*$/) { skipping = 1; tindent = indent_of(s); T[i] = 1 }
|
|
}
|
|
}
|
|
}
|
|
XI[i] = xin; K[i] = ""; C[i] = ""; Q[i] = 0
|
|
if (xin) {
|
|
K[i] = "x"; c = s
|
|
if ((q = index(c, "-->")) > 0) { c = substr(c, 1, q - 1); xin = 0 }
|
|
C[i] = trim(c)
|
|
} else if (ext == "xml" || ext == "lua") {
|
|
p = index(s, "<!--")
|
|
if (p > 0 && ext == "xml") {
|
|
K[i] = "x"; c = substr(s, p + 4); Q[i] = (trim(substr(s, 1, p - 1)) != "")
|
|
if ((q = index(c, "-->")) > 0) c = substr(c, 1, q - 1); else xin = 1
|
|
C[i] = trim(c)
|
|
} else if ((p = index(s, "--")) > 0) {
|
|
K[i] = "--"; c = substr(s, p + 2); sub(/^-+/, "", c); Q[i] = (trim(substr(s, 1, p - 1)) != "")
|
|
C[i] = trim(c)
|
|
}
|
|
} else {
|
|
p = index(s, "//")
|
|
if (p > 0) {
|
|
c = substr(s, p + 2); marker = "//"
|
|
if (substr(c, 1, 1) == "/") { c = substr(c, 2); marker = "///" }
|
|
else if (substr(c, 1, 1) == "!") { c = substr(c, 2); marker = "//!" }
|
|
K[i] = marker; Q[i] = (trim(substr(s, 1, p - 1)) != "")
|
|
C[i] = trim(c)
|
|
}
|
|
}
|
|
}
|
|
for (i = 1; i <= n; i++) {
|
|
if (K[i] == "" || (lab = label_at(C[i])) == 0) continue
|
|
text = trim(substr(C[i], lab + 10))
|
|
# Continue over the comment lines that follow, to its paragraph end: a blank comment line,
|
|
# a list item, a code fence or the next `Deviation:` ends it.
|
|
for (j = i + 1; j <= n; j++) {
|
|
if (K[i] == "x") { if (!XI[j] || K[j] != "x") break }
|
|
else if (K[j] != K[i] || Q[j]) break
|
|
cont = C[j]
|
|
if (cont == "" || cont ~ /^([-*] |```)/ || label_at(cont)) break
|
|
text = text " " cont
|
|
}
|
|
full = trim(text); gsub(/\t/, " ", full)
|
|
# The whole paragraph is written, so the list carries the reference fact with the reason.
|
|
text = trim(text)
|
|
gsub(/\t/, " ", text)
|
|
# A rustdoc link [`x`] or [`x`](path) reads as `x`.
|
|
while (match(text, /\[`[^`]*`\]\([^)]*\)/)) { q = index(substr(text, RSTART), "]("); text = substr(text, 1, RSTART - 1) substr(text, RSTART + 1, q - 2) substr(text, RSTART + RLENGTH) }
|
|
gsub(/\[`/, "`", text); gsub(/`\]/, "`", text)
|
|
# A plain lowercase first word starts a sentence; an identifier (`pfUI`, `a.b`) is left alone.
|
|
if (match(text, /^[a-z]+/) && (substr(text, RLENGTH + 1, 1) == "" || substr(text, RLENGTH + 1, 1) ~ /^[ ;,-]$/)) text = toupper(substr(text, 1, 1)) substr(text, 2)
|
|
sub(/[.;,:]+$/, "", text)
|
|
if (text == "") text = "(see the comment)"
|
|
if (T[i] || skipfile) { skipped++; continue }
|
|
printf "%s\t%s\t%s\t%d\t%s\t%s\n", cr, module_of(rel), rel, i, text, full
|
|
}
|
|
}
|
|
BEGIN {
|
|
while ((getline s < list) > 0) tests[s] = 1
|
|
close(list)
|
|
for (a = 1; a < ARGC; a++) { skipfile = (ARGV[a] in tests); process(ARGV[a]) }
|
|
if (skipped) print "genmap: " skipped " deviation comment(s) in test code left out" > "/dev/stderr"
|
|
exit
|
|
}' > "$tmp/code.tsv"
|
|
LC_ALL=C sort -t "$(printf '\t')" -k1,1 -k2,2 -k3,3 -k4,4n "$tmp/code.tsv" > "$tmp/code.sorted"
|
|
|
|
# Stage 2: the `Deviates` rows of the CVar table. The whole file is scanned as a token stream
|
|
# (comments, strings and char literals skipped), so a call may sit on one line or twelve. A literal
|
|
# is read with Rust's `\`-newline continuation and `\"`/`\\` escapes; a constant argument
|
|
# (`crate::realmlist::CVAR_REALMLIST`) is resolved to its `const NAME: &str = "…";` in the crates.
|
|
# `cvars::tests::the_deviates_rows_are_what_the_generator_reads` pins this reading to the table.
|
|
LC_ALL=C awk '
|
|
{ src = src $0 "\n" }
|
|
function resolve(e, id, cmd, line) {
|
|
id = e; sub(/.*::/, "", id)
|
|
if (id !~ /^[A-Za-z_][A-Za-z_0-9]*$/) return "`" e "`"
|
|
cmd = "grep -rhE --include=\"*.rs\" \"const " id ": &str = \\\"\" crates 2>/dev/null"
|
|
cmd | getline line; close(cmd)
|
|
if (line ~ /= "/) { sub(/^[^"]*"/, "", line); sub(/";.*$/, "", line); return line }
|
|
return "`" e "`"
|
|
}
|
|
function str_at(i, out, c, d) { # i is on the opening quote; returns the decoded text, sets endi
|
|
out = ""; i++
|
|
while (i <= N) {
|
|
c = substr(src, i, 1)
|
|
if (c == "\"") { endi = i; return out }
|
|
if (c == "\\") {
|
|
d = substr(src, i + 1, 1)
|
|
if (d == "\n") { i += 2; while (substr(src, i, 1) ~ /[ \t\n]/) i++; continue }
|
|
if (d == "\"" || d == "\\" || d == "'"'"'") { out = out d; i += 2; continue }
|
|
if (d == "n") { out = out " "; i += 2; continue }
|
|
}
|
|
out = out c; i++
|
|
}
|
|
endi = N; return out
|
|
}
|
|
function skip_noise(i, c, d) { # returns the index past a comment, or i itself
|
|
c = substr(src, i, 1); d = substr(src, i + 1, 1)
|
|
if (c == "/" && d == "/") { while (i <= N && substr(src, i, 1) != "\n") i++; return i }
|
|
if (c == "/" && d == "*") { i += 2; while (i <= N && substr(src, i, 2) != "*/") i++; return i + 2 }
|
|
return i
|
|
}
|
|
function call(i, depth, c, k, arg, a, isstr, lit, expr, j) {
|
|
# i is just past the opening paren
|
|
depth = 1; k = 1; expr = ""; lit = 0; delete A
|
|
while (i <= N && depth > 0) {
|
|
j = skip_noise(i); if (j != i) { i = j; continue }
|
|
c = substr(src, i, 1)
|
|
if (c == "\"") { A[k] = str_at(i); lit = 1; i = endi + 1; isl[k] = 1; continue }
|
|
if (c == "(" || c == "[" || c == "{") depth++
|
|
if (c == ")" || c == "]" || c == "}") { depth--; if (depth == 0) break }
|
|
if (c == "," && depth == 1) { k++; i++; continue }
|
|
if (c !~ /[ \t\n]/) { if (!isl[k]) A[k] = A[k] c }
|
|
i++
|
|
}
|
|
for (a = 1; a <= 4; a++) {
|
|
arg = A[a]
|
|
if (!isl[a]) arg = resolve(arg)
|
|
gsub(/[\t\n]/, " ", arg)
|
|
printf "%s%s", arg, (a < 4 ? "\t" : "\n")
|
|
}
|
|
delete isl
|
|
return i
|
|
}
|
|
END {
|
|
N = length(src); i = 1; prev = ""
|
|
while (i <= N) {
|
|
j = skip_noise(i); if (j != i) { i = j; continue }
|
|
c = substr(src, i, 1)
|
|
if (c == "\"") { str_at(i); i = endi + 1; continue }
|
|
if (c == "'"'"'") {
|
|
if (substr(src, i + 2, 1) == "'"'"'") i += 3
|
|
else if (substr(src, i + 1, 1) == "\\") { i += 2; while (i <= N && substr(src, i, 1) != "'"'"'") i++; i++ }
|
|
else i++
|
|
continue
|
|
}
|
|
if (c ~ /[A-Za-z_]/) {
|
|
id = c; i++
|
|
while (i <= N && substr(src, i, 1) ~ /[A-Za-z_0-9]/) { id = id substr(src, i, 1); i++ }
|
|
if (id == "deviates" && prev != "fn" && substr(src, i, 1) == "(") i = call(i + 1)
|
|
prev = id; continue
|
|
}
|
|
i++
|
|
}
|
|
}' crates/benilla-app/src/cvars/table.rs 2>/dev/null \
|
|
| LC_ALL=C sort -f -t "$(printf '\t')" -k1,1 > "$tmp/cvars.tsv"
|
|
|
|
# Markdown-escape outside code spans (an odd backtick count means no spans: every backtick escaped).
|
|
MDESC='
|
|
function mdesc(t, cell, out, k, ch, tick, n) {
|
|
n = gsub(/`/, "`", t); out = ""; tick = 0
|
|
for (k = 1; k <= length(t); k++) {
|
|
ch = substr(t, k, 1)
|
|
if (ch == "`") { if (n % 2) out = out "\\`"; else { tick = !tick; out = out ch } continue }
|
|
if (tick) { out = out ((cell && ch == "|") ? "\\|" : ch); continue }
|
|
if (ch ~ /[\\*_<\[\]&]/ || (cell && ch == "|")) out = out "\\"
|
|
out = out ch
|
|
}
|
|
return out
|
|
}'
|
|
|
|
{
|
|
echo "# benilla — generated deviations"
|
|
echo
|
|
echo "> **GENERATED** by \`scripts/genmap.sh\` from the code — do not edit by hand; rerun the script"
|
|
echo "> when a deviation is added or reworded. No timestamp — identical code yields an identical file."
|
|
echo
|
|
echo "benilla is the 1.12.1 client reimplemented: what the reference does is what benilla does. Where it"
|
|
echo "differs on purpose, that is a deviation, the maintainer's call alone (\`docs/METHOD.md\`), and it is"
|
|
echo "written down where it lives: a setting's \`Deviates\` row in the CVar table, or a \`Deviation:\` comment"
|
|
echo "at the code, the Lua or the XML that differs. This file gathers them all, regenerated at every land,"
|
|
echo "so the list is read from the code and cannot drift from it. Test code is left out, and so are the"
|
|
echo "CVars that are benilla's own knobs with no 1.12.1 counterpart (\`ours\` rows)."
|
|
echo
|
|
echo "## Settings that ship off the reference default"
|
|
echo
|
|
echo "A setting boots at the reference's default unless it is listed here (\`docs/METHOD.md\`)."
|
|
echo
|
|
echo "| CVar | 1.12.1 | benilla | Why |"
|
|
echo "| --- | --- | --- | --- |"
|
|
LC_ALL=C awk -F '\t' "$MDESC"'
|
|
function code(v) { return "`" (v == "" ? "\"\"" : v) "`" }
|
|
{ printf "| %s | %s | %s | %s |\n", code($1), code($3), code($2), mdesc($4, 1) }' "$tmp/cvars.tsv"
|
|
echo
|
|
echo "## Deviations in the code"
|
|
echo
|
|
echo "Each entry is the first sentence of a \`Deviation:\` comment, linking to where it lives."
|
|
echo
|
|
LC_ALL=C awk -F '\t' '{ c[$1]++; if (!($1 in seen)) { seen[$1] = 1; order[++n] = $1 } }
|
|
END { for (i = 1; i <= n; i++) printf "- [%s](#%s): %d\n", order[i], order[i], c[order[i]] }' "$tmp/code.sorted"
|
|
echo
|
|
LC_ALL=C awk -F '\t' "$MDESC"'
|
|
$1 != crate { crate = $1; module = ""; printf "### %s\n\n", crate }
|
|
$2 != module { if (module != "") print ""; module = $2; printf "#### %s\n\n", module }
|
|
{ printf "- %s. ([`%s:%d`](../crates/%s/%s#L%d))\n", mdesc($5), $3, $4, $1, $3, $4 }
|
|
END { print "" }' "$tmp/code.sorted"
|
|
} > "$out/DEVIATIONS.md"
|
|
|
|
echo "genmap: wrote $out/DEVIATIONS.md ($(wc -l < "$tmp/code.sorted" | tr -d ' ') code entries, $(wc -l < "$tmp/cvars.tsv" | tr -d ' ') CVar rows)"
|
|
|
|
# ── the reason check ───────────────────────────────────────────────────────────────────────────────
|
|
# `docs/METHOD.md`: a deviation is written down with the reference fact and why we differ. So a
|
|
# `Deviation:` comment carries "because …" somewhere in its paragraph, and a CVar `Deviates` row
|
|
# carries a Why. `scripts/deviation-why.allow` is the ratchet for the comments still waiting for a
|
|
# reason: one `path<TAB>first twelve words` per line. It only shrinks. An entry that gained its reason,
|
|
# or whose comment is gone or reworded, fails until it is removed; a comment off the list without
|
|
# "because" fails; and an entry new against origin/main's copy of the list fails.
|
|
allow=scripts/deviation-why.allow
|
|
haveold=0
|
|
if git cat-file -e "origin/main:$allow" 2>/dev/null; then
|
|
haveold=1
|
|
git show "origin/main:$allow" | grep -vE '^(#|$)' > "$tmp/allow.old" || true
|
|
else
|
|
: > "$tmp/allow.old"
|
|
fi
|
|
grep -vE '^(#|$)' "$allow" 2>/dev/null > "$tmp/allow.now" || : > "$tmp/allow.now"
|
|
LC_ALL=C awk -F '\t' -v haveold="$haveold" -v oldf="$tmp/allow.old" -v nowf="$tmp/allow.now" '
|
|
function key(t, n, w, i, k) { n = split(t, w, " "); k = ""; for (i = 1; i <= n && i <= 12; i++) k = k (i > 1 ? " " : "") w[i]; return k }
|
|
BEGIN {
|
|
while ((getline l < oldf) > 0) old[l] = 1
|
|
close(oldf)
|
|
FS = "\t"
|
|
while ((getline l < nowf) > 0) { listed[l] = 1; order[++nl] = l; if (haveold == 1 && !(l in old)) { print "deviation-why: new on the allowlist, which only shrinks: " l; bad++ } }
|
|
}
|
|
FILENAME ~ /cvars\.tsv$/ { if ($4 == "") { print "deviation-why: the `" $1 "` Deviates row has no Why"; bad++ } next }
|
|
{
|
|
path = "crates/" $1 "/" $3; k = path "\t" key($6)
|
|
seen[k] = 1
|
|
if (tolower($6) ~ /(^|[^a-z])because([^a-z]|$)/) {
|
|
if (k in listed) { print "deviation-why: " path ":" $4 " has its reason now; remove it from the allowlist"; bad++ }
|
|
} else {
|
|
if (++dup[k] > 1) { print "deviation-why: " path ":" $4 " shares its first twelve words with another comment without a reason; reword one"; bad++ }
|
|
if (!(k in listed)) { print "deviation-why: " path ":" $4 " gives no reason; add \"because …\" (" substr($6, 1, 60) "…)"; bad++ }
|
|
}
|
|
}
|
|
END {
|
|
for (i = 1; i <= nl; i++) if (!(order[i] in seen)) { print "deviation-why: stale allowlist entry, remove it: " order[i]; bad++ }
|
|
if (bad) { print "deviation-why: " bad " problem(s); docs/METHOD.md: a deviation names why we differ" > "/dev/stderr"; exit 1 }
|
|
}' "$tmp/cvars.tsv" "$tmp/code.sorted" >&2 || exit 1
|