Files
Panama/tests/quickshell/enum-hypr-map-contract.sh
T
Gabriel Brown d96863b687 Convert British spellings to American across the repo
colour -> color, behaviour -> behavior, centre -> center, favourite ->
favorite, and about twenty other pairs, applied consistently across
comments, docs, error/UI copy, and a handful of QML identifiers that
used the British spelling as their actual name: SystemSettings'
serialiseValue/serialiseTable/normaliseGradient, Displays'
normaliseModes, Wallpaper's normalisePolicy, SettingsBackup's
serialiseHomeState, DateTime's ntpSynchronised property, Clipboard's
_normalise helper, and ShortcutCapture's cancelled signal (with its
onCancelled handler in ShortcutsPage.qml). Every call site and the two
tests that assert on the literal source text (settings-ownership and
settings-backup-live contracts) were updated in lockstep.

Left untouched: config/dot/espanso/match/packages/misspell-en/ is a
vendored third-party autocorrect dictionary -- its entries are typo
corrections, not our prose, and rewriting them would fight the
package's own purpose (and any future re-sync from upstream).

The already-American `favorites` property (Home page pinned
accessories) was never actually misspelled -- only nearby comments and
error strings said "favourites" -- so no data migration was needed
there.

Claude-Session: https://claude.ai/code/session_01E6TJUAh41HaP25MVHWkhRZ
2026-08-19 08:07:55 -04:00

96 lines
4.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# Every enum backed by a Hyprland option must offer values that option accepts.
#
# This exists because of a bug that shipped: followMouse offered 0/1/2 labeled
# "Never" / "Click to focus" / "Sloppy focus", while Hyprland's actual mapping
# is disabled=0, follow=1, detached=2, separate=3. The desktop was labeled
# "Click to focus" and was in fact following the pointer, the way to GET click
# to focus was to choose "Never", and value 3 did not exist in the UI at all.
#
# Nothing detects that. The compositor accepts 1, reads back 1, and verification
# passes -- the value is valid, it just means something else entirely. The only
# authority on what each number MEANS is the compositor, which publishes it:
#
# hyprctl descriptions -> { "name": "input:follow_mouse",
# "map": [{"separate":3},{"detached":2},...] }
#
# So this checks the schema's enum values against that map, and against the
# min/max range for mapped options that have no named map.
set -uo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
schema="$repo_dir/config/dot/quickshell/config/PreferenceSchema.qml"
fail() {
printf 'enum hypr map contract: %s\n' "$1" >&2
exit 1
}
command -v hyprctl >/dev/null 2>&1 || { printf 'enum hypr map contract: SKIP (no compositor)\n'; exit 0; }
descriptions="$(hyprctl descriptions 2>/dev/null)" || fail 'could not read hyprctl descriptions'
jq -e 'type == "array" and length > 0' >/dev/null <<<"$descriptions" \
|| fail 'hyprctl descriptions did not return a list'
# Pull every enum entry that carries a hypr option, as: key<TAB>option<TAB>values
entries="$(python3 - "$schema" <<'PY'
import re, sys
text = open(sys.argv[1]).read()
# Each schema entry is a brace-delimited block starting with `key:`.
for block in re.findall(r'\{\s*\n?\s*key:\s*"([^"]+)"(.*?)\n \}', text, re.S):
name, body = block
if 'type: "enum"' not in body:
continue
option = re.search(r'option:\s*"([^"]+)"', body)
if not option:
continue
values = re.findall(r'value:\s*(-?\d+)', body)
if not values:
continue
print(f"{name}\t{option.group(1)}\t{','.join(values)}")
PY
)"
[[ -n "$entries" ]] || fail 'found no compositor-backed enums in the schema -- this contract is not reading it correctly'
checked=0
while IFS=$'\t' read -r key option values; do
[[ -n "$key" ]] || continue
entry="$(jq -c --arg name "$option" '.[] | select(.name == $name)' <<<"$descriptions")"
[[ -n "$entry" ]] || fail "$key maps to \"$option\", which the compositor does not publish"
map_values="$(jq -r 'if .map then (.map | map(to_entries[].value) | join(",")) else "" end' <<<"$entry")"
IFS=',' read -ra wanted <<<"$values"
for value in "${wanted[@]}"; do
if [[ -n "$map_values" ]]; then
grep -qx "$value" <<<"$(tr ',' '\n' <<<"$map_values")" \
|| fail "$key offers $value for $option, which the compositor's map does not contain (it publishes: $map_values). A value outside the map is accepted and read back unchanged, so nothing else notices -- it simply means something other than the label says."
else
min="$(jq -r '.min // empty' <<<"$entry")"
max="$(jq -r '.max // empty' <<<"$entry")"
if [[ -n "$min" && -n "$max" ]]; then
(( value >= min && value <= max )) \
|| fail "$key offers $value for $option, outside the compositor's range $min..$max"
fi
fi
done
# Every value the compositor names should be offered. A missing one is a
# capability the user simply cannot reach -- value 3 was missing here.
if [[ -n "$map_values" ]]; then
while read -r published; do
[[ -n "$published" ]] || continue
grep -qx "$published" <<<"$(tr ',' '\n' <<<"$values")" \
|| fail "$option publishes value $published but $key does not offer it, so that behavior is unreachable from Settings"
done <<<"$(tr ',' '\n' <<<"$map_values")"
fi
checked=$((checked + 1))
done <<<"$entries"
printf 'enum hypr map contract: PASS (%d mapped enums)\n' "$checked"