Files
Nomarchy/modules/state-read.nix
Bernardo Magri d8e1a13d50
Some checks failed
Check / eval (push) Has been cancelled
refactor(#107): theme-state.json → state.json, theme-sync → state-sync
The machine flake's git-tracked settings file is system state, not
"theme" only — rename it to state.json. CLI becomes nomarchy-state-sync
with a nomarchy-theme-sync symlink for scripts and muscle memory.

Eval (mkFlake, doctor, lifecycle) still accepts theme-state.json; the
next write migrates to state.json and removes the legacy file.
Documented in MIGRATION.md; drop the CLI alias after release notes.
2026-07-15 11:26:59 +01:00

91 lines
2.8 KiB
Nix

# Pure state.json loader — fail closed with a short, actionable
# message instead of a raw `readFile` / `fromJSON` stack buried in a
# consumer. Used by modules/home/theme.nix (required path), the NixOS
# stateFile consumers, and lib.mkFlake (early gate).
#
# Field-level schema checks stay in theme.nix (post-defaults). This file
# only gates *existence* and *JSON-shape* so the first failure the user
# sees points at the state file, not at nightlight.nix.
#
# Callers may still pass a legacy path (state.json); messages name
# whatever path they gave so a half-migrated checkout is still debuggable.
{ lib }:
path:
let
pathStr = toString path;
baseName = baseNameOf pathStr;
tip = ''
Fix:
Missing file copy the template next to your flake.nix:
templates/downstream/state.json
or regenerate from a preset:
nomarchy-state-sync apply boreal
Bad syntax / wrong shape edit ${baseName} (trailing commas
are the usual culprit) or reset with `apply` as above.
Field-level schema (colors, ui, border, ):
nomarchy-state-sync validate
Eval-time field checks live in modules/home/theme.nix.
Still on theme-state.json? Rename it to state.json (or just run
any menu write / `nomarchy-state-sync set` it migrates).'';
missingMsg = ''
Nomarchy: state file is missing:
${pathStr}
This file is required (appearance + menu settings). Add state.json to
your flake checkout so evaluation stays pure.
${tip}'';
emptyMsg = ''
Nomarchy: state file is empty:
${pathStr}
${tip}'';
notObjectMsg = ''
Nomarchy: state file must be a JSON object `{ ... }`:
${pathStr}
${tip}'';
# lib.trim / lib.strings.trim — strip leading/trailing whitespace so an
# all-whitespace file counts as empty and a BOM-less `{` is recognized.
strip = s:
let
# Prefer lib.trim when present (nixpkgs ≥ 23.11); fall back so a
# very old pin still gates missing/non-object.
trim =
if lib ? trim then lib.trim
else if lib.strings ? trim then lib.strings.trim
else (x: x);
in trim s;
in
if !(builtins.pathExists path) then
throw missingMsg
else
let
raw = builtins.readFile path;
stripped = strip raw;
in
if stripped == "" then
throw emptyMsg
# Reject non-objects before fromJSON where we can (null / array / string
# literals). Subtle syntax errors still surface from fromJSON itself —
# those messages already include line/column; the path tip above is the
# part a raw stack used to bury.
else if builtins.match "[[:space:]]*\\{.*" stripped == null then
throw notObjectMsg
else
let
value = builtins.fromJSON raw;
in
if !(builtins.isAttrs value) then
throw notObjectMsg
else
value