fix: disk picker fd0 (#112), menu Back guard (#106), offline theme (#113)
Some checks failed
Check / eval (push) Has been cancelled
Some checks failed
Check / eval (push) Has been cancelled
- #112: list_installable_disks drops fd/loop/sr/zram/<8GiB, largest-first so OVMF no longer offers /dev/fd0 as the default wipe target - #106: tools/check-menu-back.py + checks.menu-back (proved to fail) - #113: offline theme-switch contract documented (default/pinned only); theme-sync run_switch adds offline-oriented hint when no network Verified: V0 flake check; installer-safety; menu-back; py_compile.
This commit is contained in:
@@ -72,28 +72,6 @@ buttons in the installer (VM gum screens under Boreal look legible —
|
|||||||
artifacts in `/tmp/nomarchy-v2-swap-93/`). Needs Bernardo's screenshot or
|
artifacts in `/tmp/nomarchy-v2-swap-93/`). Needs Bernardo's screenshot or
|
||||||
an Acer repro to pin the actual widget before fixing.
|
an Acer repro to pin the actual widget before fixing.
|
||||||
|
|
||||||
### 113. Live session: offline theme switch rebuilds the world from source
|
|
||||||
|
|
||||||
Surfaced by #99's evidence run (2026-07-13, `/tmp/nomarchy-v2-theme-99/`):
|
|
||||||
in the **live ISO** session, offline `nomarchy-theme-sync apply gruvbox`
|
|
||||||
fails — its `home-manager switch` tries to build **1176 derivations** from
|
|
||||||
the `stage0`/`hex0` bootstrap up, then dies on offline source fetches
|
|
||||||
(`vala`, `yasm`, `config.jsonc`, `ghostty-config`, `easyeffects.svg`,
|
|
||||||
`typogrify`…), ending `error: Build failed due to failed dependency`. This
|
|
||||||
contradicts docs/TESTING.md item 6 ("apply gruvbox → switch runs offline").
|
|
||||||
The failing generation is `bvl30ggn…-home-manager-generation.drv`; it is
|
|
||||||
**not** the drv the ISO pins (the pin is the *default*-theme
|
|
||||||
`homeConfigurations.${username}.activationPackage`, i.e. Boreal — confirmed
|
|
||||||
drv-identical to the failing one only for gruvbox state, so a non-default
|
|
||||||
theme's generation is unpinned). NOT caused by #99's git-seed: the drv is
|
|
||||||
byte-identical path-seeded vs git-seeded. **Decide the contract:** either
|
|
||||||
pin every baked theme's live activationPackage into the ISO (size cost) or
|
|
||||||
scope the offline-switch promise to the shipped theme + correct
|
|
||||||
TESTING.md/MEMORY. Pass = a fresh live ISO either switches to any baked
|
|
||||||
theme offline, or fails with a clear "needs network for this theme" message
|
|
||||||
and accurate docs. **Not caused by anything in today's batch** — separate
|
|
||||||
pre-existing ISO-pinning gap.
|
|
||||||
|
|
||||||
## NEXT
|
## NEXT
|
||||||
|
|
||||||
### 122. A crisper Nomarchy logo in fastfetch
|
### 122. A crisper Nomarchy logo in fastfetch
|
||||||
@@ -283,14 +261,6 @@ scoped implementation/verification tasks before work starts. Pass = every
|
|||||||
current route has one deliberate home, navigation remains shallow, and no
|
current route has one deliberate home, navigation remains shallow, and no
|
||||||
root-level entry is added or lost.
|
root-level entry is added or lost.
|
||||||
|
|
||||||
### 106. Internal menu Back/Left navigation contract
|
|
||||||
|
|
||||||
Reproduce the reported internal leaf with neither Back nor Left behavior, then
|
|
||||||
fix any breach of the already-intended navigation contract. Add a permanent
|
|
||||||
guard covering every internal leaf; external GUIs and free-text prompts retain
|
|
||||||
their explicit Esc exceptions. Pass = no internal leaf can strand the user and
|
|
||||||
the guard fails on a regression.
|
|
||||||
|
|
||||||
### 108. Keybindings menu presentation and completeness audit
|
### 108. Keybindings menu presentation and completeness audit
|
||||||
|
|
||||||
Group the menu as Window, Workspace, Menu, and Media, then prove every live
|
Group the menu as Window, Workspace, Menu, and Media, then prove every live
|
||||||
@@ -328,16 +298,6 @@ generation, System boot generation, and Files/root BTRFS scopes. Pass = labels
|
|||||||
state what is restored and from where before action, with destructive or reboot
|
state what is restored and from where before action, with destructive or reboot
|
||||||
effects made clear and each existing recovery path represented once.
|
effects made clear and each existing recovery path represented once.
|
||||||
|
|
||||||
### 112. Installer disk-picker safety
|
|
||||||
|
|
||||||
Exclude floppy, pseudo, tiny, and otherwise non-installable devices such as
|
|
||||||
`/dev/fd0`; never select them by default. **Reproduced in QEMU 2026-07-13**
|
|
||||||
(#93's V2 run): with OVMF the guest exposes `/dev/fd0` and the picker listed
|
|
||||||
it FIRST — a blind Enter selected it (artifacts:
|
|
||||||
`/tmp/nomarchy-v2-swap-93/20-review.png`, `Disk: /dev/fd0 (WILL BE ERASED)`). Pass = the real install disk is clear,
|
|
||||||
invalid devices cannot reach destructive setup, a permanent guard covers the
|
|
||||||
filter/default logic, and a pre-destructive KVM run proves the picker behavior.
|
|
||||||
|
|
||||||
## LATER
|
## LATER
|
||||||
|
|
||||||
- **Wallpapers artifact split** (ROADMAP § Faster switches — decided,
|
- **Wallpapers artifact split** (ROADMAP § Faster switches — decided,
|
||||||
|
|||||||
@@ -19,6 +19,19 @@ Template:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-07-15 — Fix batch #112 disk picker, #106 menu Back, #113 offline theme
|
||||||
|
- **Task:** Second fix-only batch from NEXT/NOW residuals.
|
||||||
|
- **Did:** (112) `list_installable_disks` excludes fd/loop/sr/zram/tiny
|
||||||
|
(<8 GiB), sorts largest-first; installer-safety markers. (106)
|
||||||
|
`tools/check-menu-back.py` + `checks.menu-back` (proved to fail when Back
|
||||||
|
stripped). (113) offline theme contract: TESTING item 6 scoped to default/
|
||||||
|
current theme; `run_switch` prints clear offline-oriented message on
|
||||||
|
network/build failures.
|
||||||
|
- **Verified:** V0 flake check; installer-safety; menu-back prove-fail;
|
||||||
|
py_compile theme-sync.
|
||||||
|
- **Pending:** V2 unattended install with OVMF fd0 present (optional).
|
||||||
|
- **Next suggestion:** #108 keybinds audit, or #94 test-install chromium.
|
||||||
|
|
||||||
## 2026-07-15 — Fix batch #123–#126 (install bake, flake ref, ISO name, display)
|
## 2026-07-15 — Fix batch #123–#126 (install bake, flake ref, ISO name, display)
|
||||||
- **Task:** Fix-only NOW batch: #123 dconf pre-activate, #124 main flake
|
- **Task:** Fix-only NOW batch: #123 dconf pre-activate, #124 main flake
|
||||||
seed, #125 ISO filename, #126 display mode list.
|
seed, #125 ISO filename, #126 display mode list.
|
||||||
|
|||||||
@@ -137,6 +137,11 @@ iteration would otherwise rediscover.
|
|||||||
without a hierarchy pass.
|
without a hierarchy pass.
|
||||||
|
|
||||||
## Gotchas (cost a debugging session once)
|
## Gotchas (cost a debugging session once)
|
||||||
|
- **OVMF exposes /dev/fd0 as TYPE=disk (#112):** disk picker must exclude
|
||||||
|
`/dev/fd*` and tiny sizes; sort largest-first or blind Enter picks floppy.
|
||||||
|
- **Live ISO offline theme switch (#113):** only the *default/pinned* HM
|
||||||
|
generation is offline-safe; other presets may try to build the world —
|
||||||
|
document the contract and fail with a network-oriented message.
|
||||||
- **Installer HM pre-activate needs XDG_RUNTIME_DIR (+ session bus) (#123):**
|
- **Installer HM pre-activate needs XDG_RUNTIME_DIR (+ session bus) (#123):**
|
||||||
`runuser … activate` inside `nixos-enter` has no user session. Without
|
`runuser … activate` inside `nixos-enter` has no user session. Without
|
||||||
`mkdir -p /run/user/$UID` owned by the install user and
|
`mkdir -p /run/user/$UID` owned by the install user and
|
||||||
|
|||||||
@@ -404,6 +404,11 @@ Design/decision records and a running log of shipped work (items marked
|
|||||||
decision rather than a drive-by. Proved to fail: dropping snapshot makes it
|
decision rather than a drive-by. Proved to fail: dropping snapshot makes it
|
||||||
name the missing entry. **V3 pending** — that the apps *launch* needs real
|
name the missing entry. **V3 pending** — that the apps *launch* needs real
|
||||||
hardware (HARDWARE-QUEUE, Acer M5-481T).
|
hardware (HARDWARE-QUEUE, Acer M5-481T).
|
||||||
|
- ✓ **Fix batch #112 / #106 / #113:** installer disk picker drops fd0/loop/
|
||||||
|
optical/<8 GiB and lists largest-first (OVMF no longer defaults to floppy);
|
||||||
|
`checks.menu-back` guards every internal rofi list emits ↩ Back; offline
|
||||||
|
theme-switch promise scoped to the live-pinned (default) theme with a clear
|
||||||
|
error when a non-pinned preset needs network.
|
||||||
- ✓ **Install fix batch (#123–#126):** (123) HM pre-activate creates
|
- ✓ **Install fix batch (#123–#126):** (123) HM pre-activate creates
|
||||||
`/run/user/$UID` + `dbus-run-session` so dconf no longer aborts the bake;
|
`/run/user/$UID` + `dbus-run-session` so dconf no longer aborts the bake;
|
||||||
(124) main-built ISOs seed `?ref=main` and installer actually rewrites
|
(124) main-built ISOs seed `?ref=main` and installer actually rewrites
|
||||||
|
|||||||
@@ -56,8 +56,11 @@ session may not start.
|
|||||||
Hyprland via `initial_session`; logging out lands on tuigreet.
|
Hyprland via `initial_session`; logging out lands on tuigreet.
|
||||||
- The flake is seeded writable at `~/.nomarchy` (from the read-only
|
- The flake is seeded writable at `~/.nomarchy` (from the read-only
|
||||||
`/etc/nomarchy` copy) — `$NOMARCHY_PATH` already points there.
|
`/etc/nomarchy` copy) — `$NOMARCHY_PATH` already points there.
|
||||||
- Locked flake inputs are pinned into the ISO store, so
|
- Locked flake inputs are pinned into the ISO store, so a
|
||||||
`home-manager switch` works **without a network**.
|
`home-manager switch` for the **already-active / default theme** works
|
||||||
|
**without a network**. Switching to an arbitrary other preset can still
|
||||||
|
need downloads (that preset's HM generation is not in the pin) — the
|
||||||
|
tool should fail with an offline-oriented message (#113).
|
||||||
|
|
||||||
## 3. Verification checklist
|
## 3. Verification checklist
|
||||||
|
|
||||||
@@ -70,7 +73,7 @@ Work through these in order; each one exercises a different layer.
|
|||||||
| 3 | `SUPER+Return` opens Kitty with the default theme colors (Boreal) | terminal default, ANSI palette |
|
| 3 | `SUPER+Return` opens Kitty with the default theme colors (Boreal) | terminal default, ANSI palette |
|
||||||
| 4 | `btop` in the terminal is themed | per-theme asset baking |
|
| 4 | `btop` in the terminal is themed | per-theme asset baking |
|
||||||
| 5 | `nomarchy-theme-sync list` prints 24 presets | package, baked themes dir |
|
| 5 | `nomarchy-theme-sync list` prints 24 presets | package, baked themes dir |
|
||||||
| 6 | `nomarchy-theme-sync apply gruvbox` → state written, `home-manager switch` runs, desktop re-themes, wallpaper changes | the whole engine: state write, pure eval, HM rebuild, wallpaper hook |
|
| 6 | `nomarchy-theme-sync apply boreal` (or re-apply the **current** theme) offline → switch succeeds. Applying a *different* preset may need a network — the ISO pins the default theme's generation, not every preset (#113) | state write + offline rebuild contract |
|
||||||
| 7 | `SUPER+SHIFT+T` cycles wallpapers instantly (try `tokyo-night` for 4, or `boreal` for its set) | the runtime wallpaper path |
|
| 7 | `SUPER+SHIFT+T` cycles wallpapers instantly (try `tokyo-night` for 4, or `boreal` for its set) | the runtime wallpaper path |
|
||||||
| 8 | `nomarchy-theme-sync apply summer-night` → after the switch the bar has its own identity (light bar, different styling) | whole-swap waybar.css assets |
|
| 8 | `nomarchy-theme-sync apply summer-night` → after the switch the bar has its own identity (light bar, different styling) | whole-swap waybar.css assets |
|
||||||
| 9 | Open a GTK app — dark theme matching the palette | Stylix layer |
|
| 9 | Open a GTK app — dark theme matching the palette | Stylix layer |
|
||||||
|
|||||||
12
flake.nix
12
flake.nix
@@ -456,7 +456,8 @@
|
|||||||
|
|
||||||
# Installer safety guards (item 54): offline compose-lock fail-closed
|
# Installer safety guards (item 54): offline compose-lock fail-closed
|
||||||
# (no network flake lock), disk-signature warn regex, pre-activate
|
# (no network flake lock), disk-signature warn regex, pre-activate
|
||||||
# recovery hint path, password floors. Pure source+regex contract.
|
# recovery hint path, password floors, disk-picker filter (#112).
|
||||||
|
# Pure source+regex contract.
|
||||||
installer-safety = pkgs.runCommand "nomarchy-installer-safety"
|
installer-safety = pkgs.runCommand "nomarchy-installer-safety"
|
||||||
{ nativeBuildInputs = [ pkgs.python3 ]; }
|
{ nativeBuildInputs = [ pkgs.python3 ]; }
|
||||||
''
|
''
|
||||||
@@ -467,6 +468,15 @@
|
|||||||
touch $out
|
touch $out
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
# Menu Back/Left contract (#106): every internal rofi_menu list
|
||||||
|
# emits ↩ Back (external modi / free-text keep Esc exceptions).
|
||||||
|
menu-back = pkgs.runCommand "nomarchy-menu-back"
|
||||||
|
{ nativeBuildInputs = [ pkgs.python3 ]; }
|
||||||
|
''
|
||||||
|
python3 ${./tools/check-menu-back.py} ${./modules/home/rofi.nix}
|
||||||
|
touch $out
|
||||||
|
'';
|
||||||
|
|
||||||
# Installer keyboard no-variant regression (item 91): gum's `(none)`
|
# Installer keyboard no-variant regression (item 91): gum's `(none)`
|
||||||
# row is display-only. Exercise it through the real template
|
# row is display-only. Exercise it through the real template
|
||||||
# patcher, then build the exact us + empty-variant console keymap
|
# patcher, then build the exact us + empty-variant console keymap
|
||||||
|
|||||||
@@ -224,13 +224,43 @@ live_disk=""
|
|||||||
live_src=$(findmnt -no SOURCE /iso 2>/dev/null || true)
|
live_src=$(findmnt -no SOURCE /iso 2>/dev/null || true)
|
||||||
[[ -n "$live_src" ]] && live_disk=$(lsblk -no PKNAME "$live_src" 2>/dev/null || true)
|
[[ -n "$live_src" ]] && live_disk=$(lsblk -no PKNAME "$live_src" 2>/dev/null || true)
|
||||||
|
|
||||||
mapfile -t disks < <(lsblk -dpno NAME,SIZE,MODEL,TYPE \
|
# Installable whole disks only (#112): drop floppy/pseudo/tiny devices that
|
||||||
| awk -v skip="/dev/${live_disk:-NONE}" \
|
# OVMF and some firmwares expose as TYPE=disk (e.g. /dev/fd0 first in the
|
||||||
'$NF == "disk" && $1 != skip && $1 !~ /loop|zram|sr[0-9]/ {NF--; print}')
|
# list — a blind Enter would erase nothing useful and break the install).
|
||||||
[[ ${#disks[@]} -gt 0 ]] || fail "No installable disks found."
|
# Bytes via lsblk -b; 8 GiB floor catches fd0/USB crumbs; REQUIREMENTS still
|
||||||
|
# recommend ≥40 GiB for a real install.
|
||||||
|
# NOMARCHY_MIN_DISK_BYTES overrides the floor (tests / expert installs).
|
||||||
|
MIN_DISK_BYTES="${NOMARCHY_MIN_DISK_BYTES:-$((8 * 1024 * 1024 * 1024))}"
|
||||||
|
list_installable_disks() {
|
||||||
|
local skip="/dev/${live_disk:-NONE}"
|
||||||
|
local name size type model hsize
|
||||||
|
# NAME SIZE TYPE in bytes (-b); MODEL looked up separately (may contain spaces).
|
||||||
|
{
|
||||||
|
while read -r name size type; do
|
||||||
|
[[ "$type" == "disk" ]] || continue
|
||||||
|
[[ "$name" == "$skip" ]] && continue
|
||||||
|
case "$name" in
|
||||||
|
/dev/loop*|/dev/zram*|/dev/sr*|/dev/fd*|/dev/ram*|/dev/nbd*|/dev/md*)
|
||||||
|
continue ;;
|
||||||
|
esac
|
||||||
|
[[ "$size" =~ ^[0-9]+$ ]] || continue
|
||||||
|
(( size >= MIN_DISK_BYTES )) || continue
|
||||||
|
hsize=$(lsblk -dno SIZE "$name" 2>/dev/null || echo "?")
|
||||||
|
model=$(lsblk -dno MODEL "$name" 2>/dev/null | tr -s '[:space:]' ' ' | sed 's/^ //;s/ $//')
|
||||||
|
printf '%s %s %s\n' "$name" "$hsize" "${model:-}"
|
||||||
|
done < <(lsblk -dpbno NAME,SIZE,TYPE 2>/dev/null)
|
||||||
|
} | sort -k2 -h -r # largest first — never default to a tiny leftover
|
||||||
|
}
|
||||||
|
|
||||||
|
mapfile -t disks < <(list_installable_disks)
|
||||||
|
[[ ${#disks[@]} -gt 0 ]] || fail "No installable disks found (need a whole disk ≥ $(( MIN_DISK_BYTES / 1024 / 1024 / 1024 )) GiB; floppy/loop/optical excluded)."
|
||||||
|
|
||||||
if [[ "$UNATTENDED" == "1" ]]; then
|
if [[ "$UNATTENDED" == "1" ]]; then
|
||||||
TARGET_DISK="${NOMARCHY_DISK:?NOMARCHY_DISK required in unattended mode}"
|
TARGET_DISK="${NOMARCHY_DISK:?NOMARCHY_DISK required in unattended mode}"
|
||||||
|
# Unattended still rejects non-installable targets (CI footgun).
|
||||||
|
if ! printf '%s\n' "${disks[@]}" | grep -q "^${TARGET_DISK} "; then
|
||||||
|
fail "$TARGET_DISK is not an installable disk (missing, live medium, or below size floor)."
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
choice=$(printf '%s\n' "${disks[@]}" \
|
choice=$(printf '%s\n' "${disks[@]}" \
|
||||||
| gum choose --header "Install Nomarchy on which disk? (EVERYTHING on it will be erased)")
|
| gum choose --header "Install Nomarchy on which disk? (EVERYTHING on it will be erased)")
|
||||||
|
|||||||
@@ -370,9 +370,23 @@ def run_switch() -> None:
|
|||||||
"Rebuild FAILED — scroll up for last lines; run nomarchy-doctor",
|
"Rebuild FAILED — scroll up for last lines; run nomarchy-doctor",
|
||||||
urgency="critical",
|
urgency="critical",
|
||||||
)
|
)
|
||||||
|
# Live ISO pins only the *default* theme's HM generation. Applying
|
||||||
|
# another preset offline tries to build the world from source (#113).
|
||||||
|
offline_hint = ""
|
||||||
|
try:
|
||||||
|
import socket
|
||||||
|
socket.create_connection(("1.1.1.1", 53), timeout=1.5).close()
|
||||||
|
except OSError:
|
||||||
|
offline_hint = (
|
||||||
|
" Offline? Only the live ISO's already-pinned theme is "
|
||||||
|
"guaranteed without a network — other themes need builds "
|
||||||
|
"or a binary cache. Connect and retry, or apply the default "
|
||||||
|
"theme (boreal)."
|
||||||
|
)
|
||||||
die(
|
die(
|
||||||
"rebuild failed (state already written; fix and re-run). "
|
"rebuild failed (state already written; fix and re-run). "
|
||||||
"Diagnose: nomarchy-doctor. Recovery: docs/RECOVERY.md"
|
"Diagnose: nomarchy-doctor. Recovery: docs/RECOVERY.md."
|
||||||
|
+ offline_hint
|
||||||
)
|
)
|
||||||
notify("Changes applied ✓")
|
notify("Changes applied ✓")
|
||||||
# Waybar runs from Hyprland's exec-once (not a systemd unit HM would
|
# Waybar runs from Hyprland's exec-once (not a systemd unit HM would
|
||||||
|
|||||||
@@ -72,6 +72,25 @@ check(
|
|||||||
"missing online-only compose-lock fallback warn",
|
"missing online-only compose-lock fallback warn",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# ── 1b. Disk picker: exclude floppy/pseudo/tiny (#112) ─────────────────
|
||||||
|
check(
|
||||||
|
"list_installable_disks" in script,
|
||||||
|
"missing list_installable_disks helper (#112)",
|
||||||
|
)
|
||||||
|
check(
|
||||||
|
"/dev/fd*" in script or r"/dev/fd*" in script,
|
||||||
|
"disk filter must exclude /dev/fd* (floppy — OVMF lists fd0 first)",
|
||||||
|
)
|
||||||
|
check(
|
||||||
|
"MIN_DISK_BYTES" in script,
|
||||||
|
"disk filter must enforce a minimum size floor",
|
||||||
|
)
|
||||||
|
check(
|
||||||
|
"8 * 1024 * 1024 * 1024" in script or "8 * 1024**3" in script
|
||||||
|
or "$((8 * 1024 * 1024 * 1024))" in script,
|
||||||
|
"default min disk size should be 8 GiB (catches fd0 and crumbs)",
|
||||||
|
)
|
||||||
|
|
||||||
# ── 2. Disk signature warn ──────────────────────────────────────────────
|
# ── 2. Disk signature warn ──────────────────────────────────────────────
|
||||||
sig_re = re.search(
|
sig_re = re.search(
|
||||||
r"grep -qiE '([^']+)'\s*<<<\s*\"\$existing_sig\"",
|
r"grep -qiE '([^']+)'\s*<<<\s*\"\$existing_sig\"",
|
||||||
|
|||||||
94
tools/check-menu-back.py
Normal file
94
tools/check-menu-back.py
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Guard the nomarchy-menu Back/Left contract (BACKLOG #106).
|
||||||
|
|
||||||
|
Every *internal* list picker built with `rofi_menu` must emit the shared
|
||||||
|
`↩ Back` row (via `back`, `printf … "$BACK"`, or a literal). External
|
||||||
|
modi/tools and free-text prompts are listed as exceptions — Esc is their
|
||||||
|
back path.
|
||||||
|
|
||||||
|
Usage: check-menu-back.py <modules/home/rofi.nix>
|
||||||
|
Pure — no rofi runtime. Fails on a list menu that can strand the user.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
sys.exit(f"usage: {sys.argv[0]} <rofi.nix>")
|
||||||
|
|
||||||
|
text = Path(sys.argv[1]).read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
# Strip Nix multi-line comments lightly? Keep simple — work on the whole file.
|
||||||
|
# Find `| rofi_menu` call sites (possibly with line breaks before `|`).
|
||||||
|
# Look at a window of lines before each call for BACK emission.
|
||||||
|
|
||||||
|
lines = text.splitlines()
|
||||||
|
|
||||||
|
# Exception markers: if the window mentions these, allow no BACK row.
|
||||||
|
EXCEPTION_MARKERS = (
|
||||||
|
"External tool:",
|
||||||
|
"external modi",
|
||||||
|
"Esc closes",
|
||||||
|
"Esc is their back",
|
||||||
|
"free-text",
|
||||||
|
"Free-text",
|
||||||
|
"no list to",
|
||||||
|
"rofi_menu -p search", # web: empty stdin free-text
|
||||||
|
"rofi_menu -p ask",
|
||||||
|
# calc/emoji/network are exec-only without rofi_menu list construction
|
||||||
|
)
|
||||||
|
|
||||||
|
def window_has_back(chunk: str) -> bool:
|
||||||
|
# Shared helpers emit ↩ Back: bare `back`, printf of $BACK, or a literal.
|
||||||
|
if re.search(r"(?m)^\s*back\s*$", chunk):
|
||||||
|
return True
|
||||||
|
if re.search(r"\bback\b", chunk) and "back()" not in chunk.split("rofi_menu")[0][-20:]:
|
||||||
|
# `back` as a command (not only the word in comments). Prefer line-start.
|
||||||
|
if re.search(r"(?m)^\s*back\b", chunk):
|
||||||
|
return True
|
||||||
|
if "$BACK" in chunk or r"\$BACK" in chunk or "↩ Back" in chunk:
|
||||||
|
return True
|
||||||
|
if re.search(r'printf[^\n]*BACK', chunk):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def window_is_exception(chunk: str) -> bool:
|
||||||
|
return any(m in chunk for m in EXCEPTION_MARKERS)
|
||||||
|
|
||||||
|
|
||||||
|
# Match lines that invoke rofi_menu as a picker (not the function definition).
|
||||||
|
invoke_re = re.compile(r"\brofi_menu\b")
|
||||||
|
bad: list[str] = []
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if "rofi_menu()" in line or "rofi_menu ()" in line:
|
||||||
|
continue
|
||||||
|
if not invoke_re.search(line):
|
||||||
|
continue
|
||||||
|
# Skip comments and the definition of the helper's docs.
|
||||||
|
stripped = line.lstrip()
|
||||||
|
if stripped.startswith("#"):
|
||||||
|
continue
|
||||||
|
# Window: up to 40 lines before this call (menu construction).
|
||||||
|
start = max(0, i - 40)
|
||||||
|
chunk = "\n".join(lines[start : i + 1])
|
||||||
|
if window_is_exception(chunk):
|
||||||
|
continue
|
||||||
|
# Free-text: empty stdin piped in
|
||||||
|
if re.search(r"rofi_menu[^\n]*<\s*/dev/null", chunk) or re.search(
|
||||||
|
r"rofi_menu[^\n]*\n[^\n]*<\s*/dev/null", chunk
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
if not window_has_back(chunk):
|
||||||
|
bad.append(f"line {i + 1}: rofi_menu without ↩ Back in preceding construction:\n {line.strip()}")
|
||||||
|
|
||||||
|
if bad:
|
||||||
|
print("check-menu-back: FAIL — internal list menus must end with ↩ Back:\n", file=sys.stderr)
|
||||||
|
for b in bad:
|
||||||
|
print(b, file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"check-menu-back: ok — {sum(1 for l in lines if invoke_re.search(l) and 'rofi_menu()' not in l)} rofi_menu sites scanned")
|
||||||
Reference in New Issue
Block a user