Skip to content

Commit

Permalink
feat: shell completion improvements (#577)
Browse files Browse the repository at this point in the history
  • Loading branch information
scop committed Jan 12, 2024
1 parent 02e6f77 commit eead08c
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 28 deletions.
21 changes: 16 additions & 5 deletions cmd/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/spf13/cobra"

"github.com/evilmartians/lefthook/internal/config"
"github.com/evilmartians/lefthook/internal/lefthook"
)

Expand All @@ -14,12 +15,22 @@ var addDoc string
func newAddCmd(opts *lefthook.Options) *cobra.Command {
args := lefthook.AddArgs{}

addHookCompletions := func(cmd *cobra.Command, args []string, toComplete string) (ret []string, compDir cobra.ShellCompDirective) {
compDir = cobra.ShellCompDirectiveNoFileComp
if len(args) != 0 {
return
}
ret = config.AvailableHooks[:]
return
}

addCmd := cobra.Command{
Use: "add hook-name",
Short: "This command adds a hook directory to a repository",
Long: addDoc,
Example: "lefthook add pre-commit",
Args: cobra.MinimumNArgs(1),
Use: "add hook-name",
Short: "This command adds a hook directory to a repository",
Long: addDoc,
Example: "lefthook add pre-commit",
ValidArgsFunction: addHookCompletions,
Args: cobra.MinimumNArgs(1),
RunE: func(_cmd *cobra.Command, hooks []string) error {
args.Hook = hooks[0]
return lefthook.Add(opts, &args)
Expand Down
7 changes: 4 additions & 3 deletions cmd/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import (
func newDumpCmd(opts *lefthook.Options) *cobra.Command {
dumpArgs := lefthook.DumpArgs{}
dumpCmd := cobra.Command{
Use: "dump",
Short: "Prints config merged from all extensions (in YAML format by default)",
Example: "lefthook dump",
Use: "dump",
Short: "Prints config merged from all extensions (in YAML format by default)",
Example: "lefthook dump",
ValidArgsFunction: cobra.NoFileCompletions,
Run: func(cmd *cobra.Command, args []string) {
lefthook.Dump(opts, dumpArgs)
},
Expand Down
5 changes: 3 additions & 2 deletions cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ func newInstallCmd(opts *lefthook.Options) *cobra.Command {
var a, force bool

installCmd := cobra.Command{
Use: "install",
Short: "Write basic configuration file in your project repository. Or initialize existed config",
Use: "install",
Short: "Write basic configuration file in your project repository. Or initialize existed config",
ValidArgsFunction: cobra.NoFileCompletions,
RunE: func(cmd *cobra.Command, _args []string) error {
return lefthook.Install(opts, force)
},
Expand Down
30 changes: 26 additions & 4 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,38 @@ package cmd
import (
"github.com/spf13/cobra"

"github.com/evilmartians/lefthook/internal/config"
"github.com/evilmartians/lefthook/internal/lefthook"
)

func newRunCmd(opts *lefthook.Options) *cobra.Command {
runArgs := lefthook.RunArgs{}

runHookCompletions := func(cmd *cobra.Command, args []string, toComplete string) (ret []string, compDir cobra.ShellCompDirective) {
compDir = cobra.ShellCompDirectiveNoFileComp
if len(args) != 0 {
return
}
ret = lefthook.ConfigHookCompletions(opts)
ret = append(ret, config.AvailableHooks[:]...)
return
}

runHookCommandCompletions := func(cmd *cobra.Command, args []string, toComplete string) (ret []string, compDir cobra.ShellCompDirective) {
compDir = cobra.ShellCompDirectiveNoFileComp
if len(args) == 0 {
return
}
ret = lefthook.ConfigHookCommandCompletions(opts, args[0])
return
}

runCmd := cobra.Command{
Use: "run hook-name [git args...]",
Short: "Execute group of hooks",
Example: "lefthook run pre-commit",
Args: cobra.MinimumNArgs(1),
Use: "run hook-name [git args...]",
Short: "Execute group of hooks",
Example: "lefthook run pre-commit",
ValidArgsFunction: runHookCompletions,
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
// args[0] - hook name
// args[1:] - git hook arguments, number and value depends on the hook
Expand All @@ -39,6 +60,7 @@ func newRunCmd(opts *lefthook.Options) *cobra.Command {
runCmd.Flags().StringSliceVar(&runArgs.Files, "files", nil, "run on specified files. takes precedence over --all-files")

runCmd.Flags().StringSliceVar(&runArgs.RunOnlyCommands, "commands", nil, "run only specified commands")
_ = runCmd.RegisterFlagCompletionFunc("commands", runHookCommandCompletions)

return &runCmd
}
5 changes: 3 additions & 2 deletions cmd/uninstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ func newUninstallCmd(opts *lefthook.Options) *cobra.Command {
args := lefthook.UninstallArgs{}

uninstallCmd := cobra.Command{
Use: "uninstall",
Short: "Revert install command",
Use: "uninstall",
Short: "Revert install command",
ValidArgsFunction: cobra.NoFileCompletions,
RunE: func(cmd *cobra.Command, _args []string) error {
return lefthook.Uninstall(opts, &args)
},
Expand Down
5 changes: 3 additions & 2 deletions cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ func newVersionCmd(_opts *lefthook.Options) *cobra.Command {
var verbose bool

versionCmd := cobra.Command{
Use: "version",
Short: "Show lefthook version",
Use: "version",
Short: "Show lefthook version",
ValidArgsFunction: cobra.NoFileCompletions,
Run: func(cmd *cobra.Command, args []string) {
log.Println(version.Version(verbose))
},
Expand Down
50 changes: 50 additions & 0 deletions internal/lefthook/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,53 @@ func printSummary(
}
}
}

func ConfigHookCompletions(opts *Options) []string {
lefthook, err := initialize(opts)
if err != nil {
return nil
}
return lefthook.configHookCompletions()
}

func (l *Lefthook) configHookCompletions() []string {
cfg, err := config.Load(l.Fs, l.repo)
if err != nil {
return nil
}
if err = cfg.Validate(); err != nil {
return nil
}
hooks := make([]string, 0, len(cfg.Hooks))
for hook := range cfg.Hooks {
hooks = append(hooks, hook)
}
return hooks
}

func ConfigHookCommandCompletions(opts *Options, hookName string) []string {
lefthook, err := initialize(opts)
if err != nil {
return nil
}
return lefthook.configHookCommandCompletions(hookName)
}

func (l *Lefthook) configHookCommandCompletions(hookName string) []string {
cfg, err := config.Load(l.Fs, l.repo)
if err != nil {
return nil
}
if err = cfg.Validate(); err != nil {
return nil
}
if hook, found := cfg.Hooks[hookName]; !found {
return nil
} else {
commands := make([]string, 0, len(hook.Commands))
for command := range hook.Commands {
commands = append(commands, command)
}
return commands
}
}
49 changes: 39 additions & 10 deletions internal/lefthook/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package lefthook
import (
"fmt"
"path/filepath"
"slices"
"testing"

"github.com/spf13/afero"
Expand Down Expand Up @@ -33,11 +34,13 @@ func TestRun(t *testing.T) {
gitPath := filepath.Join(root, ".git")

for i, tt := range [...]struct {
name, hook, config string
gitArgs []string
envs map[string]string
existingDirs []string
error bool
name, hook, config string
gitArgs []string
envs map[string]string
existingDirs []string
hookNameCompletions []string
hookCommandCompletions []string
error bool
}{
{
name: "Skip case",
Expand Down Expand Up @@ -79,7 +82,8 @@ pre-commit:
parallel: true
piped: true
`,
error: true,
hookNameCompletions: []string{"pre-commit"},
error: true,
},
{
name: "Valid hook",
Expand All @@ -89,7 +93,8 @@ pre-commit:
parallel: false
piped: true
`,
error: false,
hookNameCompletions: []string{"pre-commit"},
error: false,
},
{
name: "When in git rebase-merge flow",
Expand All @@ -108,7 +113,9 @@ pre-commit:
existingDirs: []string{
filepath.Join(gitPath, "rebase-merge"),
},
error: false,
hookNameCompletions: []string{"pre-commit"},
hookCommandCompletions: []string{"echo"},
error: false,
},
{
name: "When in git rebase-apply flow",
Expand All @@ -127,7 +134,9 @@ pre-commit:
existingDirs: []string{
filepath.Join(gitPath, "rebase-apply"),
},
error: false,
hookNameCompletions: []string{"pre-commit"},
hookCommandCompletions: []string{"echo"},
error: false,
},
{
name: "When not in rebase flow",
Expand All @@ -143,7 +152,9 @@ post-commit:
- merge
run: echo 'SHOULD RUN'
`,
error: true,
hookNameCompletions: []string{"post-commit"},
hookCommandCompletions: []string{"echo"},
error: true,
},
} {
t.Run(fmt.Sprintf("%d: %s", i, tt.name), func(t *testing.T) {
Expand Down Expand Up @@ -185,6 +196,24 @@ post-commit:
t.Errorf("expected an error")
}
}

hookNameCompletions := lefthook.configHookCompletions()
if tt.hookNameCompletions != nil {
if !slices.Equal(tt.hookNameCompletions, hookNameCompletions) {
t.Errorf("expected hook name completions %v, got %v", tt.hookNameCompletions, hookNameCompletions)
}
} else if len(hookNameCompletions) != 0 {
t.Errorf("expected no hook name completions, got %v", lefthook.configHookCompletions())
}

hookCommandCompletions := lefthook.configHookCommandCompletions(tt.hook)
if tt.hookCommandCompletions != nil {
if !slices.Equal(tt.hookCommandCompletions, hookCommandCompletions) {
t.Errorf("expected hook command completions %v, got %v", tt.hookCommandCompletions, hookCommandCompletions)
}
} else if len(hookCommandCompletions) != 0 {
t.Errorf("expected no hook command completions, got %v", hookCommandCompletions)
}
})
}
}

0 comments on commit eead08c

Please sign in to comment.