diff --git a/.config/dot/.claude/tasks/0000-nested-subcommand-discovery.md b/.config/dot/.claude/tasks/0000-nested-subcommand-discovery.md index da48ee4..cc823b9 100644 --- a/.config/dot/.claude/tasks/0000-nested-subcommand-discovery.md +++ b/.config/dot/.claude/tasks/0000-nested-subcommand-discovery.md @@ -17,8 +17,15 @@ This is pure prefactoring: no KDE-specific behavior is introduced here. ## Acceptance criteria -- [ ] `dot help` lists a subcommand that lives at `commands//.fish` -- [ ] `dot ` sources and dispatches to `commands//.fish`'s `_dot_` function -- [ ] Tab-completion (`__dot_custom_subcommands`) lists a nested-directory subcommand -- [ ] Existing flat-file subcommands (`dot install`) are still discovered and dispatched correctly -- [ ] `tests/dot.fish` covers a nested-directory dummy command dispatching correctly, alongside the existing flat-file dispatch case +- [x] `dot help` lists a subcommand that lives at `commands//.fish` +- [x] `dot ` sources and dispatches to `commands//.fish`'s `_dot_` function +- [x] Tab-completion (`__dot_custom_subcommands`) lists a nested-directory subcommand +- [x] Existing flat-file subcommands (`dot install`) are still discovered and dispatched correctly +- [x] `tests/dot.fish` covers a nested-directory dummy command dispatching correctly, alongside the existing flat-file dispatch case + +## Implementation Notes + +- The dispatch check in `dot.fish` tries the flat file first, then falls back to `commands//.fish` — a flat file always wins if both somehow exist for the same name. +- The nested-directory scan requires the file basename to match its containing directory's name (`commands/foo/foo.fish`), not just any `.fish` file one level deep — this matches the acceptance criteria's exact convention and avoids misclassifying a stray companion file (e.g. a `.py` helper) as its own subcommand. +- Tab-completion's nested-directory listing was verified manually (sourcing `completions/dot.fish` and calling `__dot_custom_subcommands` directly) rather than via an automated test — `tests/dot.fish` has no existing infrastructure for testing completions at all, even for pre-existing flat commands, so adding one here would be out of scope for this prefactoring task. +- Updated `CLAUDE.md`'s "Architecture" and "Adding a subcommand" sections to document the new nested-directory convention, since it previously only described the flat-file dispatch contract. diff --git a/.config/dot/CLAUDE.md b/.config/dot/CLAUDE.md index 960fc72..c611d77 100644 --- a/.config/dot/CLAUDE.md +++ b/.config/dot/CLAUDE.md @@ -62,6 +62,7 @@ shared: fish only autoloads a function from a file named after that function, so a helper defined inside `dot.fish` would be undefined if tab-completion ran before `dot` had ever been sourced in the session. Keep both copies in sync when the listing logic changes. +Both copies also glob one directory level deeper, matching `~/.config/dot/commands//.fish`, so a subcommand's companion file (e.g. a Python helper) can live alongside it in its own directory. `dot init`: @@ -76,7 +77,9 @@ sync when the listing logic changes. ### Adding a subcommand Beyond `init`, `dot` looks for `~/.config/dot/commands/.fish`, sources -it, and calls `_dot_`. These files are deliberately kept out of +it, and calls `_dot_`. +A subcommand needing a companion file can instead live nested one level deeper, as `~/.config/dot/commands//.fish` — both layouts dispatch identically. +These files are deliberately kept out of `~/.config/fish/functions/` (fish's autoload path) so they never become independently invokable top-level commands or clutter tab-completion outside of `dot` itself. diff --git a/.config/dot/tests/dot.fish b/.config/dot/tests/dot.fish index e4c0fb3..4be9e92 100644 --- a/.config/dot/tests/dot.fish +++ b/.config/dot/tests/dot.fish @@ -73,6 +73,19 @@ end" >$HOME/.config/dot/commands/mark.fish dot mark >/dev/null 2>&1 @test "dispatches to a command file under ~/.config/dot/commands/" (cat $marker) = marked +# --- dispatches to a nested commands//.fish, same as a flat file +set -gx HOME (mktemp -d) +dot init --url $remote >/dev/null 2>&1 + +mkdir -p $HOME/.config/dot/commands/nested +set -l nested_marker (mktemp) +echo "function _dot_nested + echo nested-marked >$nested_marker +end" >$HOME/.config/dot/commands/nested/nested.fish + +dot nested >/dev/null 2>&1 +@test "dispatches to a nested commands//.fish" (cat $nested_marker) = nested-marked + # --- dot help --- set -gx HOME (mktemp -d) dot init --url $remote >/dev/null 2>&1 @@ -93,6 +106,14 @@ end" >$HOME/.config/dot/commands/mark.fish set -l help_with_custom (dot help) @test "dot help lists custom commands found under ~/.config/dot/commands/" (string match -q '*mark*' -- $help_with_custom; echo $status) -eq 0 +mkdir -p $HOME/.config/dot/commands/nested +echo "function _dot_nested + echo nested +end" >$HOME/.config/dot/commands/nested/nested.fish + +set -l help_with_nested (dot help) +@test "dot help lists a nested-directory subcommand" (string match -q '*nested*' -- $help_with_nested; echo $status) -eq 0 + # --- dot install --- # pacman and sudo are faked out via a bin dir prepended to PATH: sudo just # execs its arguments, and pacman logs each invocation to $PACMAN_LOG (one diff --git a/.config/fish/completions/dot.fish b/.config/fish/completions/dot.fish index 0233670..c50f9ea 100644 --- a/.config/fish/completions/dot.fish +++ b/.config/fish/completions/dot.fish @@ -2,6 +2,13 @@ function __dot_custom_subcommands echo init echo help path basename $HOME/.config/dot/commands/*.fish 2>/dev/null | path change-extension '' + + for d in $HOME/.config/dot/commands/*/ + test -d $d; or continue + set -l name (path basename $d) + test -f $d$name.fish; or continue + echo $name + end end complete -c dot -n __fish_use_subcommand -a "(__dot_custom_subcommands)" diff --git a/.config/fish/functions/dot.fish b/.config/fish/functions/dot.fish index ee5f620..aea4c0d 100644 --- a/.config/fish/functions/dot.fish +++ b/.config/fish/functions/dot.fish @@ -14,11 +14,18 @@ function dot --wraps=git --description 'Manage dotfiles via a bare repo checked set -l commands_dir $HOME/.config/dot/commands set -l command_file $commands_dir/$argv[1].fish + set -l nested_command_file $commands_dir/$argv[1]/$argv[1].fish - if test -n "$argv[1]" -a -f "$command_file" - source $command_file - _dot_$argv[1] $argv[2..-1] - return $status + if test -n "$argv[1]" + if test -f "$command_file" + source $command_file + _dot_$argv[1] $argv[2..-1] + return $status + else if test -f "$nested_command_file" + source $nested_command_file + _dot_$argv[1] $argv[2..-1] + return $status + end end git --git-dir=$dotfiles_dir --work-tree=$HOME $argv @@ -109,6 +116,13 @@ Commands: echo " "(path basename $f | path change-extension '') end + for d in $HOME/.config/dot/commands/*/ + test -d $d; or continue + set -l name (path basename $d) + test -f $d$name.fish; or continue + echo " $name" + end + echo " Run 'dot help' for flags on a specific command.