refactor(#107): theme-state.json → state.json, theme-sync → state-sync
Some checks failed
Check / eval (push) Has been cancelled
Some checks failed
Check / eval (push) Has been cancelled
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.
This commit is contained in:
@@ -1,28 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Nomarchy Control Center — TUI frontend over nomarchy-theme-sync and tools
|
||||
# Nomarchy Control Center — TUI frontend over nomarchy-state-sync and tools
|
||||
# Uses charmbracelet/gum for rendering.
|
||||
|
||||
trap 'exit 0' SIGINT
|
||||
|
||||
function get_state() {
|
||||
nomarchy-theme-sync get "$1" 2>/dev/null || echo ""
|
||||
nomarchy-state-sync get "$1" 2>/dev/null || echo ""
|
||||
}
|
||||
|
||||
function set_state() {
|
||||
nomarchy-theme-sync --quiet set "$1" "$2" --no-switch
|
||||
nomarchy-state-sync --quiet set "$1" "$2" --no-switch
|
||||
}
|
||||
|
||||
function first_boot() {
|
||||
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "Welcome to Nomarchy!"
|
||||
|
||||
echo "Pick a Theme Preset:"
|
||||
themes=$(nomarchy-theme-sync list)
|
||||
themes=$(nomarchy-state-sync list)
|
||||
chosen_theme=$(printf "%s" "$themes" | gum choose)
|
||||
if [ -n "$chosen_theme" ]; then
|
||||
echo "Applying theme $chosen_theme..."
|
||||
nomarchy-theme-sync apply "$chosen_theme"
|
||||
nomarchy-state-sync apply "$chosen_theme"
|
||||
fi
|
||||
|
||||
if gum confirm "Enable auto-commit for your config? (settings changes + a sweep of pending edits before each rebuild)"; then
|
||||
@@ -113,10 +113,10 @@ function appearance_menu() {
|
||||
choice=$(gum choose "Pick Theme" "Toggle Blur" "Set Gaps" "Back")
|
||||
case "$choice" in
|
||||
"Pick Theme")
|
||||
themes=$(nomarchy-theme-sync list)
|
||||
themes=$(nomarchy-state-sync list)
|
||||
chosen=$(printf "%s" "$themes" | gum choose)
|
||||
if [ -n "$chosen" ]; then
|
||||
nomarchy-theme-sync apply "$chosen"
|
||||
nomarchy-state-sync apply "$chosen"
|
||||
fi
|
||||
;;
|
||||
"Toggle Blur")
|
||||
|
||||
@@ -67,21 +67,33 @@ for mp in / /boot /nix; do
|
||||
done
|
||||
|
||||
# ── the flake + state file ───────────────────────────────────────────
|
||||
if [ -f "$flake/theme-state.json" ]; then
|
||||
if jq empty "$flake/theme-state.json" 2>/dev/null; then
|
||||
ok "theme-state.json parses"
|
||||
# #107: prefer state.json; theme-state.json still counts until migrated.
|
||||
state_file=
|
||||
if [ -f "$flake/state.json" ]; then
|
||||
state_file="$flake/state.json"
|
||||
elif [ -f "$flake/theme-state.json" ]; then
|
||||
state_file="$flake/theme-state.json"
|
||||
fi
|
||||
if [ -n "$state_file" ]; then
|
||||
state_base=$(basename "$state_file")
|
||||
if jq empty "$state_file" 2>/dev/null; then
|
||||
ok "$state_base parses"
|
||||
else
|
||||
bad "theme-state.json is not valid JSON (rebuilds will fail)" \
|
||||
"nomarchy-theme-sync validate (names the spot; fix it, or re-apply a theme)"
|
||||
bad "$state_base is not valid JSON (rebuilds will fail)" \
|
||||
"nomarchy-state-sync validate (names the spot; fix it, or re-apply a theme)"
|
||||
fi
|
||||
if git -C "$flake" ls-files --error-unmatch theme-state.json >/dev/null 2>&1; then
|
||||
ok "theme-state.json is git-tracked"
|
||||
if git -C "$flake" ls-files --error-unmatch "$state_base" >/dev/null 2>&1; then
|
||||
ok "$state_base is git-tracked"
|
||||
else
|
||||
bad "theme-state.json is NOT git-tracked (flake evaluation can't see it)" \
|
||||
"git -C $flake add theme-state.json"
|
||||
bad "$state_base is NOT git-tracked (flake evaluation can't see it)" \
|
||||
"git -C $flake add $state_base"
|
||||
fi
|
||||
if [ "$state_base" = "theme-state.json" ]; then
|
||||
warn "state file still named theme-state.json — run any menu write or" \
|
||||
"nomarchy-state-sync set … to migrate to state.json"
|
||||
fi
|
||||
else
|
||||
skip "theme-state.json (no flake checkout at $flake)"
|
||||
skip "state.json (no flake checkout at $flake)"
|
||||
fi
|
||||
|
||||
if [ -d "$flake/.git" ]; then
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# First-session "you're set" toast (nomarchy.firstBootWelcome). A package
|
||||
# so checks.first-boot can exercise the gate on a minimal node — same
|
||||
# pattern as nomarchy-battery-notify. libnotify + theme-sync stay on PATH
|
||||
# pattern as nomarchy-battery-notify. libnotify + state-sync stay on PATH
|
||||
# (user unit / VM shim), not runtimeInputs.
|
||||
{ writeShellApplication, coreutils }:
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# nomarchy-first-boot — one-shot "you're set" toast on the first session.
|
||||
# Marker is settings.firstBootShown in the flake's theme-state.json
|
||||
# Marker is settings.firstBootShown in the flake's state.json
|
||||
# (in-checkout state; never ~/.local/state). notify-send and
|
||||
# nomarchy-theme-sync come from PATH so the VM check can shim them.
|
||||
# nomarchy-state-sync come from PATH so the VM check can shim them.
|
||||
|
||||
# Live ISO already has its own welcome (hosts/live.nix); skip there so
|
||||
# users aren't double-toasted and the live seed doesn't get a sticky
|
||||
@@ -13,8 +13,8 @@ if [ "$hn" = nomarchy-live ]; then
|
||||
fi
|
||||
|
||||
# Already shown → silent. Missing key / no checkout → treat as not shown
|
||||
# (theme-sync get exits non-zero when the key is absent).
|
||||
shown=$(nomarchy-theme-sync get settings.firstBootShown 2>/dev/null || true)
|
||||
# (state-sync get exits non-zero when the key is absent).
|
||||
shown=$(nomarchy-state-sync get settings.firstBootShown 2>/dev/null || true)
|
||||
case "$shown" in
|
||||
true|1|yes) exit 0 ;;
|
||||
esac
|
||||
@@ -46,7 +46,7 @@ if [ -z "$ok" ]; then
|
||||
fi
|
||||
|
||||
# Persist in the checkout so re-login is silent. --no-switch: marker only.
|
||||
if ! nomarchy-theme-sync --quiet set settings.firstBootShown true --no-switch; then
|
||||
if ! nomarchy-state-sync --quiet set settings.firstBootShown true --no-switch; then
|
||||
# No writable flake checkout (or tool missing) — still showed the toast;
|
||||
# without a marker it may reappear next login. Don't fail the unit.
|
||||
exit 0
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
, btrfs-progs # inspect-internal map-swapfile (hibernation offset)
|
||||
, xkeyboard_config # human-readable installed layout/variant catalog
|
||||
# Baked metadata — what this installer installs and where it came from.
|
||||
, templateDir # templates/downstream (home.nix, theme-state.json)
|
||||
, templateDir # templates/downstream (home.nix, state.json)
|
||||
, nomarchyLock # the distro's flake.lock (for offline lock composition)
|
||||
, hardwareModuleNames # newline-separated nixos-hardware module names
|
||||
, nixpkgsPath # pinned nixpkgs source (NIX_PATH for disko's eval)
|
||||
@@ -54,7 +54,7 @@ stdenvNoCC.mkDerivation {
|
||||
mkdir -p "$share/template"
|
||||
# Full downstream template is the SoT; install script copies + patches.
|
||||
cp ${templateDir}/flake.nix ${templateDir}/system.nix \
|
||||
${templateDir}/home.nix ${templateDir}/theme-state.json \
|
||||
${templateDir}/home.nix ${templateDir}/state.json \
|
||||
"$share/template/"
|
||||
install -Dm644 patch-template.py "$share/patch-template.py"
|
||||
|
||||
|
||||
@@ -585,7 +585,7 @@ rm -rf /mnt/etc/nixos
|
||||
cp "$SHARE/template/flake.nix" \
|
||||
"$SHARE/template/system.nix" \
|
||||
"$SHARE/template/home.nix" \
|
||||
"$SHARE/template/theme-state.json" \
|
||||
"$SHARE/template/state.json" \
|
||||
"$FLAKE_DIR/"
|
||||
|
||||
# Detected hardware → flags for the patcher (safe defaults active).
|
||||
@@ -679,7 +679,7 @@ if (( compose_ok != 0 )); then
|
||||
(cd "$FLAKE_DIR" && nix --extra-experimental-features "nix-command flakes" flake lock)
|
||||
fi
|
||||
|
||||
# A flake worktree must be git-tracked (theme-state.json especially).
|
||||
# A flake worktree must be git-tracked (state.json especially).
|
||||
(
|
||||
cd "$FLAKE_DIR"
|
||||
git init -q
|
||||
@@ -702,7 +702,7 @@ success "Configuration written to ~$USERNAME/.nomarchy"
|
||||
section "Installing (this takes a while)"
|
||||
|
||||
# Seed the target store with the flake source + all inputs so the first
|
||||
# `nomarchy-theme-sync apply` (and the HM pre-activation below) work
|
||||
# `nomarchy-state-sync apply` (and the HM pre-activation below) work
|
||||
# before the machine has ever seen a network. Two steps because
|
||||
# `flake archive --to` enforces signatures and locally-evaluated source
|
||||
# paths have none; plain `nix copy` accepts --no-check-sigs.
|
||||
|
||||
@@ -116,7 +116,7 @@ def build_installer_region(v: dict) -> str:
|
||||
]
|
||||
|
||||
# Auto-login is deliberately NOT emitted here — it is seeded into
|
||||
# theme-state.json instead (patch_state). A line in system.nix outranks
|
||||
# state.json instead (patch_state). A line in system.nix outranks
|
||||
# the state default, which would make the System › Auto-login toggle
|
||||
# write JSON that nothing reads.
|
||||
|
||||
@@ -260,7 +260,7 @@ def patch_system(text: str, v: dict) -> str:
|
||||
|
||||
|
||||
def patch_state(text: str, v: dict) -> str:
|
||||
"""Seed menu-owned settings into theme-state.json.
|
||||
"""Seed menu-owned settings into state.json.
|
||||
|
||||
These live in the state rather than system.nix precisely so the menu can
|
||||
change them later: a baked Nix assignment would outrank the state default
|
||||
@@ -286,7 +286,7 @@ def main() -> None:
|
||||
"flake.nix": patch_flake,
|
||||
"home.nix": patch_home,
|
||||
"system.nix": patch_system,
|
||||
"theme-state.json": patch_state,
|
||||
"state.json": patch_state,
|
||||
}
|
||||
for name, fn in mapping.items():
|
||||
path = flake_dir / name
|
||||
|
||||
@@ -15,12 +15,12 @@ let
|
||||
flake="''${NOMARCHY_PATH:-$HOME/.nomarchy}"
|
||||
'';
|
||||
|
||||
# Opt-in sweep (settings.autoCommit — the same flag theme-sync honours):
|
||||
# Opt-in sweep (settings.autoCommit — the same flag state-sync honours):
|
||||
# commit EVERYTHING dirty in the machine flake before a pull/rebuild/
|
||||
# home switch, so hand edits to system.nix/home.nix and lock bumps land
|
||||
# in history at the moment they become live — `git log` mirrors the
|
||||
# generation list. Complements nomarchy-theme-sync's auto_commit, which
|
||||
# is pathspec-limited to theme-state.json on menu writes precisely so
|
||||
# generation list. Complements nomarchy-state-sync's auto_commit, which
|
||||
# is pathspec-limited to state.json on menu writes precisely so
|
||||
# half-finished hand edits never ride a settings-named commit; here the
|
||||
# sweep is the point, and the commit body lists what was swept. Never
|
||||
# fatal — callers `|| true` so a git hiccup can't block a rebuild. Not
|
||||
@@ -31,8 +31,12 @@ let
|
||||
label="''${1:-rebuild}"
|
||||
[ -d "$flake/.git" ] || exit 0
|
||||
command -v git >/dev/null 2>&1 || exit 0
|
||||
# #107: prefer state.json; still honour a legacy theme-state.json
|
||||
# until the next menu write migrates the checkout.
|
||||
state="$flake/state.json"
|
||||
[ -r "$state" ] || state="$flake/theme-state.json"
|
||||
flag=$(${jq}/bin/jq -r '.settings.autoCommit // false' \
|
||||
"$flake/theme-state.json" 2>/dev/null || true)
|
||||
"$state" 2>/dev/null || true)
|
||||
[ "$flag" = "true" ] || exit 0
|
||||
dirty=$(git -C "$flake" status --porcelain || true)
|
||||
[ -n "$dirty" ] || exit 0
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
}:
|
||||
|
||||
stdenvNoCC.mkDerivation {
|
||||
pname = "nomarchy-theme-sync";
|
||||
version = "0.4.0";
|
||||
pname = "nomarchy-state-sync";
|
||||
version = "0.5.0";
|
||||
|
||||
src = ./.;
|
||||
|
||||
@@ -22,8 +22,8 @@ stdenvNoCC.mkDerivation {
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
install -Dm755 nomarchy-theme-sync.py $out/bin/nomarchy-theme-sync
|
||||
patchShebangs $out/bin/nomarchy-theme-sync
|
||||
install -Dm755 nomarchy-state-sync.py $out/bin/nomarchy-state-sync
|
||||
patchShebangs $out/bin/nomarchy-state-sync
|
||||
|
||||
${lib.optionalString (themesDir != null) ''
|
||||
mkdir -p $out/share/nomarchy
|
||||
@@ -32,18 +32,23 @@ stdenvNoCC.mkDerivation {
|
||||
|
||||
# Stdlib-only Python. home-manager is deliberately NOT wrapped in —
|
||||
# the rebuild must use the user's own home-manager from their PATH.
|
||||
wrapProgram $out/bin/nomarchy-theme-sync \
|
||||
wrapProgram $out/bin/nomarchy-state-sync \
|
||||
--prefix PATH : ${lib.makeBinPath [ awww libnotify git ]} \
|
||||
${lib.optionalString (themesDir != null)
|
||||
"--set NOMARCHY_DEFAULT_THEMES $out/share/nomarchy/themes"}
|
||||
|
||||
# #107: old CLI name kept as a symlink so muscle memory and scripts
|
||||
# survive a pull (drop after the next stable release notes say so).
|
||||
# After wrapProgram so it points at the wrapper, not the raw script.
|
||||
ln -s nomarchy-state-sync $out/bin/nomarchy-theme-sync
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
meta = {
|
||||
description = "Nomarchy theming: JSON state writer + Home Manager rebuild dispatcher";
|
||||
description = "Nomarchy state writer + Home Manager rebuild dispatcher";
|
||||
license = lib.licenses.mit;
|
||||
mainProgram = "nomarchy-theme-sync";
|
||||
mainProgram = "nomarchy-state-sync";
|
||||
platforms = lib.platforms.linux;
|
||||
};
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
"""nomarchy-theme-sync — state writer for Nomarchy's declarative theming.
|
||||
"""nomarchy-state-sync — state writer for Nomarchy's declarative theming.
|
||||
|
||||
Single source of truth: $NOMARCHY_PATH/theme-state.json (inside the flake,
|
||||
Single source of truth: $NOMARCHY_PATH/state.json (inside the flake,
|
||||
read purely by Home Manager via the nomarchy.stateFile option).
|
||||
Legacy name `theme-state.json` is still read (and migrated on write).
|
||||
The old binary name `nomarchy-theme-sync` is a symlink to this tool.
|
||||
|
||||
Theme changes are applied by Home Manager: this tool writes the new state
|
||||
and runs `home-manager switch` (override with $NOMARCHY_REBUILD, or skip
|
||||
@@ -39,7 +41,24 @@ from pathlib import Path
|
||||
# ─── Paths ────────────────────────────────────────────────────────────────
|
||||
|
||||
FLAKE_DIR = Path(os.environ.get("NOMARCHY_PATH", Path.home() / ".nomarchy")).expanduser()
|
||||
STATE_FILE = FLAKE_DIR / "theme-state.json"
|
||||
# Canonical name after #107. Legacy theme-state.json (and a brief theme.json
|
||||
# misnomer some notes used) are still accepted for existing checkouts.
|
||||
STATE_NAME = "state.json"
|
||||
LEGACY_STATE_NAMES = ("theme-state.json", "theme.json")
|
||||
|
||||
|
||||
def resolve_state_file(flake_dir: Path = FLAKE_DIR) -> Path:
|
||||
preferred = flake_dir / STATE_NAME
|
||||
if preferred.exists():
|
||||
return preferred
|
||||
for name in LEGACY_STATE_NAMES:
|
||||
legacy = flake_dir / name
|
||||
if legacy.exists():
|
||||
return legacy
|
||||
return preferred
|
||||
|
||||
|
||||
STATE_FILE = resolve_state_file()
|
||||
|
||||
# Preset search path: the user's flake first (their custom themes win),
|
||||
# then the presets baked into this package by the Nix build.
|
||||
@@ -51,21 +70,24 @@ WALLPAPER_EXTS = {".png", ".jpg", ".jpeg", ".webp"}
|
||||
|
||||
QUIET = False
|
||||
|
||||
# argv[0] may still be the compatibility name nomarchy-theme-sync.
|
||||
_TOOL = Path(sys.argv[0]).name or "nomarchy-state-sync"
|
||||
|
||||
|
||||
def log(msg: str) -> None:
|
||||
if not QUIET:
|
||||
print(f"nomarchy-theme-sync: {msg}")
|
||||
print(f"{_TOOL}: {msg}")
|
||||
|
||||
|
||||
def die(msg: str) -> "None":
|
||||
print(f"nomarchy-theme-sync: error: {msg}", file=sys.stderr)
|
||||
print(f"{_TOOL}: error: {msg}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# All Nomarchy theme notifications share this synchronous tag, so the
|
||||
# notification daemon (swaync) REPLACES the previous one in place instead
|
||||
# of stacking — the "rebuilding…" toast becomes the "applied ✓" toast.
|
||||
NOTIFY_SYNC_TAG = "nomarchy-theme-sync"
|
||||
NOTIFY_SYNC_TAG = "nomarchy-state-sync"
|
||||
|
||||
|
||||
def notify(body: str, summary: str = "Nomarchy", *,
|
||||
@@ -87,13 +109,14 @@ def notify(body: str, summary: str = "Nomarchy", *,
|
||||
|
||||
# ─── State management ─────────────────────────────────────────────────────
|
||||
|
||||
def load_state(path: Path = STATE_FILE) -> dict:
|
||||
def load_state(path: Path | None = None) -> dict:
|
||||
path = path or resolve_state_file()
|
||||
try:
|
||||
return json.loads(path.read_text())
|
||||
except FileNotFoundError:
|
||||
die(f"state file not found: {path} (set $NOMARCHY_PATH to your flake checkout)")
|
||||
except json.JSONDecodeError as e:
|
||||
die(f"theme-state.json has a JSON syntax error at line {e.lineno}, "
|
||||
die(f"{path.name} has a JSON syntax error at line {e.lineno}, "
|
||||
f"column {e.colno}: {e.msg}\n"
|
||||
f" file: {path}\n"
|
||||
f" fix: correct the syntax by hand (a trailing comma is the "
|
||||
@@ -103,32 +126,48 @@ def load_state(path: Path = STATE_FILE) -> dict:
|
||||
def write_state(state: dict) -> None:
|
||||
"""Atomic write: render to a temp file in the same dir, then rename.
|
||||
Validates first — an invalid state never reaches disk (the old file
|
||||
stays untouched), so a bad `set` can't brick the next rebuild."""
|
||||
stays untouched), so a bad `set` can't brick the next rebuild.
|
||||
Always writes the canonical state.json; removes a legacy-named sibling
|
||||
so a checkout never has two competing sources of truth."""
|
||||
errors, warnings = validate_state(state)
|
||||
for w in warnings:
|
||||
log(f"warning: {w}")
|
||||
if errors:
|
||||
die("refusing to write an invalid state (nothing changed):\n ✖ "
|
||||
+ "\n ✖ ".join(errors))
|
||||
STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
fd, tmp = tempfile.mkstemp(dir=STATE_FILE.parent, prefix=".theme-state.", suffix=".json")
|
||||
out = FLAKE_DIR / STATE_NAME
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
fd, tmp = tempfile.mkstemp(dir=out.parent, prefix=".state.", suffix=".json")
|
||||
try:
|
||||
with os.fdopen(fd, "w") as f:
|
||||
json.dump(state, f, indent=2)
|
||||
f.write("\n")
|
||||
os.replace(tmp, STATE_FILE)
|
||||
os.replace(tmp, out)
|
||||
except BaseException:
|
||||
os.unlink(tmp)
|
||||
raise
|
||||
log(f"state written to {STATE_FILE}")
|
||||
for name in LEGACY_STATE_NAMES:
|
||||
legacy = FLAKE_DIR / name
|
||||
if legacy.exists() and legacy.resolve() != out.resolve():
|
||||
try:
|
||||
legacy.unlink()
|
||||
log(f"migrated: removed legacy {name} (now {STATE_NAME})")
|
||||
except OSError as e:
|
||||
log(f"warning: could not remove legacy {name}: {e}")
|
||||
log(f"state written to {out}")
|
||||
|
||||
# Flakes only see git-tracked files. theme-state.json ships tracked,
|
||||
# Flakes only see git-tracked files. state.json ships tracked,
|
||||
# but make sure a fresh checkout / reset can't silently hide it.
|
||||
if (FLAKE_DIR / ".git").exists() and shutil.which("git"):
|
||||
subprocess.run(
|
||||
["git", "-C", str(FLAKE_DIR), "add", "--intent-to-add", "theme-state.json"],
|
||||
["git", "-C", str(FLAKE_DIR), "add", "--intent-to-add", STATE_NAME],
|
||||
capture_output=True,
|
||||
)
|
||||
for name in LEGACY_STATE_NAMES:
|
||||
subprocess.run(
|
||||
["git", "-C", str(FLAKE_DIR), "rm", "-f", "--ignore-unmatch", name],
|
||||
capture_output=True,
|
||||
)
|
||||
|
||||
|
||||
def auto_commit_enabled(state: dict) -> bool:
|
||||
@@ -136,7 +175,7 @@ def auto_commit_enabled(state: dict) -> bool:
|
||||
|
||||
|
||||
def auto_commit(message: str) -> None:
|
||||
"""Opt-in (settings.autoCommit): commit theme-state.json — and nothing
|
||||
"""Opt-in (settings.autoCommit): commit state.json — and nothing
|
||||
else — after a mutation, so settings history is `git log`. The pathspec
|
||||
keeps unrelated dirty files out of the commit; a missing git identity
|
||||
falls back to a Nomarchy one so a fresh machine never errors. Callers
|
||||
@@ -150,16 +189,18 @@ def auto_commit(message: str) -> None:
|
||||
if not (FLAKE_DIR / ".git").exists() or shutil.which("git") is None:
|
||||
return
|
||||
git = ["git", "-C", str(FLAKE_DIR)]
|
||||
# Pathspec: preferred name plus any leftover legacy file still staged.
|
||||
paths = [STATE_NAME, *LEGACY_STATE_NAMES]
|
||||
# No-op when the file already matches HEAD (a `set` to the same value).
|
||||
# On a repo with no commits yet this diff errors — then just commit.
|
||||
if subprocess.run(git + ["diff", "--quiet", "HEAD", "--", "theme-state.json"],
|
||||
if subprocess.run(git + ["diff", "--quiet", "HEAD", "--"] + paths,
|
||||
capture_output=True).returncode == 0:
|
||||
return
|
||||
if not subprocess.run(git + ["config", "user.email"],
|
||||
capture_output=True, text=True).stdout.strip():
|
||||
git += ["-c", "user.name=Nomarchy", "-c", "user.email=nomarchy@localhost"]
|
||||
result = subprocess.run(
|
||||
git + ["commit", "--quiet", "-m", message, "--", "theme-state.json"],
|
||||
git + ["commit", "--quiet", "-m", message, "--"] + paths,
|
||||
capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
log(f"auto-committed: {message}")
|
||||
@@ -212,7 +253,7 @@ def validate_state(state) -> tuple:
|
||||
|
||||
if not isinstance(state, dict):
|
||||
err("top level", "must be a JSON object",
|
||||
"re-apply a preset: nomarchy-theme-sync apply boreal")
|
||||
"re-apply a preset: nomarchy-state-sync apply boreal")
|
||||
return errors, warnings
|
||||
|
||||
for k in state:
|
||||
@@ -315,7 +356,7 @@ def cmd_validate(_args) -> None:
|
||||
for w in warnings:
|
||||
print(f" ● {w}")
|
||||
if errors:
|
||||
print(f"nomarchy-theme-sync: {STATE_FILE} has "
|
||||
print(f"nomarchy-state-sync: {STATE_FILE} has "
|
||||
f"{len(errors)} problem(s):", file=sys.stderr)
|
||||
for e in errors:
|
||||
print(f" ✖ {e}", file=sys.stderr)
|
||||
@@ -340,7 +381,7 @@ def check_fonts(state: dict) -> None:
|
||||
families = [f.strip().lower() for f in result.stdout.split(",")]
|
||||
if result.returncode != 0 or name.strip().lower() not in families:
|
||||
fallback = result.stdout.strip() or "unknown"
|
||||
print(f"nomarchy-theme-sync: warning: fonts.{key} '{name}' is not "
|
||||
print(f"nomarchy-state-sync: warning: fonts.{key} '{name}' is not "
|
||||
f"installed — fontconfig will substitute '{fallback}'",
|
||||
file=sys.stderr)
|
||||
notify(f"Font '{name}' is not installed — using '{fallback}'")
|
||||
@@ -490,7 +531,7 @@ def apply_named(theme: str, no_switch: bool = False) -> None:
|
||||
candidate = Path(theme).expanduser()
|
||||
preset_path = candidate if candidate.is_file() else find_preset(theme)
|
||||
if preset_path is None:
|
||||
die(f"unknown theme '{theme}' (try `nomarchy-theme-sync list`)")
|
||||
die(f"unknown theme '{theme}' (try `nomarchy-state-sync list`)")
|
||||
|
||||
preset = json.loads(preset_path.read_text())
|
||||
# Merge over current state. Presets carry a full appearance block —
|
||||
@@ -642,7 +683,7 @@ def main() -> None:
|
||||
global QUIET
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="nomarchy-theme-sync",
|
||||
prog="nomarchy-state-sync",
|
||||
description="Nomarchy theming — state writer + Home Manager rebuild dispatcher.",
|
||||
)
|
||||
parser.add_argument("--quiet", action="store_true", help="suppress progress output")
|
||||
@@ -671,7 +712,7 @@ def main() -> None:
|
||||
p.add_argument("--no-switch", action="store_true", help="write state only, skip the rebuild")
|
||||
p.set_defaults(func=cmd_auto)
|
||||
|
||||
sub.add_parser("validate", help="check theme-state.json against the schema (read-only)").set_defaults(func=cmd_validate)
|
||||
sub.add_parser("validate", help="check state.json against the schema (read-only)").set_defaults(func=cmd_validate)
|
||||
|
||||
sub.add_parser("wallpaper", help="apply the current wallpaper via swww").set_defaults(func=cmd_wallpaper)
|
||||
|
||||
Reference in New Issue
Block a user