dotcli: Add dot kde diff for schema-backed settings, fix apply/diff completions
Implements the broad, read-only schema-backed scan for dot kde diff: walks every (rcfile, group, key) reachable through the kcfg mapping table, compares live values against their schema-declared defaults, and reports each mismatch tagged declared or undeclared. Never writes. Also fixes dot kde completions, which only ever offered save/help — apply (added in a prior task) and diff were both missing.
This commit is contained in:
@@ -19,9 +19,17 @@ Add a README row for `dot kde diff`.
|
|||||||
|
|
||||||
## Acceptance criteria
|
## Acceptance criteria
|
||||||
|
|
||||||
- [ ] `dot kde diff` reports every schema-backed setting whose live value differs from its schema-declared default
|
- [x] `dot kde diff` reports every schema-backed setting whose live value differs from its schema-declared default
|
||||||
- [ ] Each reported mismatch is tagged declared or undeclared based on manifest presence
|
- [x] Each reported mismatch is tagged declared or undeclared based on manifest presence
|
||||||
- [ ] `dot kde diff` makes no writes under any circumstances
|
- [x] `dot kde diff` makes no writes under any circumstances
|
||||||
- [ ] `dot kde diff help` prints usage without scanning
|
- [x] `dot kde diff help` prints usage without scanning
|
||||||
- [ ] Tests run against a scratch `$HOME` and fixture `.kcfg` schema directory, covering: a declared mismatch, an undeclared mismatch, and a setting matching its default (not reported)
|
- [x] Tests run against a scratch `$HOME` and fixture `.kcfg` schema directory, covering: a declared mismatch, an undeclared mismatch, and a setting matching its default (not reported)
|
||||||
- [ ] README has a row for `dot kde diff`
|
- [x] README has a row for `dot kde diff`
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
- `cmd_diff` (in `commands/kde/kde.py`) reuses `build_kcfg_map`/`iter_schema_identifiers` (already built for `kde.py complete`) to walk every schema-backed `(rcfile, group, key)`, then `find_schema_default`/`read_live_value` (already built for `save`) to compare live vs. default. No new scanning machinery was needed — this task's whole job was wiring existing pieces together into a read-only report.
|
||||||
|
- Output format: one line per mismatch, `<declared|undeclared> <identifier> = <live> (default: <default>)`. Not specified by the task, so chosen to read clearly and stay unambiguous under substring matching in tests (avoided bracketed tags like `[declared]`, since fish's `string match` glob treats `[...]` as a character class).
|
||||||
|
- `/review-uncommitted`'s Spec pass caught that `cmd_diff` had no error handling around `read_live_value`, unlike `cmd_apply`/`cmd_save`'s `try/except (ValueError, RuntimeError)` — a single `kreadconfig6` failure would have aborted the entire broad scan with an uncaught traceback, contradicting `diff`'s "report every mismatch" framing. Fixed: `cmd_diff` now catches `RuntimeError` per-identifier, prints a warning to stderr, and continues scanning the rest.
|
||||||
|
- The Standards pass flagged the "build map → iterate `sorted(set(iter_schema_identifiers(...)))`" shape as now duplicated between `cmd_diff` and `cmd_complete`, and the new test scenarios' fixture boilerplate as repeating the `apply` tests' shape almost verbatim. Left both as-is: the loop duplication is two call sites doing genuinely different things with the result, and the test boilerplate matches this file's already-established per-scenario convention (each scenario resets `$HOME` independently) rather than introducing a new pattern.
|
||||||
|
- Post-closeout fix (user-reported): `~/.config/fish/completions/dot.fish`'s `dot kde` completion block only ever listed `save`/`help` as verbs — `apply` was never added when task 0002 built it, and this task initially repeated the same omission for `diff`. Fixed both by adding `apply` and `diff` to the top-level verb-offering line and to the post-subcommand `help` gating; verified manually via `complete -C"dot kde "` and `complete -C"dot kde apply "`/`complete -C"dot kde diff "`.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ function _dot_kde_usage
|
|||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
apply push manifest entries onto the live system
|
apply push manifest entries onto the live system
|
||||||
|
diff scan for settings whose live value differs from its default
|
||||||
save write live KDE settings into the manifest
|
save write live KDE settings into the manifest
|
||||||
help show this message
|
help show this message
|
||||||
|
|
||||||
@@ -21,6 +22,9 @@ function _dot_kde
|
|||||||
case apply
|
case apply
|
||||||
python3 $helper_dir/kde.py apply $argv[2..-1]
|
python3 $helper_dir/kde.py apply $argv[2..-1]
|
||||||
return $status
|
return $status
|
||||||
|
case diff
|
||||||
|
python3 $helper_dir/kde.py diff $argv[2..-1]
|
||||||
|
return $status
|
||||||
case save
|
case save
|
||||||
python3 $helper_dir/kde.py save $argv[2..-1]
|
python3 $helper_dir/kde.py save $argv[2..-1]
|
||||||
return $status
|
return $status
|
||||||
|
|||||||
@@ -26,6 +26,15 @@ APPLY_USAGE = """usage: dot kde apply
|
|||||||
Pushes every manifest entry's declared value onto the live system.
|
Pushes every manifest entry's declared value onto the live system.
|
||||||
help show this message"""
|
help show this message"""
|
||||||
|
|
||||||
|
DIFF_USAGE = """usage: dot kde diff
|
||||||
|
|
||||||
|
Scans every schema-backed setting reachable through the kcfg mapping
|
||||||
|
table and reports each one whose live value differs from its
|
||||||
|
schema-declared default, tagged declared (present in the manifest)
|
||||||
|
or undeclared. Read-only -- never writes the manifest or the live
|
||||||
|
system.
|
||||||
|
help show this message"""
|
||||||
|
|
||||||
Setting = namedtuple("Setting", ["file", "group", "key"])
|
Setting = namedtuple("Setting", ["file", "group", "key"])
|
||||||
|
|
||||||
|
|
||||||
@@ -223,6 +232,36 @@ def cmd_apply(args, manifest_path, schema_dir):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_diff(args, manifest_path, schema_dir):
|
||||||
|
if args and args[0] == "help":
|
||||||
|
print(DIFF_USAGE)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if args:
|
||||||
|
print("dot kde diff: too many arguments", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
kcfg_map = build_kcfg_map(schema_dir)
|
||||||
|
manifest = load_manifest(manifest_path)
|
||||||
|
|
||||||
|
for identifier in sorted(set(iter_schema_identifiers(kcfg_map))):
|
||||||
|
setting = parse_identifier(identifier)
|
||||||
|
default = find_schema_default(kcfg_map.get(setting.file, []), setting)
|
||||||
|
try:
|
||||||
|
live = read_live_value(setting, default)
|
||||||
|
except RuntimeError as e:
|
||||||
|
print(f"dot kde diff: {e}", file=sys.stderr)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if live == default:
|
||||||
|
continue
|
||||||
|
|
||||||
|
tag = "declared" if identifier in manifest else "undeclared"
|
||||||
|
print(f"{tag} {identifier} = {live} (default: {default})")
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def cmd_complete(schema_dir):
|
def cmd_complete(schema_dir):
|
||||||
kcfg_map = build_kcfg_map(schema_dir)
|
kcfg_map = build_kcfg_map(schema_dir)
|
||||||
for identifier in sorted(set(iter_schema_identifiers(kcfg_map))):
|
for identifier in sorted(set(iter_schema_identifiers(kcfg_map))):
|
||||||
@@ -245,6 +284,9 @@ def main(argv):
|
|||||||
if command == "apply":
|
if command == "apply":
|
||||||
return cmd_apply(rest, manifest_path, schema_dir)
|
return cmd_apply(rest, manifest_path, schema_dir)
|
||||||
|
|
||||||
|
if command == "diff":
|
||||||
|
return cmd_diff(rest, manifest_path, schema_dir)
|
||||||
|
|
||||||
# Internal, not a user-facing `dot kde` subcommand -- called directly by
|
# Internal, not a user-facing `dot kde` subcommand -- called directly by
|
||||||
# completions/dot.fish to source candidates from the live schema, never
|
# completions/dot.fish to source candidates from the live schema, never
|
||||||
# dispatched to via kde.fish.
|
# dispatched to via kde.fish.
|
||||||
|
|||||||
@@ -457,6 +457,67 @@ dot kde apply extra-arg >/dev/null 2>&1
|
|||||||
set -l apply_extra_arg_status $status
|
set -l apply_extra_arg_status $status
|
||||||
@test "dot kde apply rejects an unexpected argument" $apply_extra_arg_status -ne 0
|
@test "dot kde apply rejects an unexpected argument" $apply_extra_arg_status -ne 0
|
||||||
|
|
||||||
|
# --- dot kde diff help touches neither the manifest nor kreadconfig6 ---
|
||||||
|
set -gx HOME (mktemp -d)
|
||||||
|
dot init --url $remote >/dev/null 2>&1
|
||||||
|
mkdir -p $HOME/.config/dot/commands/kde
|
||||||
|
cp $commands_dir/kde/kde.fish $HOME/.config/dot/commands/kde/kde.fish
|
||||||
|
cp $commands_dir/kde/kde.py $HOME/.config/dot/commands/kde/kde.py
|
||||||
|
|
||||||
|
set -l fake_bin_kde_diff (mktemp -d)
|
||||||
|
set -gx KREADCONFIG_LOG (mktemp)
|
||||||
|
echo '#!/bin/sh
|
||||||
|
echo "$@" >>"$KREADCONFIG_LOG"
|
||||||
|
exit 1' >$fake_bin_kde_diff/kreadconfig6
|
||||||
|
chmod +x $fake_bin_kde_diff/kreadconfig6
|
||||||
|
set -gx PATH $fake_bin_kde_diff $path_before_fake_kreadconfig
|
||||||
|
|
||||||
|
set -l kde_diff_help_output (dot kde diff help)
|
||||||
|
set -l kde_diff_help_status $status
|
||||||
|
set -l kreadconfig_called_for_diff_help (test -s $KREADCONFIG_LOG; and echo yes; or echo no)
|
||||||
|
set -l manifest_exists_after_diff_help (test -e $HOME/.config/dot/kde-manifest; and echo yes; or echo no)
|
||||||
|
|
||||||
|
@test "dot kde diff help succeeds" $kde_diff_help_status -eq 0
|
||||||
|
@test "dot kde diff help mentions undeclared" (string match -q '*undeclared*' -- $kde_diff_help_output; echo $status) -eq 0
|
||||||
|
@test "dot kde diff help never invokes kreadconfig6" $kreadconfig_called_for_diff_help = no
|
||||||
|
@test "dot kde diff help does not create a manifest" $manifest_exists_after_diff_help = no
|
||||||
|
|
||||||
|
set -gx PATH $path_before_fake_kreadconfig
|
||||||
|
|
||||||
|
# --- dot kde diff: broad read-only scan over every schema-backed identifier,
|
||||||
|
# tagging each mismatch declared/undeclared, and skipping settings that
|
||||||
|
# already match their schema default ---
|
||||||
|
set -gx HOME (mktemp -d)
|
||||||
|
dot init --url $remote >/dev/null 2>&1
|
||||||
|
mkdir -p $HOME/.config/dot/commands/kde
|
||||||
|
cp $commands_dir/kde/kde.fish $HOME/.config/dot/commands/kde/kde.fish
|
||||||
|
cp $commands_dir/kde/kde.py $HOME/.config/dot/commands/kde/kde.py
|
||||||
|
mkdir -p $HOME/.config/dot
|
||||||
|
|
||||||
|
# Greeting differs from its default and is already declared in the manifest;
|
||||||
|
# RealKey differs from its default but has never been declared; Some.Key With
|
||||||
|
# Spaces is left unset, so it falls back to (and matches) its schema default,
|
||||||
|
# and kwinrc.Windows.BorderSize likewise matches its default via the
|
||||||
|
# arg=true/exceptions-list mapping -- neither should be reported.
|
||||||
|
printf '[General]\nGreeting=Bonjour\nRealKey=ChangedAlias\n' >$HOME/.config/testrc
|
||||||
|
printf 'testrc.General.Greeting=Bonjour\n' >$HOME/.config/dot/kde-manifest
|
||||||
|
set -l manifest_before_diff (cat $HOME/.config/dot/kde-manifest | string collect)
|
||||||
|
|
||||||
|
set -l diff_output (dot kde diff)
|
||||||
|
set -l diff_status $status
|
||||||
|
set -l manifest_after_diff (cat $HOME/.config/dot/kde-manifest | string collect)
|
||||||
|
|
||||||
|
@test "dot kde diff succeeds" $diff_status -eq 0
|
||||||
|
@test "dot kde diff tags an already-declared mismatch as declared" (string match -q '*declared testrc.General.Greeting = Bonjour (default: Hello)*' -- $diff_output; echo $status) -eq 0
|
||||||
|
@test "dot kde diff tags a never-declared mismatch as undeclared" (string match -q '*undeclared testrc.General.RealKey = ChangedAlias (default: AliasDefault)*' -- $diff_output; echo $status) -eq 0
|
||||||
|
@test "dot kde diff does not report a setting matching its default (unset key)" (string match -q '*Some.Key With Spaces*' -- $diff_output; echo $status) -eq 1
|
||||||
|
@test "dot kde diff does not report a setting matching its default (arg=true mapping)" (string match -q '*BorderSize*' -- $diff_output; echo $status) -eq 1
|
||||||
|
@test "dot kde diff makes no writes to the manifest" "$manifest_after_diff" = "$manifest_before_diff"
|
||||||
|
|
||||||
|
dot kde diff extra-arg >/dev/null 2>&1
|
||||||
|
set -l diff_extra_arg_status $status
|
||||||
|
@test "dot kde diff rejects an unexpected argument" $diff_extra_arg_status -ne 0
|
||||||
|
|
||||||
# --- kde.py complete: tab-completion candidates, sourced from the live
|
# --- kde.py complete: tab-completion candidates, sourced from the live
|
||||||
# schema mapping table rather than a hardcoded list. This is the
|
# schema mapping table rather than a hardcoded list. This is the
|
||||||
# underlying data completions/dot.fish shells out to; the fish
|
# underlying data completions/dot.fish shells out to; the fish
|
||||||
|
|||||||
@@ -19,9 +19,11 @@ complete -c dot -n "__fish_seen_subcommand_from install; and not __fish_seen_arg
|
|||||||
complete -c dot -n "__fish_seen_subcommand_from install; and not __fish_seen_argument -l restore" -f -a "(__fish_print_pacman_packages)"
|
complete -c dot -n "__fish_seen_subcommand_from install; and not __fish_seen_argument -l restore" -f -a "(__fish_print_pacman_packages)"
|
||||||
|
|
||||||
# --- dot kde ---
|
# --- dot kde ---
|
||||||
complete -c dot -n "__fish_seen_subcommand_from kde; and not __fish_seen_subcommand_from save help" -f -a save -d "write live KDE settings into the manifest"
|
complete -c dot -n "__fish_seen_subcommand_from kde; and not __fish_seen_subcommand_from apply diff save help" -f -a apply -d "push manifest entries onto the live system"
|
||||||
complete -c dot -n "__fish_seen_subcommand_from kde; and not __fish_seen_subcommand_from save help" -f -a help -d "show usage"
|
complete -c dot -n "__fish_seen_subcommand_from kde; and not __fish_seen_subcommand_from apply diff save help" -f -a diff -d "scan for settings whose live value differs from its default"
|
||||||
complete -c dot -n "__fish_seen_subcommand_from kde; and __fish_seen_subcommand_from save" -f -a help -d "show usage"
|
complete -c dot -n "__fish_seen_subcommand_from kde; and not __fish_seen_subcommand_from apply diff save help" -f -a save -d "write live KDE settings into the manifest"
|
||||||
|
complete -c dot -n "__fish_seen_subcommand_from kde; and not __fish_seen_subcommand_from apply diff save help" -f -a help -d "show usage"
|
||||||
|
complete -c dot -n "__fish_seen_subcommand_from kde; and __fish_seen_subcommand_from apply diff save" -f -a help -d "show usage"
|
||||||
# Sourced live from the schema mapping table (real .kcfg files), not a
|
# Sourced live from the schema mapping table (real .kcfg files), not a
|
||||||
# hardcoded list -- same helper kde.py's own save/refresh logic builds from.
|
# hardcoded list -- same helper kde.py's own save/refresh logic builds from.
|
||||||
complete -c dot -n "__fish_seen_subcommand_from kde; and __fish_seen_subcommand_from save" -f -a "(python3 $HOME/.config/dot/commands/kde/kde.py complete 2>/dev/null)"
|
complete -c dot -n "__fish_seen_subcommand_from kde; and __fish_seen_subcommand_from save" -f -a "(python3 $HOME/.config/dot/commands/kde/kde.py complete 2>/dev/null)"
|
||||||
|
|||||||
1
.github/README.md
vendored
1
.github/README.md
vendored
@@ -21,6 +21,7 @@ fish -c 'dot init'
|
|||||||
| `dot install <pkgs>` | Installs the given pacman packages and appends them to the tracked list (`~/.config/dot/packages/pacman`). |
|
| `dot install <pkgs>` | Installs the given pacman packages and appends them to the tracked list (`~/.config/dot/packages/pacman`). |
|
||||||
| `dot install --restore` | Reinstalls every package from the tracked list. |
|
| `dot install --restore` | Reinstalls every package from the tracked list. |
|
||||||
| `dot kde apply` | Pushes every manifest entry's declared value onto the live system. |
|
| `dot kde apply` | Pushes every manifest entry's declared value onto the live system. |
|
||||||
|
| `dot kde diff` | Reports every schema-backed setting whose live value differs from its default, tagged declared or undeclared. |
|
||||||
| `dot kde help` | Lists `dot kde`'s subcommands. |
|
| `dot kde help` | Lists `dot kde`'s subcommands. |
|
||||||
| `dot kde save <identifier>` | Reads a KDE setting's current live value and declares it in the manifest (`~/.config/dot/kde-manifest`). |
|
| `dot kde save <identifier>` | Reads a KDE setting's current live value and declares it in the manifest (`~/.config/dot/kde-manifest`). |
|
||||||
| `dot kde save` | Refreshes every already-declared manifest entry's value from the live system. |
|
| `dot kde save` | Refreshes every already-declared manifest entry's value from the live system. |
|
||||||
|
|||||||
Reference in New Issue
Block a user