From 87788c9171b485f78b55f2800ac067be98bf4aa9 Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Tue, 18 Aug 2026 08:28:32 +0800 Subject: [PATCH] Shell formatting --- .mise/config.coverage.toml | 55 +++--- .mise/config.dart.toml | 4 +- .mise/config.dotnet.toml | 4 +- .mise/config.linux.toml | 23 +-- .mise/config.macos.toml | 12 +- .mise/config.maint.toml | 45 ++--- .mise/config.msvc.toml | 5 +- .mise/config.python.toml | 2 +- .mise/config.r.toml | 15 +- .mise/config.toml | 97 +++++++++-- .mise/config.windows.toml | 2 +- .mise/mise.lock | 33 ++++ .mise/shfmt-mise.awk | 161 ++++++++++++++++++ CLAUDE.md | 9 +- .../ws-modules/pic-viewer/tests/show_image.rs | 29 +++- services/ws-server/static/favicon.png | Bin 10683 -> 41271 bytes 16 files changed, 410 insertions(+), 86 deletions(-) create mode 100644 .mise/shfmt-mise.awk diff --git a/.mise/config.coverage.toml b/.mise/config.coverage.toml index 821bb9b1..e5cb754e 100644 --- a/.mise/config.coverage.toml +++ b/.mise/config.coverage.toml @@ -137,7 +137,7 @@ fi host="$(rustc -vV | goawk '/^host:/ { print $2 }')" bin="$(rustc +{{ vars.rust_nightly }} --print sysroot)/lib/rustlib/$host/bin" gut="$covdir/gut.awk" -coreutils cat > "$gut" <<'AWK' +coreutils cat >"$gut" <<'AWK' /^target datalayout/ { next } /^target triple/ { next } /^define/ { print; print "start:"; print " unreachable"; print "}"; skip = 1; next } @@ -145,7 +145,7 @@ skip && /^}/ { skip = 0; next } skip { next } { gsub(/"target-cpu"="[^"]*"/, ""); gsub(/"target-features"="[^"]*"/, ""); print } AWK -: > "$covdir/wasi.lcov" +: >"$covdir/wasi.lcov" for profraw in "$covdir"/*.profraw; do name="$(coreutils basename "$profraw" .profraw)" # Look for the .ll in nightly's new build-dir layout first, then the classic one. @@ -166,25 +166,28 @@ for profraw in "$covdir"/*.profraw; do if [ -z "$ll" ]; then ll="$(find target -path '*/release/deps/*' \\( -name "$name.ll" -o -name "$name-*.ll" \\) -print -quit 2>/dev/null)" fi - if [ -z "$ll" ]; then echo "wasi-cov: no .ll found for $name"; exit 1; fi + if [ -z "$ll" ]; then + echo "wasi-cov: no .ll found for $name" + exit 1 + fi pd="$covdir/$name.profdata" obj="$covdir/$name.o" - goawk -f "$gut" "$ll" > "$covdir/$name.gutted.ll" + goawk -f "$gut" "$ll" >"$covdir/$name.gutted.ll" "$bin/llc" -filetype=obj -mtriple=x86_64-unknown-linux-gnu -o "$obj" "$covdir/$name.gutted.ll" "$bin/llvm-profdata" merge -sparse -o "$pd" "$profraw" - "$bin/llvm-cov" export --format=lcov --instr-profile "$pd" "$obj" >> "$covdir/wasi.lcov" + "$bin/llvm-cov" export --format=lcov --instr-profile "$pd" "$obj" >>"$covdir/wasi.lcov" done # The wasm covmap records reference every source each module linked. # Dependency crates under ~/.cargo/registry and toolchain std under ~/.rustup are not in VCS -- DeepSource # flags them and they skew the aggregate metric -- so keep only workspace records (dropping .cargo/.rustup/rustc # blocks) before merging the wasm lcov into lcov.info. keep="$covdir/keep.awk" -coreutils cat > "$keep" <<'AWK' +coreutils cat >"$keep" <<'AWK' { buf = buf $0 ORS } /^SF:/ { p = substr($0, 4); drop = (index(p, "/.cargo/") || index(p, "/.rustup/") || index(p, "/rustc/")) } /^end_of_record$/ { if (!drop) printf "%s", buf; buf = ""; drop = 0 } AWK -goawk -f "$keep" "$covdir/wasi.lcov" >> lcov.info +goawk -f "$keep" "$covdir/wasi.lcov" >>lcov.info """ shell = "bash -euo pipefail -c" @@ -282,8 +285,14 @@ RUSTFLAGS="-Cinstrument-coverage" cargo build -q -p et-ws-test-server --bin cov- LLVM_PROFILE_FILE="{{ config_root }}/$covdir/cov-server-%p.profraw" ./target/debug/cov-server "$covdir/server-ready" & server_pid=$! trap 'kill "$server_pid" 2>/dev/null || true' EXIT -for _ in $(coreutils seq 1 100); do [ -s "$covdir/server-ready" ] && break; coreutils sleep 0.1; done -[ -s "$covdir/server-ready" ] || { echo "cov-server never became ready"; exit 1; } +for _ in $(coreutils seq 1 100); do + [ -s "$covdir/server-ready" ] && break + coreutils sleep 0.1 +done +[ -s "$covdir/server-ready" ] || { + echo "cov-server never became ready" + exit 1 +} # Export the instrumentation env for the coverage test build. # Trailing-backslash line continuations are banned repo-wide, so each variable is set on its own line; no later @@ -304,7 +313,7 @@ trap - EXIT host="$(rustc +{{ vars.rust_nightly }} -vV | goawk '/^host:/ { print $2 }')" llbin="$(rustc +{{ vars.rust_nightly }} --print sysroot)/lib/rustlib/$host/bin" gut="$covdir/gut.awk" -coreutils cat > "$gut" <<'AWK' +coreutils cat >"$gut" <<'AWK' /^target datalayout/ { next } /^target triple/ { next } /^define/ { print; print "start:"; print " unreachable"; print "}"; skip = 1; next } @@ -336,31 +345,31 @@ fi while IFS= read -r ll; do [ -n "$ll" ] || continue name="$(coreutils basename "$ll" .ll)" - goawk -f "$gut" "$ll" > "$covdir/$name.g.ll" + goawk -f "$gut" "$ll" >"$covdir/$name.g.ll" "$llbin/llc" -filetype=obj -mtriple=x86_64-unknown-linux-gnu -o "$covdir/$name.o" "$covdir/$name.g.ll" objs+=("-object" "$covdir/$name.o") done < "$covdir/all.lcov" +"$llbin/llvm-cov" export --format=lcov --instr-profile "$covdir/agent.profdata" "${objs[@]}" >"$covdir/all.lcov" keep="$covdir/keep.awk" -coreutils cat > "$keep" <<'AWK' +coreutils cat >"$keep" <<'AWK' { buf = buf $0 ORS } /^SF:/ { keep = index($0, want) > 0 } /^end_of_record$/ { if (keep) printf "%s", buf; buf = ""; keep = 0 } AWK coreutils touch lcov.info -goawk -v want="ws-wasm-agent/src/" -f "$keep" "$covdir/all.lcov" >> lcov.info +goawk -v want="ws-wasm-agent/src/" -f "$keep" "$covdir/all.lcov" >>lcov.info # Fold cov-server's own native coverage in from the instrumented run above (its cov-server-*.profraw). # The export object links its whole dep tree, so keep only the cov-server.rs launcher records. csp="$covdir/cov-server" "$llbin/llvm-profdata" merge -sparse -o "$csp.profdata" "$covdir"/cov-server-*.profraw -"$llbin/llvm-cov" export --format=lcov --instr-profile "$csp.profdata" -object target/debug/cov-server > "$csp.lcov" -goawk -v want="ws-test-server/src/bin/cov-server.rs" -f "$keep" "$csp.lcov" >> lcov.info +"$llbin/llvm-cov" export --format=lcov --instr-profile "$csp.profdata" -object target/debug/cov-server >"$csp.lcov" +goawk -v want="ws-test-server/src/bin/cov-server.rs" -f "$keep" "$csp.lcov" >>lcov.info rpt="$covdir/report.txt" -"$llbin/llvm-cov" report --instr-profile "$covdir/agent.profdata" "${objs[@]}" > "$rpt" 2>/dev/null || true +"$llbin/llvm-cov" report --instr-profile "$covdir/agent.profdata" "${objs[@]}" >"$rpt" 2>/dev/null || true rg "ws-wasm-agent/src/lib.rs|Filename|TOTAL" "$rpt" || true """ shell = "bash -euo pipefail -c" @@ -397,7 +406,7 @@ cargo test -p et-ws-pic-viewer --features et-web/coverage --target wasm32-unknow host="$(rustc +{{ vars.rust_nightly }} -vV | goawk '/^host:/ { print $2 }')" llbin="$(rustc +{{ vars.rust_nightly }} --print sysroot)/lib/rustlib/$host/bin" gut="$covdir/gut.awk" -coreutils cat > "$gut" <<'AWK' +coreutils cat >"$gut" <<'AWK' /^target datalayout/ { next } /^target triple/ { next } /^define/ { print; print "start:"; print " unreachable"; print "}"; skip = 1; next } @@ -424,24 +433,24 @@ fi while IFS= read -r ll; do [ -n "$ll" ] || continue name="$(coreutils basename "$ll" .ll)" - goawk -f "$gut" "$ll" > "$covdir/$name.g.ll" + goawk -f "$gut" "$ll" >"$covdir/$name.g.ll" "$llbin/llc" -filetype=obj -mtriple=x86_64-unknown-linux-gnu -o "$covdir/$name.o" "$covdir/$name.g.ll" objs+=("-object" "$covdir/$name.o") done < "$covdir/all.lcov" +"$llbin/llvm-cov" export --format=lcov --instr-profile "$covdir/pic-viewer.profdata" "${objs[@]}" >"$covdir/all.lcov" keep="$covdir/keep.awk" -coreutils cat > "$keep" <<'AWK' +coreutils cat >"$keep" <<'AWK' { buf = buf $0 ORS } /^SF:/ { keep = index($0, want) > 0 } /^end_of_record$/ { if (keep) printf "%s", buf; buf = ""; keep = 0 } AWK coreutils touch lcov.info -goawk -v want="ws-modules/pic-viewer/src/" -f "$keep" "$covdir/all.lcov" >> lcov.info +goawk -v want="ws-modules/pic-viewer/src/" -f "$keep" "$covdir/all.lcov" >>lcov.info rpt="$covdir/report.txt" -"$llbin/llvm-cov" report --instr-profile "$covdir/pic-viewer.profdata" "${objs[@]}" > "$rpt" 2>/dev/null || true +"$llbin/llvm-cov" report --instr-profile "$covdir/pic-viewer.profdata" "${objs[@]}" >"$rpt" 2>/dev/null || true rg "ws-modules/pic-viewer/src/lib.rs|Filename|TOTAL" "$rpt" || true """ shell = "bash -euo pipefail -c" diff --git a/.mise/config.dart.toml b/.mise/config.dart.toml index ae2485df..7ec06c95 100644 --- a/.mise/config.dart.toml +++ b/.mise/config.dart.toml @@ -143,10 +143,10 @@ END { printf("gen:dart-rest storage fixup miss: io=%d get=%d body=%d\\n", io, get, body) > "/dev/stderr" exit 1 } -}' lib/clients/storage.dart | coreutils tr -d '\\r' > "$s/storage.dart" +}' lib/clients/storage.dart | coreutils tr -d '\\r' >"$s/storage.dart" coreutils mv "$s/storage.dart" lib/clients/storage.dart goawk '/^import / && /clients/ { gsub(/_\\.dart/, ".dart") } { print }' lib/rest_client.dart | - coreutils tr -d '\\r' > "$s/rest_client.dart" + coreutils tr -d '\\r' >"$s/rest_client.dart" coreutils mv "$s/rest_client.dart" lib/rest_client.dart dart run build_runner build dart format lib diff --git a/.mise/config.dotnet.toml b/.mise/config.dotnet.toml index 855a5dde..20839535 100644 --- a/.mise/config.dotnet.toml +++ b/.mise/config.dotnet.toml @@ -92,7 +92,7 @@ dotnet workload install wasm-tools --skip-manifest-update # rather than PATH -- verified: the compile succeeds even when cmd can read no PATH whatsoever. publish_args="" if [ "${OS:-}" = "Windows_NT" ]; then - publish_args="-p:PATH=C:\\Windows\\System32" + publish_args="-p:PATH=C:\\Windows\\System32" fi # $publish_args holds zero or one CLI flag; word-splitting is intentional. @@ -115,7 +115,7 @@ dotnet workload install wasm-tools --skip-manifest-update publish_args="" if [ "${OS:-}" = "Windows_NT" ]; then - publish_args="-p:PATH=C:\\Windows\\System32" + publish_args="-p:PATH=C:\\Windows\\System32" fi # $publish_args holds zero or one CLI flag; word-splitting is intentional. diff --git a/.mise/config.linux.toml b/.mise/config.linux.toml index 18c62eed..74d3f3ea 100644 --- a/.mise/config.linux.toml +++ b/.mise/config.linux.toml @@ -101,7 +101,7 @@ fi pkgs="$common $specific" # Azure Linux / openSUSE rename a few DNF_PACKAGES entries. # Remap before the iteration so the checks (and the printed `Install with: ...` hint) refer to the right -# names. Word-by-word in pure bash avoids the regex escape gymnastics that `\b` or awk word-boundary tokens +# names. Word-by-word in pure bash avoids the regex escape gymnastics that `\\b` or awk word-boundary tokens # would need through the TOML basic-string layer. if [ "$arg" = DNF_PACKAGES ] && grep -q '^ID=azurelinux$' /etc/os-release 2>/dev/null; then remapped="" @@ -137,10 +137,11 @@ for pkg in $pkgs; do # Debian/Ubuntu: /etc/ssl/certs/ca-certificates.crt. # Fedora/RHEL/AL2023: /etc/pki/tls/certs/ca-bundle.crt; openSUSE: /etc/ssl/ca-bundle.pem. [ -r /etc/ssl/certs/ca-certificates.crt ] || - [ -r /etc/pki/tls/certs/ca-bundle.crt ] || - [ -r /etc/ssl/ca-bundle.pem ] ;; - libc6-dev|glibc-devel) [ -r /usr/include/stdio.h ] ;; - g++|gcc-c++) command -v g++ >/dev/null 2>&1 ;; + [ -r /etc/pki/tls/certs/ca-bundle.crt ] || + [ -r /etc/ssl/ca-bundle.pem ] + ;; + libc6-dev | glibc-devel) [ -r /usr/include/stdio.h ] ;; + g++ | gcc-c++) command -v g++ >/dev/null 2>&1 ;; # `binutils` provides the assembler/linker (as/ld). # The package itself has no binary called `binutils`. So probe via `as`, which it ships. binutils) command -v as >/dev/null 2>&1 ;; @@ -148,18 +149,20 @@ for pkg in $pkgs; do # Fedora pre-includes it in the base image; Azure Linux base/core does not. So we install it explicitly # on Azure Linux. Debian/Ubuntu get the same headers via libc6-dev's transitive linux-libc-dev. That # path is already checked above. openSUSE names the same package linux-glibc-devel. - kernel-headers|linux-glibc-devel) [ -r /usr/include/linux/limits.h ] ;; - libicu*|icu) + kernel-headers | linux-glibc-devel) [ -r /usr/include/linux/limits.h ] ;; + libicu* | icu) # Try both libicu install layouts. # Debian/Ubuntu install libicu under /usr/lib//. Fedora and Azure Linux put it under /usr/lib64/ directly. coreutils ls /usr/lib/*/libicui18n.so.* >/dev/null 2>&1 || - coreutils ls /usr/lib64/libicui18n.so.* >/dev/null 2>&1 ;; - libatomic*|libgcc-atomic) + coreutils ls /usr/lib64/libicui18n.so.* >/dev/null 2>&1 + ;; + libatomic* | libgcc-atomic) # Fedora and Azure Linux need libatomic explicitly for pnpm's prebuilt binary. # Ubuntu gcc pulls it via libgcc-s1 by default. Same dual-layout split as libicu above. Debian/Ubuntu use # a per-triple subdir vs RPM-family's /usr/lib64. coreutils ls /usr/lib/*/libatomic.so.* >/dev/null 2>&1 || - coreutils ls /usr/lib64/libatomic.so.* >/dev/null 2>&1 ;; + coreutils ls /usr/lib64/libatomic.so.* >/dev/null 2>&1 + ;; *) command -v "$pkg" >/dev/null 2>&1 ;; esac || missing="$missing $pkg" done diff --git a/.mise/config.macos.toml b/.mise/config.macos.toml index cfd07594..9226723b 100644 --- a/.mise/config.macos.toml +++ b/.mise/config.macos.toml @@ -88,7 +88,7 @@ missing="" brewy="" for pkg in $pkgs; do case "$pkg" in - ca-certificates|libc6-dev|libicu*|binutils) cmd="" ;; + ca-certificates | libc6-dev | libicu* | binutils) cmd="" ;; *) cmd="$pkg" ;; esac case "$pkg" in @@ -99,7 +99,10 @@ for pkg in $pkgs; do # The probe via the assembler binary still confirms the assembler is reachable. binutils) command -v as >/dev/null 2>&1 ;; *) command -v "$pkg" >/dev/null 2>&1 ;; - esac || { missing="$missing $pkg"; continue; } + esac || { + missing="$missing $pkg" + continue + } # Reject Homebrew-installed tools, since this repo deliberately avoids Brew. # A tool under a brew prefix means PATH shadowed the Xcode CLT / native one. # Skip the file-based pkgs (no binary to check). @@ -107,8 +110,9 @@ for pkg in $pkgs; do path="$(command -v "$cmd")" || continue real="$(coreutils readlink -f "$path" 2>/dev/null || echo "$path")" case "$real" in - /opt/homebrew/*|/usr/local/Cellar/*|/usr/local/opt/*|/usr/local/Homebrew/*) - brewy="$brewy $cmd($real)" ;; + /opt/homebrew/* | /usr/local/Cellar/* | /usr/local/opt/* | /usr/local/Homebrew/*) + brewy="$brewy $cmd($real)" + ;; esac done if [ -n "$missing" ]; then diff --git a/.mise/config.maint.toml b/.mise/config.maint.toml index 4fde6aed..4e8547be 100644 --- a/.mise/config.maint.toml +++ b/.mise/config.maint.toml @@ -99,7 +99,7 @@ short=$(echo "$commit" | coreutils cut -c1-7) asset="${stem}-${short}.onnx" coreutils mv original.onnx "$asset" -coreutils sha256sum "$asset" > "${asset}.sha256" +coreutils sha256sum "$asset" >"${asset}.sha256" # Sanity-check the computed SHA256 against HF's recorded LFS oid. # The SHA256 we just computed must match HF's recorded LFS oid for the blob; mismatch means the download was @@ -123,7 +123,7 @@ cksum_path="{{ config_root }}/config/upstream-cache/data.toml" mise_path="{{ config_root }}/.mise/config.toml" augtool --transform="Toml incl $cksum_path" --autosave < "${asset}.sha256" +coreutils sha256sum "$asset" >"${asset}.sha256" actual=$(coreutils cut -c1-64 "${asset}.sha256") gh release upload "{{ vars.a_hf_cache_tag }}" --clobber --repo "$GH_REPO" "$asset" "${asset}.sha256" cksum_path="{{ config_root }}/config/upstream-cache/data.toml" augtool --transform="Toml incl $cksum_path" --autosave < "${tarball}.sha256" +coreutils sha256sum "$tarball" >"${tarball}.sha256" actual=$(coreutils cut -c1-64 "${tarball}.sha256") gh release upload "$RP_RELEASE" --clobber --repo "$GH_REPO" "$tarball" "${tarball}.sha256" @@ -240,13 +240,13 @@ gh release upload "$RP_RELEASE" --clobber --repo "$GH_REPO" "$tarball" "${tarbal cksum_path="{{ config_root }}/config/upstream-cache/data.toml" mise_path="{{ config_root }}/.mise/config.toml" augtool --transform="Toml incl $cksum_path" --autosave </dev/null | tr -d '\r' | grep -iE 'MSVC[\\/].*Hostx64' | head -1 || true) + msvc_link=$(where.exe link.exe 2>/dev/null | tr -d '\\r' | grep -iE 'MSVC[\\/].*Hostx64' | head -1 || true) if [ -n "${msvc_link:-}" ]; then export CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER="$msvc_link" fi @@ -348,12 +348,15 @@ else fi case "$triple" in - x86_64-apple-darwin) plat="macos-x64" ;; - aarch64-apple-darwin) plat="macos-arm64" ;; - x86_64-unknown-linux-gnu) plat="linux-x64" ;; - aarch64-unknown-linux-gnu) plat="linux-arm64" ;; - x86_64-pc-windows-*) plat="windows-x64" ;; - *) echo "unsupported host triple: $triple" >&2; exit 1 ;; + x86_64-apple-darwin) plat="macos-x64" ;; + aarch64-apple-darwin) plat="macos-arm64" ;; + x86_64-unknown-linux-gnu) plat="linux-x64" ;; + aarch64-unknown-linux-gnu) plat="linux-arm64" ;; + x86_64-pc-windows-*) plat="windows-x64" ;; + *) + echo "unsupported host triple: $triple" >&2 + exit 1 + ;; esac # Glob the produced binary so the same tar line works on every host. @@ -367,7 +370,7 @@ fi asset="rustpython-${short}-${triple}.tar.gz" tarball="$RP_WORK/$asset" tar -C "$prefix" -czf "$tarball" "$bin_rel" -coreutils sha256sum "$tarball" > "${tarball}.sha256" +coreutils sha256sum "$tarball" >"${tarball}.sha256" actual=$(coreutils cut -c1-64 "${tarball}.sha256") gh release upload "$RP_RELEASE" --clobber --repo "$GH_REPO" "$tarball" "${tarball}.sha256" @@ -387,9 +390,9 @@ if ! command -v augtool >/dev/null 2>&1; then exit 0 fi augtool --transform="Toml incl $mise_path" --autosave < "$shared_venv/bin/pip" <<'PIP_SHIM' + coreutils cat >"$shared_venv/bin/pip" <<'PIP_SHIM' #!/bin/sh exec "$(dirname "$0")/python" -m pip "$@" PIP_SHIM diff --git a/.mise/config.msvc.toml b/.mise/config.msvc.toml index 7af93911..c1d0d885 100644 --- a/.mise/config.msvc.toml +++ b/.mise/config.msvc.toml @@ -70,7 +70,10 @@ if [ ! -f "$script" ]; then rclone copyto "$src" "$script" --http-url https://gist.githubusercontent.com --progress fi actual=$(coreutils sha256sum -b "$script" | coreutils cut -c1-64) -[ "$actual" = "{{ vars.a_pmsvc_sha }}" ] || { echo "portable-msvc.py checksum mismatch: $actual" >&2; exit 1; } +[ "$actual" = "{{ vars.a_pmsvc_sha }}" ] || { + echo "portable-msvc.py checksum mismatch: $actual" >&2 + exit 1 +} # Versions pinned for reproducibility (`--show-versions` lists what the manifest offers). # The rendered msvc.env tracks whatever is installed, so a bump only needs these two flags changed and a # re-run after deleting target/msvc/msvc. diff --git a/.mise/config.python.toml b/.mise/config.python.toml index 80ece8c6..7fc29bab 100644 --- a/.mise/config.python.toml +++ b/.mise/config.python.toml @@ -281,7 +281,7 @@ datamodel-codegen --input ../../target/int-gen/ws.schema.json --output et_ws/mes # Drop the 2-line header that custom-file-header = "#" emits, so the file starts cleanly. # That header is a lone `#` + the blank after it. tail, not sed: datamodel-codegen always emits exactly those # two lines, so a fixed offset is safe. -coreutils tail -n +3 et_ws/messages.py > et_ws/messages.py.tmp +coreutils tail -n +3 et_ws/messages.py >et_ws/messages.py.tmp coreutils mv et_ws/messages.py.tmp et_ws/messages.py # Re-run repo ruff because datamodel-codegen's bundled ruff uses its own defaults. # Those defaults aren't our repo ruff.toml, so re-run repo ruff: `check --fix` diff --git a/.mise/config.r.toml b/.mise/config.r.toml index 5d131cc9..c4cc42c1 100644 --- a/.mise/config.r.toml +++ b/.mise/config.r.toml @@ -27,7 +27,10 @@ description = "Vendor the webR distribution into rdata1's pkg/webr/ (served at / dir = "services/ws-modules/rdata1" run = """ src="$(mise where "http:webr")" -[ -n "$src" ] || { echo "http:webr not installed; run 'MISE_ENV=r mise install' first" >&2; exit 1; } +[ -n "$src" ] || { + echo "http:webr not installed; run 'MISE_ENV=r mise install' first" >&2 + exit 1 +} coreutils rm -rf pkg/webr coreutils mkdir -p pkg/webr coreutils cp -R "$src/." pkg/webr/ @@ -40,7 +43,10 @@ description = "Vendor the webR distribution into rmath1's pkg/webr/ (served at / dir = "services/ws-modules/rmath1" run = """ src="$(mise where "http:webr")" -[ -n "$src" ] || { echo "http:webr not installed; run 'MISE_ENV=r mise install' first" >&2; exit 1; } +[ -n "$src" ] || { + echo "http:webr not installed; run 'MISE_ENV=r mise install' first" >&2 + exit 1 +} coreutils rm -rf pkg/webr coreutils mkdir -p pkg/webr coreutils cp -R "$src/." pkg/webr/ @@ -53,7 +59,10 @@ description = "Vendor the webR distribution into rcomm1's pkg/webr/ (served at / dir = "services/ws-modules/rcomm1" run = """ src="$(mise where "http:webr")" -[ -n "$src" ] || { echo "http:webr not installed; run 'MISE_ENV=r mise install' first" >&2; exit 1; } +[ -n "$src" ] || { + echo "http:webr not installed; run 'MISE_ENV=r mise install' first" >&2 + exit 1 +} coreutils rm -rf pkg/webr coreutils mkdir -p pkg/webr coreutils cp -R "$src/." pkg/webr/ diff --git a/.mise/config.toml b/.mise/config.toml index 124b20ae..38a8d5de 100644 --- a/.mise/config.toml +++ b/.mise/config.toml @@ -59,6 +59,8 @@ action-validator = { version = "latest", os = ["linux", "macos"] } "aqua:EmbarkStudios/cargo-deny" = "latest" "aqua:StyraInc/regal" = "latest" "aqua:koalaman/shellcheck" = "latest" +# shfmt (the `sh` repo's formatter binary) formats the multi-line bash bodies of mise tasks. +"aqua:mvdan/sh" = "latest" "aqua:rhysd/actionlint" = "latest" "aqua:rustwasm/wasm-pack" = "latest" # Vector ships no darwin/amd64 prebuilt, so second-tier macos/x64 builds the same pin from source below. @@ -556,6 +558,11 @@ a_rp_wasm_short = "08a1d6f" # Extra actionlint flags for actionlint-check; empty everywhere except the config.windows.toml override. actionlint_args = "" gh_http = "--http-url https://github.com --progress --ignore-existing" +# Shared shfmt flags for the shfmt-mise-check / shfmt-mise-fmt pair. +# `-ln bash` because a split-out body carries no shebang for shfmt to sniff, `-i 2` to match the indent_size +# .editorconfig sets for every file in the repo, and `-ci` to keep `case` arms indented under their `case` +# (shfmt's default pulls them back to the `case` column, which every body in these configs disagrees with). +shfmt_args = "-ln bash -i 2 -ci" # OpenObserve docker image for the openobserve/o2 dev-observability task. o2_image = "openobserve/openobserve:v0.91.5" # OpenTelemetry Collector image for the o2-hostmetrics / o2-macmon / o2-nvidia / o2-winmetrics tasks. @@ -632,7 +639,7 @@ description = "Format every checked-in .jq file with jqfmt (in-place)" # worth keeping, lift them into the calling task / source-of-truth file before running this. run = """ git ls-files '*.jq' | while read -r f; do - jqfmt -f "$f" -op pipe -ob -ar | goawk '{ sub(/[ \\t]+$/, ""); print }' > "$f.tmp" + jqfmt -f "$f" -op pipe -ob -ar | goawk '{ sub(/[ \\t]+$/, ""); print }' >"$f.tmp" coreutils mv "$f.tmp" "$f" done """ @@ -640,7 +647,7 @@ shell = "bash -euo pipefail -c" [tasks."fmt:rust"] # Rust + universal formatters. Always-loaded, so `fmt`'s glob always matches it. -depends = ["cargo-clippy-fix", "cargo-fmt", "dprint-fmt", "jq-fmt", "taplo-fmt"] +depends = ["cargo-clippy-fix", "cargo-fmt", "dprint-fmt", "jq-fmt", "shfmt-mise-fmt", "taplo-fmt"] description = "Run the Rust + universal repo-wide formatters" [tasks.fmt] @@ -679,6 +686,7 @@ depends = [ "ryl-check", "semgrep-check", "shellcheck-mise-check", + "shfmt-mise-check", "taplo-check", "taplo-fmt-check", "typos-check", @@ -884,7 +892,7 @@ files=$(git ls-files '*Dockerfile' '*Dockerfile.*') conftest test --combine --namespace dockerfile -p config/conftest/policy $files .mise # Filter to Dockerfiles that actually use heredocs before the `ignore` parser. for f in $files; do - if rg -q '^RUN[^\n]*<<' "$f"; then + if rg -q '^RUN[^\\n]*<<' "$f"; then conftest test --parser ignore --namespace dockerfile_heredoc -p config/conftest/policy "$f" fi done @@ -925,13 +933,71 @@ for cfg in .mise/config*.toml; do echo '#!/usr/bin/env bash' echo 'set -euo pipefail' printf '%s' "$b64" | coreutils base64 -d | goawk "$mask" - } | coreutils tr -d '\r' > "$out" + } | coreutils tr -d '\r' >"$out" done done shellcheck --shell=bash --exclude=SC2034,SC2154 "$boot"/*.sh ''' shell = "bash -euo pipefail -c" +[tasks.shfmt-mise-check] +description = "Check that every multi-line bash mise task body is shfmt-formatted" +# Run the shfmt-mise-fmt pipeline against scratch copies and compare, rather than over the configs themselves. +# The `-d` pass is what makes a failure actionable -- shfmt prints the offending body's own diff -- while the +# hash compare additionally catches whatever the merge pass would rewrite outside shfmt's view (the TOML +# escape normalisation .mise/shfmt-mise.awk performs on a `"""` body). +run = ''' +work="{{ vars.config_root_fwd }}/target/scratch/shfmt-mise-check" +coreutils rm -rf "$work" +status=0 +for cfg in .mise/config*.toml; do + d="$work/$(coreutils basename "$cfg" .toml)" + coreutils mkdir -p "$d" + goawk -N raw -v mode=split -v dir="$d" -f .mise/shfmt-mise.awk "$cfg" +done +shfmt {{ vars.shfmt_args }} -d "$work"/*/*.sh || status=1 +shfmt {{ vars.shfmt_args }} -w "$work"/*/*.sh +for cfg in .mise/config*.toml; do + d="$work/$(coreutils basename "$cfg" .toml)" + goawk -N raw -v mode=merge -v dir="$d" -f .mise/shfmt-mise.awk "$cfg" >"$d/merged.toml" + have=$(coreutils sha256sum -b "$cfg" | coreutils cut -c1-64) + want=$(coreutils sha256sum -b "$d/merged.toml" | coreutils cut -c1-64) + if [ "$have" != "$want" ]; then + echo "$cfg: task bodies need reformatting; run 'mise run shfmt-mise-fmt'" >&2 + status=1 + fi +done +exit $status +''' +shell = "bash -euo pipefail -c" + +[tasks.shfmt-mise-fmt] +description = "Format every multi-line bash mise task body with shfmt" +# Split each config's task bodies into scratch scripts, shfmt them, then merge them back over the config. +# .mise/shfmt-mise.awk owns both halves of that trip; the two loops here are split around one shfmt call so +# the glob always has something to match (config.java.toml carries no multi-line body of its own). +run = ''' +work="{{ vars.config_root_fwd }}/target/scratch/shfmt-mise-fmt" +coreutils rm -rf "$work" +for cfg in .mise/config*.toml; do + d="$work/$(coreutils basename "$cfg" .toml)" + coreutils mkdir -p "$d" + goawk -N raw -v mode=split -v dir="$d" -f .mise/shfmt-mise.awk "$cfg" +done +shfmt {{ vars.shfmt_args }} -w "$work"/*/*.sh +for cfg in .mise/config*.toml; do + d="$work/$(coreutils basename "$cfg" .toml)" + goawk -N raw -v mode=merge -v dir="$d" -f .mise/shfmt-mise.awk "$cfg" >"$d/merged.toml" + coreutils cp "$d/merged.toml" "$cfg" +done +''' +shell = "bash -euo pipefail -c" +# taplo-fmt rewrites every .mise/config*.toml in full, and so does this task. +# fmt:rust runs its dependencies as parallel siblings, so with no ordering edge between the two the second +# writer silently drops the first one's work. `wait_for` rather than `depends` because the edge only matters +# when both are already scheduled -- `mise run shfmt-mise-fmt` alone should stay one task, not two. +wait_for = ["taplo-fmt"] + [tasks.taplo-check] alias = "toml-check" # `taplo lint` reads config/taplo.toml's `[[rule]] schema` entries. @@ -1083,7 +1149,7 @@ run = """ echo 'IgnoredVulns = [' rg -oN -r ' { id = $0 },' '"(RUSTSEC-[0-9]{4}-[0-9]{4}|GHSA(-[0-9a-z]{4}){3})"' config/deny.toml | coreutils sort -u echo ']' -} > config/osv-scanner.toml +} >config/osv-scanner.toml """ shell = "bash -euo pipefail -c" @@ -1249,7 +1315,7 @@ mkdir -p "$s" for crate in et-cli et-int-gen et-onnx; do helpmd="utilities/${crate#et-}/HELP.md" echo "[gen-help-all] $crate -> $helpmd" - cargo run --features markdown-help -q -p "$crate" -- --markdown-help | dprint fmt --stdin md > "$s/$crate.md" + cargo run --features markdown-help -q -p "$crate" -- --markdown-help | dprint fmt --stdin md >"$s/$crate.md" coreutils mv "$s/$crate.md" "$helpmd" done """ @@ -1690,7 +1756,10 @@ rclone copyto {{ vars.face1_src }} {{ vars.face1_dst }} {{ vars.gh_http }} export asset='{{ vars.face1_asset }}' sha=$(mise exec -- yq -p toml -oy '.asset[strenv(asset)].sha256' config/upstream-cache/data.toml) actual=$(mise exec -- coreutils sha256sum -b "{{ vars.face1_dst }}" | mise exec -- coreutils cut -c1-64) -[ "$actual" = "$sha" ] || { echo "face1 checksum mismatch: expected=$sha actual=$actual" >&2; exit 1; } +[ "$actual" = "$sha" ] || { + echo "face1 checksum mismatch: expected=$sha actual=$actual" >&2 + exit 1 +} """ shell = "bash -euo pipefail -c" @@ -1703,7 +1772,10 @@ rclone copyto {{ vars.eye1_src }} {{ vars.eye1_dst }} {{ vars.gh_http }} export asset='{{ vars.eye1_asset }}' sha=$(mise exec -- yq -p toml -oy '.asset[strenv(asset)].sha256' config/upstream-cache/data.toml) actual=$(mise exec -- coreutils sha256sum -b "{{ vars.eye1_dst }}" | mise exec -- coreutils cut -c1-64) -[ "$actual" = "$sha" ] || { echo "eye1 checksum mismatch: expected=$sha actual=$actual" >&2; exit 1; } +[ "$actual" = "$sha" ] || { + echo "eye1 checksum mismatch: expected=$sha actual=$actual" >&2 + exit 1 +} """ shell = "bash -euo pipefail -c" @@ -1726,7 +1798,10 @@ rclone copyto {{ vars.speech1_src }} {{ vars.speech1_dst }} $opts export asset='{{ vars.speech1_asset }}' sha=$(mise exec -- yq -p toml -oy '.asset[strenv(asset)].sha256' config/upstream-cache/data.toml) actual=$(mise exec -- coreutils sha256sum -b "{{ vars.speech1_dst }}" | mise exec -- coreutils cut -c1-64) -[ "$actual" = "$sha" ] || { echo "speech1 checksum mismatch: expected=$sha actual=$actual" >&2; exit 1; } +[ "$actual" = "$sha" ] || { + echo "speech1 checksum mismatch: expected=$sha actual=$actual" >&2 + exit 1 +} """ shell = "bash -euo pipefail -c" @@ -1773,14 +1848,14 @@ assets=$(gh release view "$tag" --repo edge-toolkit/core --json assets --jq '.as native_missing=true wasm_missing=true if echo "$assets" | rg -Fxq "$native_asset"; then native_missing=false; fi -if echo "$assets" | rg -Fxq "$wasm_asset"; then wasm_missing=false; fi +if echo "$assets" | rg -Fxq "$wasm_asset"; then wasm_missing=false; fi out="${GITHUB_OUTPUT:-/dev/stdout}" { echo "short=$short" echo "triple=$triple" echo "native_missing=$native_missing" echo "wasm_missing=$wasm_missing" -} >> "$out" +} >>"$out" echo "[rp-detect-missing] triple=$triple short=$short native_missing=$native_missing wasm_missing=$wasm_missing" >&2 """ shell = "bash -euo pipefail -c" diff --git a/.mise/config.windows.toml b/.mise/config.windows.toml index acfcfb91..477bc4cc 100644 --- a/.mise/config.windows.toml +++ b/.mise/config.windows.toml @@ -353,7 +353,7 @@ elif [ "${ENABLE_RUSTPYTHON_PIPX_BOOTSTRAP:-}" = "1" ]; then shared_site="$(coreutils ls -d "$shared_venv"/lib/*/site-packages)" coreutils cp -R "$pip_extracted/pip" "$shared_site/" coreutils cp -R "$pip_extracted"/pip-*.dist-info "$shared_site/" - coreutils cat > "$shared_venv/bin/pip" <<'PIP_SHIM' + coreutils cat >"$shared_venv/bin/pip" <<'PIP_SHIM' #!/bin/sh exec "$(dirname "$0")/python" -m pip "$@" PIP_SHIM diff --git a/.mise/mise.lock b/.mise/mise.lock index a1760535..22137c65 100644 --- a/.mise/mise.lock +++ b/.mise/mise.lock @@ -200,6 +200,35 @@ checksum = "sha256:8a4e35ab0b331c85d73567b12f2a444df187f483e5079ceffa6bda1faa2e7 url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.zip" url_api = "https://api.github.com/repos/koalaman/shellcheck/releases/assets/279056944" +[[tools."aqua:mvdan/sh"]] +version = "3.13.1" +backend = "aqua:mvdan/sh" + +[tools."aqua:mvdan/sh"."platforms.linux-arm64"] +checksum = "sha256:32d92acaa5cd8abb29fc49dac123dc412442d5713967819d8af2c29f1b3857c7" +url = "https://github.com/mvdan/sh/releases/download/v3.13.1/shfmt_v3.13.1_linux_arm64" +url_api = "https://api.github.com/repos/mvdan/sh/releases/assets/390322859" + +[tools."aqua:mvdan/sh"."platforms.linux-x64"] +checksum = "sha256:fb096c5d1ac6beabbdbaa2874d025badb03ee07929f0c9ff67563ce8c75398b1" +url = "https://github.com/mvdan/sh/releases/download/v3.13.1/shfmt_v3.13.1_linux_amd64" +url_api = "https://api.github.com/repos/mvdan/sh/releases/assets/390322866" + +[tools."aqua:mvdan/sh"."platforms.macos-arm64"] +checksum = "sha256:9680526be4a66ea1ffe988ed08af58e1400fe1e4f4aef5bd88b20bb9b3da33f8" +url = "https://github.com/mvdan/sh/releases/download/v3.13.1/shfmt_v3.13.1_darwin_arm64" +url_api = "https://api.github.com/repos/mvdan/sh/releases/assets/390322881" + +[tools."aqua:mvdan/sh"."platforms.macos-x64"] +checksum = "sha256:6feedafc72915794163114f512348e2437d080d0047ef8b8fa2ec63b575f12af" +url = "https://github.com/mvdan/sh/releases/download/v3.13.1/shfmt_v3.13.1_darwin_amd64" +url_api = "https://api.github.com/repos/mvdan/sh/releases/assets/390322886" + +[tools."aqua:mvdan/sh"."platforms.windows-x64"] +checksum = "sha256:60cd368533d0ad73fa86d93d5bbf95ef40587245ce684ed138c1b31557b5fe97" +url = "https://github.com/mvdan/sh/releases/download/v3.13.1/shfmt_v3.13.1_windows_amd64.exe" +url_api = "https://api.github.com/repos/mvdan/sh/releases/assets/390322844" + [[tools."aqua:rhysd/actionlint"]] version = "1.7.12" backend = "aqua:rhysd/actionlint" @@ -370,6 +399,10 @@ backend = "cargo:rustfs/rustfs" version = "0.21.0" backend = "cargo:ryl" +[[tools."cargo:vectordotdev/vector"]] +version = "tag:v0.56.0" +backend = "cargo:vectordotdev/vector" + [[tools."cargo:wasm-opt"]] version = "0.116.1" backend = "cargo:wasm-opt" diff --git a/.mise/shfmt-mise.awk b/.mise/shfmt-mise.awk new file mode 100644 index 00000000..4d9b17b3 --- /dev/null +++ b/.mise/shfmt-mise.awk @@ -0,0 +1,161 @@ +# Split the multi-line bash bodies out of a `.mise/config*.toml` so shfmt can format them, then merge back. +# +# `-v mode=split` writes the Nth body of the config to /NNN.sh, plus /NNN.tok holding the Tera +# tokens it masked out of that body (one per line, in the order they appeared). `-v mode=merge` re-reads the +# same config and re-emits it on stdout with each body replaced by the (by then shfmt-formatted) /NNN.sh. +# Bodies are paired between the two passes positionally, so both passes must see the same input file. +# +# A body is the run of lines between a `run = """` (or `run = '''`) line and the matching closing delimiter on +# its own line -- the shape every multi-line mise task uses, and the only shape a task `run` can take given the +# conftest rule that a multiline run is a bash script. Only the opening delimiter closes a body, so a `"""` +# python docstring inside a `'''` literal block does not end it early. +# +# Two encodings sit between the TOML byte and the byte shfmt must see, and both are reversed on the way back: +# +# Tera `{{ ... }}` / `{% ... %}` masks to `ET_TERA__`, a plain bash word shfmt leaves alone. Without +# it a `{% if os() == 'windows' %}` body fails to parse (`(` where shfmt wants a word) and shfmt +# declines to format it at all. Masking happens before the escape decode and unmasking after the +# re-encode, so a token's own bytes round-trip verbatim however they were spelled. +# escapes A `"""` block is a TOML *basic* string, so `\\` and `\"` in the file are one backslash and one +# quote by the time the shell sees them; shfmt has to be handed that decoded form (raw `\\(` reads +# as escaped-backslash plus a bare `(`, which does not parse), and the merge pass re-escapes each +# backslash on the way out. The decode deliberately rejects every other escape rather than growing +# a table for them: a lone `\n` / `\r` / `\b` in a body is nearly always meant as the two +# characters the shell needs, and TOML quietly folding it into a control character is a bug in the +# body (a `\b` inside a comment here once decoded to a backspace and ate the surrounding text). +# `'''` blocks are TOML *literal* strings -- no escape processing at all -- so they pass untouched. + +function fail(msg) { + printf("%s:%d: %s\n", FILENAME, FNR, msg) > "/dev/stderr" + exit 1 +} + +function placeholder(n) { + return "ET_TERA_" n "_" +} + +# Replace every occurrence of `from` with `to`, byte-wise. +# index/substr rather than sub(): neither argument is a regex, and `&` in an awk replacement is magic. +function subst_all(s, from, to, out, p) { + out = "" + while ((p = index(s, from)) > 0) { + out = out substr(s, 1, p - 1) to + s = substr(s, p + length(from)) + } + return out s +} + +# Swap each Tera token for its placeholder, recording the original in the body's .tok sidecar. +function mask(line, out, rest, tok) { + out = "" + rest = line + while (match(rest, /[{][{][^}]*[}][}]|[{][%][^%]*[%][}]/)) { + tok = substr(rest, RSTART, RLENGTH) + ntok++ + print tok > tokfile + out = out substr(rest, 1, RSTART - 1) placeholder(ntok) + rest = substr(rest, RSTART + RLENGTH) + } + return out rest +} + +function unmask(line, i) { + for (i = 1; i <= ntok; i++) { + line = subst_all(line, placeholder(i), tok[i]) + } + return line +} + +# Undo TOML basic-string escaping, one left-to-right pass so `\\"` decodes as backslash then quote. +function decode(s, out, n, i, c, nx) { + out = "" + n = length(s) + for (i = 1; i <= n; i++) { + c = substr(s, i, 1) + if (c != "\\") { + out = out c + continue + } + nx = substr(s, i + 1, 1) + if (nx == "\\" || nx == "\"") { + out = out nx + i++ + continue + } + fail("unsupported TOML escape \"\\" nx "\" in a task body; double the backslash to pass it to the shell") + } + return out +} + +function encode(s) { + return subst_all(s, "\\", "\\\\") +} + +BEGIN { + if (mode != "split" && mode != "merge") { + printf("shfmt-mise.awk: -v mode= must be split or merge\n") > "/dev/stderr" + exit 1 + } + if (dir == "") { + printf("shfmt-mise.awk: -v dir= must name the body directory\n") > "/dev/stderr" + exit 1 + } +} + +!inbody && ($0 == "run = \"\"\"" || $0 == "run = '''") { + inbody = 1 + delim = ($0 == "run = \"\"\"") ? "\"\"\"" : "'''" + basic = (delim == "\"\"\"") + idx++ + body = sprintf("%s/%03d.sh", dir, idx) + tokfile = sprintf("%s/%03d.tok", dir, idx) + ntok = 0 + if (mode == "split") { + printf("") > body + printf("") > tokfile + next + } + print + while ((getline line < tokfile) > 0) { + tok[++ntok] = line + } + close(tokfile) + while ((getline line < body) > 0) { + if (basic) { + line = encode(line) + if (index(line, "\"\"\"") > 0) { + fail("formatted body contains `\"\"\"`, which would close the TOML string early") + } + } + print unmask(line) + } + close(body) + next +} + +inbody && $0 == delim { + inbody = 0 + if (mode == "split") { + close(body) + close(tokfile) + next + } + print + next +} + +inbody { + if (mode == "merge") { + next + } + line = mask($0) + if (basic) { + line = decode(line) + } + print line > body + next +} + +mode == "merge" { + print +} diff --git a/CLAUDE.md b/CLAUDE.md index 9b959fc0..6e2e38cd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -302,7 +302,8 @@ run every loaded language's row; guest rows need their `MISE_ENV` loaded. `dprint-fmt` / `dprint-check` cover `*.md`, `*.yaml`, `*.json`/`*.jsonc`, `*.ts`/`*.js`, `*.css`, `*.html`, `*.java`, and `Dockerfile*`; `hadolint-check` also lints Dockerfiles, and `link-check` scans `*.md` + `*.rs`. Every file is covered by `editorconfig-check` and `typos-check`, file and directory names by `ls-lint-check`, and `*.yml` is -rejected by `semgrep-check` (use `*.yaml`). +rejected by `semgrep-check` (use `*.yaml`). A multi-line task body inside `.mise/config*.toml` is shell, not TOML, so +it carries two more of its own: `shfmt-mise-fmt` / `shfmt-mise-check` format it, and `shellcheck-mise-check` lints it. For Rust inner-loop iteration on a single crate, use `mise run cargo-clippy-check-pkg ` (alias `clippy-pkg`) instead of `cargo-clippy-check` -- it runs `cargo clippy --keep-going --tests -p ` so you @@ -890,6 +891,12 @@ standalone file or a mise task `run`. The available linters: """` from `.mise/config*.toml`, masks Tera tokens (`{{ ... }}` -> `MISEVAR`, `{% ... %}` -> empty), and shellchecks the lot. This is how shell-quality lints reach mise task bodies (shellcheck itself doesn't read TOML). +- **shfmt on mise task bodies** (`.mise/shfmt-mise.awk`, via `shfmt-mise-fmt` and `shfmt-mise-check`) -- splits those + same bodies out, shfmt-formats each one, and merges the result back over the config, so a task body is held to the + same formatting as any other shell in the repo. The check reports shfmt's own diff and names `shfmt-mise-fmt` as the + fix. Its one demand on how a body is written: inside a `"""` block every backslash escape must be doubled, since a + lone `\n` / `\r` / `\b` is TOML syntax that folds into a control character rather than the two bytes the shell wants + -- the split pass rejects one instead of guessing which was meant. - plus hadolint, ls-lint (file/dir naming), zizmor (Actions security), ryl (YAML), lychee (links), clang-format / clang-tidy / cpplint / flawfinder (C, in the zig config), editorconfig-checker, typos, and action-validator for their domains. diff --git a/services/ws-modules/pic-viewer/tests/show_image.rs b/services/ws-modules/pic-viewer/tests/show_image.rs index 37fd0cf4..23a1c404 100644 --- a/services/ws-modules/pic-viewer/tests/show_image.rs +++ b/services/ws-modules/pic-viewer/tests/show_image.rs @@ -14,10 +14,24 @@ use wasm_bindgen_test::wasm_bindgen_test; wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); -/// The ws-server page favicon: a real 64x64 PNG, staged into `OUT_DIR` by build.rs and embedded here. +/// The ws-server page favicon: a real PNG, staged into `OUT_DIR` by build.rs and embedded here. const FAVICON_PNG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/favicon.png")); -const FAVICON_WIDTH: u32 = 64; -const FAVICON_HEIGHT: u32 = 64; +/// The fixture's natural size, read out of its own header rather than restated as a literal. +/// A PNG's width and height are the big-endian `u32`s at bytes 16 and 20 (the first two fields of the IHDR +/// chunk's data), so what this test expects on the canvas comes from the very bytes the browser decodes. +/// Deriving it is what keeps the test honest when the favicon is redrawn at a different size. +const FAVICON_WIDTH: u32 = ihdr_u32(16); +const FAVICON_HEIGHT: u32 = ihdr_u32(20); + +/// Read the big-endian `u32` that the PNG header carries at byte `at`. +const fn ihdr_u32(at: usize) -> u32 { + u32::from_be_bytes([ + FAVICON_PNG[at], + FAVICON_PNG[at + 1], + FAVICON_PNG[at + 2], + FAVICON_PNG[at + 3], + ]) +} /// Insert the page's shared output canvas (`show_image` looks it up by this id), hidden like the real page. fn install_output_canvas() -> web_sys::HtmlCanvasElement { @@ -57,15 +71,18 @@ async fn shows_the_favicon_on_the_output_canvas() { assert_eq!(canvas.width(), FAVICON_WIDTH); assert_eq!(canvas.height(), FAVICON_HEIGHT); - // ...and actually carry the drawn pixels: the favicon is an opaque RGB PNG, so every pixel of a real - // draw has full alpha, while an untouched canvas is all-transparent (alpha 0). + // ...and actually carry the drawn pixels: an untouched canvas is all-transparent (alpha 0), so a fully + // opaque pixel can only have come from a real draw. The centre is sampled because the favicon carries an + // alpha channel and only its artwork is guaranteed opaque -- a corner may legitimately be transparent. let context = canvas .get_context("2d") .unwrap() .unwrap() .dyn_into::() .unwrap(); - let center = context.get_image_data(32.0, 32.0, 1.0, 1.0).unwrap(); + let center_x = f64::from(FAVICON_WIDTH) / 2.0; + let center_y = f64::from(FAVICON_HEIGHT) / 2.0; + let center = context.get_image_data(center_x, center_y, 1.0, 1.0).unwrap(); let pixel = center.data(); assert_eq!(pixel[3], 255, "center pixel should be opaque after drawing the favicon"); } diff --git a/services/ws-server/static/favicon.png b/services/ws-server/static/favicon.png index 545ecb00bb3dba9068e6822a60f36ad67c718513..c37afaedfca4f92c1bc5959e49e1fa29ddfdf85e 100644 GIT binary patch literal 41271 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