diff --git a/.gitignore b/.gitignore index 47b0dba7d4..064fd52a16 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,3 @@ releases/ dist/ *.completion -!fish.completion diff --git a/Makefile b/Makefile index 9b591227c3..69ac1e8b3b 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ GOPASS_VERSION ?= $(shell cat VERSION) GOPASS_OUTPUT ?= gopass GOPASS_REVISION := $(shell cat COMMIT 2>/dev/null || git rev-parse --short=8 HEAD) BASH_COMPLETION_OUTPUT := bash.completion +FISH_COMPLETION_OUTPUT := fish.completion ZSH_COMPLETION_OUTPUT := zsh.completion # Support reproducible builds by embedding date according to SOURCE_DATE_EPOCH if present DATE := $(shell date -u -d "@$(SOURCE_DATE_EPOCH)" '+%FT%T%z' 2>/dev/null || date -u '+%FT%T%z') @@ -24,7 +25,7 @@ all: sysinfo crosscompile build install test codequality completion define ok @tput setaf 6 2>/dev/null || echo -n "" - @echo "[OK]" + @echo " [OK]" @tput sgr0 2>/dev/null || echo -n "" endef @@ -98,16 +99,21 @@ crosscompile: @GOOS=windows GOARCH=amd64 $(GO) build -o $(GOPASS_OUTPUT)-windows-amd64 @$(call ok) -completion: $(BASH_COMPLETION_OUTPUT) $(ZSH_COMPLETION_OUTPUT) +completion: $(BASH_COMPLETION_OUTPUT) $(FISH_COMPLETION_OUTPUT) $(ZSH_COMPLETION_OUTPUT) $(BASH_COMPLETION_OUTPUT): build @echo -n ">> BASH COMPLETION, output = $(BASH_COMPLETION_OUTPUT)" - @gopass completion bash > $(BASH_COMPLETION_OUTPUT) + @./gopass completion bash > $(BASH_COMPLETION_OUTPUT) @$(call ok) +$(FISH_COMPLETION_OUTPUT): build + @echo -n ">> FISH COMPLETION, output = $(FISH_COMPLETION_OUTPUT)" + @./gopass completion fish > $(FISH_COMPLETION_OUTPUT) + @$(call ok) + $(ZSH_COMPLETION_OUTPUT): build @echo -n ">> ZSH COMPLETION, output = $(ZSH_COMPLETION_OUTPUT)" - @gopass completion bash > $(ZSH_COMPLETION_OUTPUT) + @./gopass completion zsh > $(ZSH_COMPLETION_OUTPUT) @$(call ok) codequality: @@ -134,7 +140,7 @@ codequality: $(GO) get -u github.com/golang/lint/golint; \ fi @$(foreach pkg, $(PKGS),\ - golint -set_exit_status $(pkg);) + golint -set_exit_status $(pkg) || exit 1;) @$(call ok) @echo -n " INEFF " @@ -149,7 +155,7 @@ codequality: $(GO) get -u github.com/client9/misspell/cmd/misspell; \ fi @$(foreach gofile, $(GOFILES_NOVENDOR),\ - misspell --error $(gofile);) + misspell --error $(gofile) || exit 1;) @$(call ok) @echo -n " MEGACHECK " diff --git a/action/completion.go b/action/completion.go index 2e0263496a..aea5bd5845 100644 --- a/action/completion.go +++ b/action/completion.go @@ -4,6 +4,8 @@ import ( "fmt" "regexp" + fishcomp "github.com/justwatchcom/gopass/utils/completion/fish" + zshcomp "github.com/justwatchcom/gopass/utils/completion/zsh" "github.com/urfave/cli" ) @@ -50,14 +52,24 @@ func (s *Action) CompletionBash(c *cli.Context) error { return nil } -// CompletionZSH returns a script that uses bash's auto completion -func (s *Action) CompletionZSH(c *cli.Context) error { - out := `autoload -U compinit && compinit -autoload -U bashcompinit && bashcompinit +// CompletionFish returns an autocompletion script for fish +func (s *Action) CompletionFish(c *cli.Context, a *cli.App) error { + comp, err := fishcomp.GetCompletion(a) + if err != nil { + return err + } -` - out += "source <(" + s.Name + " completion bash)" - fmt.Println(out) + fmt.Println(comp) + return nil +} + +// CompletionZSH returns a zsh completion script +func (s *Action) CompletionZSH(c *cli.Context, a *cli.App) error { + comp, err := zshcomp.GetCompletion(a) + if err != nil { + return err + } + fmt.Println(comp) return nil } diff --git a/action/completion_test.go b/action/completion_test.go index c371717f72..4e60afd768 100644 --- a/action/completion_test.go +++ b/action/completion_test.go @@ -6,6 +6,8 @@ import ( "os" "strings" "testing" + + "github.com/urfave/cli" ) func TestBashEscape(t *testing.T) { @@ -30,6 +32,8 @@ func TestComplete(t *testing.T) { t.Fatalf("Error: %s", err) } + app := cli.NewApp() + out := capture(t, func() error { act.Complete(nil) return nil @@ -38,6 +42,7 @@ func TestComplete(t *testing.T) { t.Errorf("should return 'foo' not '%s'", out) } + // bash out = capture(t, func() error { return act.CompletionBash(nil) }) @@ -45,8 +50,17 @@ func TestComplete(t *testing.T) { t.Errorf("should contain name of test") } + // fish + out = capture(t, func() error { + return act.CompletionFish(nil, app) + }) + if !strings.Contains(out, "action.test") { + t.Errorf("should contain name of test") + } + + // zsh out = capture(t, func() error { - return act.CompletionZSH(nil) + return act.CompletionZSH(nil, app) }) if !strings.Contains(out, "action.test") { t.Errorf("should contain name of test") diff --git a/fish.completion b/fish.completion deleted file mode 100644 index 864a76bb7a..0000000000 --- a/fish.completion +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env fish -set PROG 'gopass' - -function __fish_gopass_needs_command - set -l cmd (commandline -opc) - if [ (count $cmd) -eq 1 -a $cmd[1] = $PROG ] - return 0 - end - return 1 -end - -function __fish_gopass_uses_command - set cmd (commandline -opc) - if [ (count $cmd) -gt 1 ] - if [ $argv[1] = $cmd[2] ] - return 0 - end - end - return 1 -end - -function __fish_gopass_print_gpg_keys - gpg2 --list-keys | grep uid | sed 's/.*<\(.*\)>/\1/' -end - -function __fish_gopass_print_entries - eval "gopass ls --flat" - for file in $files - echo "$file" - end -end - -complete -c $PROG -e -complete -c $PROG -f -A -n '__fish_gopass_needs_command' -a help -d 'Command: show usage help' -complete -c $PROG -f -A -n '__fish_gopass_needs_command' -a version -d 'Command: show program version' - -complete -c $PROG -f -A -n '__fish_gopass_needs_command' -a init -d 'Command: initialize new password storage' -complete -c $PROG -f -A -n '__fish_gopass_uses_command init' -s p -l path -d 'Assign gpg-id for specified sub folder of password store' - -complete -c $PROG -f -A -n '__fish_gopass_needs_command' -a ls -d 'Command: list passwords' -complete -c $PROG -f -A -n '__fish_gopass_uses_command ls' -a "(__fish_gopass_print_entry_dirs)" - -complete -c $PROG -f -A -n '__fish_gopass_needs_command' -a insert -d 'Command: insert new password' -complete -c $PROG -f -A -n '__fish_gopass_uses_command insert' -s e -l echo -d 'Echo the password on console' -complete -c $PROG -f -A -n '__fish_gopass_uses_command insert' -s m -l multiline -d 'Provide multiline password entry' -complete -c $PROG -f -A -n '__fish_gopass_uses_command insert' -s f -l force -d 'Do not prompt before overwritting' -complete -c $PROG -f -A -n '__fish_gopass_uses_command insert' -a "(__fish_gopass_print_entry_dirs)" - -complete -c $PROG -f -A -n '__fish_gopass_needs_command' -a generate -d 'Command: generate new password' -complete -c $PROG -f -A -n '__fish_gopass_uses_command generate' -s n -l no-symbols -d 'Do not use special symbols' -complete -c $PROG -f -A -n '__fish_gopass_uses_command generate' -s c -l clip -d 'Put the password in clipboard' -complete -c $PROG -f -A -n '__fish_gopass_uses_command generate' -s f -l force -d 'Do not prompt before overwritting' -complete -c $PROG -f -A -n '__fish_gopass_uses_command generate' -s i -l in-place -d 'Replace only the first line with the generated password' -complete -c $PROG -f -A -n '__fish_gopass_uses_command generate' -a "(__fish_gopass_print_entry_dirs)" - -complete -c $PROG -f -A -n '__fish_gopass_needs_command' -a mv -d 'Command: rename existing password' -complete -c $PROG -f -A -n '__fish_gopass_uses_command mv' -s f -l force -d 'Force rename' -complete -c $PROG -f -A -n '__fish_gopass_uses_command mv' -a "(__fish_gopass_print_entries)" - -complete -c $PROG -f -A -n '__fish_gopass_needs_command' -a cp -d 'Command: copy existing password' -complete -c $PROG -f -A -n '__fish_gopass_uses_command cp' -s f -l force -d 'Force copy' -complete -c $PROG -f -A -n '__fish_gopass_uses_command cp' -a "(__fish_gopass_print_entries)" - -complete -c $PROG -f -A -n '__fish_gopass_needs_command' -a rm -d 'Command: remove existing password' -complete -c $PROG -f -A -n '__fish_gopass_uses_command rm' -s r -l recursive -d 'Remove password groups recursively' -complete -c $PROG -f -A -n '__fish_gopass_uses_command rm' -s f -l force -d 'Force removal' -complete -c $PROG -f -A -n '__fish_gopass_uses_command rm' -a "(__fish_gopass_print_entries)" - -complete -c $PROG -f -A -n '__fish_gopass_needs_command' -a edit -d 'Command: edit password using text editor' -complete -c $PROG -f -A -n '__fish_gopass_uses_command edit' -a "(__fish_gopass_print_entries)" - -complete -c $PROG -f -A -n '__fish_gopass_needs_command' -a show -d 'Command: show existing password' -complete -c $PROG -f -A -n '__fish_gopass_uses_command show' -s c -l clip -d 'Put password in clipboard' -complete -c $PROG -f -A -n '__fish_gopass_uses_command show' -a "(__fish_gopass_print_entries)" - -# Default to show -complete -c $PROG -f -A -n '__fish_gopass_needs_command' -s c -l clip -d 'Put password in clipboard' -complete -c $PROG -f -A -n '__fish_gopass_needs_command' -a "(__fish_gopass_print_entries)" -complete -c $PROG -f -A -n '__fish_gopass_uses_command -c' -a "(__fish_gopass_print_entries)" -complete -c $PROG -f -A -n '__fish_gopass_uses_command --clip' -a "(__fish_gopass_print_entries)" - -complete -c $PROG -f -A -n '__fish_gopass_needs_command' -a git -d 'Command: execute a git command' -complete -c $PROG -f -A -n '__fish_gopass_uses_command git' -a 'init' -d 'Initialize git repository' -complete -c $PROG -f -A -n '__fish_gopass_uses_command git' -a 'status' -d 'Show status of the repo' -complete -c $PROG -f -A -n '__fish_gopass_uses_command git' -a 'add' -d 'Add changes to the index' -complete -c $PROG -f -A -n '__fish_gopass_uses_command git' -a 'commit' -d 'Commit changes to the repo' -complete -c $PROG -f -A -n '__fish_gopass_uses_command git' -a 'push' -d 'Push changes to remote repo' -complete -c $PROG -f -A -n '__fish_gopass_uses_command git' -a 'pull' -d 'Pull changes from remote repo' -complete -c $PROG -f -A -n '__fish_gopass_uses_command git' -a 'log' -d 'View changelog' - -complete -c $PROG -f -A -n '__fish_gopass_needs_command' -a find -d 'Command: find a password file or directory matching pattern' -complete -c $PROG -f -A -n '__fish_gopass_needs_command' -a grep -d 'Command: search inside of decrypted password files for matching pattern' diff --git a/main.go b/main.go index 005d439ea1..64f5379909 100644 --- a/main.go +++ b/main.go @@ -295,9 +295,17 @@ func main() { Usage: "Source for auto completion in bash", Action: action.CompletionBash, }, { - Name: "zsh", - Usage: "Source for auto completion in zsh", - Action: action.CompletionZSH, + Name: "zsh", + Usage: "Source for auto completion in zsh", + Action: func(c *cli.Context) error { + return action.CompletionZSH(c, app) + }, + }, { + Name: "fish", + Usage: "Source for auto completion in fish", + Action: func(c *cli.Context) error { + return action.CompletionFish(c, app) + }, }}, }, { diff --git a/tests/completion_test.go b/tests/completion_test.go index 76e491d182..f11cc34f2a 100644 --- a/tests/completion_test.go +++ b/tests/completion_test.go @@ -31,12 +31,11 @@ complete -F _gopass_bash_autocomplete gopass` assert.NoError(t, err) assert.Equal(t, bash, out) - zsh := `autoload -U compinit && compinit -autoload -U bashcompinit && bashcompinit - -source <(gopass completion bash)` - out, err = ts.run("completion zsh") assert.NoError(t, err) - assert.Equal(t, zsh, out) + assert.Contains(t, out, "compdef gopass") + + out, err = ts.run("completion fish") + assert.NoError(t, err) + assert.Contains(t, out, "complete") } diff --git a/utils/completion/fish/completion.go b/utils/completion/fish/completion.go new file mode 100644 index 0000000000..ada26d35e6 --- /dev/null +++ b/utils/completion/fish/completion.go @@ -0,0 +1,88 @@ +package fish + +import ( + "bytes" + "fmt" + "html/template" + "strings" + + "github.com/urfave/cli" +) + +func longName(name string) string { + parts := strings.Split(name, ",") + if len(parts) < 1 { + return "" + } + return strings.TrimSpace(parts[0]) +} + +func shortName(name string) string { + parts := strings.Split(name, ",") + if len(parts) < 2 { + return "" + } + return strings.TrimSpace(parts[1]) +} + +func formatFlag(name, usage, typ string) string { + switch typ { + case "short": + return shortName(name) + case "long": + return longName(name) + case "usage": + return usage + default: + return "" + } +} + +func formatFlagFunc(typ string) func(cli.Flag) (string, error) { + return func(f cli.Flag) (string, error) { + switch ft := f.(type) { + case cli.BoolFlag: + return formatFlag(ft.Name, ft.Usage, typ), nil + case cli.Float64Flag: + return formatFlag(ft.Name, ft.Usage, typ), nil + case cli.GenericFlag: + return formatFlag(ft.Name, ft.Usage, typ), nil + case cli.Int64Flag: + return formatFlag(ft.Name, ft.Usage, typ), nil + case cli.Int64SliceFlag: + return formatFlag(ft.Name, ft.Usage, typ), nil + case cli.IntFlag: + return formatFlag(ft.Name, ft.Usage, typ), nil + case cli.IntSliceFlag: + return formatFlag(ft.Name, ft.Usage, typ), nil + case cli.StringFlag: + return formatFlag(ft.Name, ft.Usage, typ), nil + case cli.StringSliceFlag: + return formatFlag(ft.Name, ft.Usage, typ), nil + case cli.Uint64Flag: + return formatFlag(ft.Name, ft.Usage, typ), nil + case cli.UintFlag: + return formatFlag(ft.Name, ft.Usage, typ), nil + default: + return "", fmt.Errorf("unknown type: '%T'", f) + } + } +} + +// GetCompletion returns a fish completion script +func GetCompletion(a *cli.App) (string, error) { + tplFuncs := template.FuncMap{ + "formatShortFlag": formatFlagFunc("short"), + "formatLongFlag": formatFlagFunc("long"), + "formatFlagUsage": formatFlagFunc("usage"), + } + tpl, err := template.New("fish").Funcs(tplFuncs).Parse(fishTemplate) + if err != nil { + return "", err + } + buf := &bytes.Buffer{} + if err := tpl.Execute(buf, a); err != nil { + return "", err + } + return buf.String(), nil +} diff --git a/utils/completion/fish/completion_test.go b/utils/completion/fish/completion_test.go new file mode 100644 index 0000000000..c08a38d1ed --- /dev/null +++ b/utils/completion/fish/completion_test.go @@ -0,0 +1,35 @@ +package fish + +import ( + "testing" + + "github.com/urfave/cli" +) + +func TestFormatFlag(t *testing.T) { + for _, tc := range []struct { + Name string + Usage string + Typ string + Out string + }{ + {"print, p", "Print", "short", "p"}, + {"print, p", "Print", "long", "print"}, + {"print, p", "Print", "usage", "Print"}, + {"print, p", "Print", "foo", ""}, + } { + out := formatFlag(tc.Name, tc.Usage, tc.Typ) + if out != tc.Out { + t.Errorf("'%s' != '%s'", out, tc.Out) + } + } +} + +func TestGetCompletion(t *testing.T) { + app := cli.NewApp() + sv, err := GetCompletion(app) + if err != nil { + t.Fatalf("Error: %s", err) + } + t.Logf("Output: %s", sv) +} diff --git a/utils/completion/fish/template.go b/utils/completion/fish/template.go new file mode 100644 index 0000000000..a24f67abe2 --- /dev/null +++ b/utils/completion/fish/template.go @@ -0,0 +1,55 @@ +package fish + +// see https://fishshell.com/docs/current/commands.html#complete +const fishTemplate = `#!/usr/bin/env fish +{{ $prog := .Name -}} +set PROG '{{ $prog }}' + +function __fish_{{ $prog }}_needs_command + set -l cmd (commandline -opc) + if [ (count $cmd) -eq 1 -a $cmd[1] = $PROG ] + return 0 + end + return 1 +end + +function __fish_{{ $prog }}_uses_command + set cmd (commandline -opc) + if [ (count $cmd) -gt 1 ] + if [ $argv[1] = $cmd[2] ] + return 0 + end + end + return 1 +end + +function __fish_{{ $prog }}_print_gpg_keys + gpg2 --list-keys | grep uid | sed 's/.*<\(.*\)>/\1/' +end + +function __fish_{{ $prog }}_print_entries + eval "{{ $prog }} ls --flat" + for file in $files + echo "$file" + end +end + +# erase any existing completions for {{ $prog }} +complete -c $PROG -e +complete -c $PROG -f -n '__fish_{{ $prog }}_needs_command' -a "(__fish_{{ $prog }}_print_entries)" +{{- $gflags := .Flags -}} +{{ range .Commands }} +complete -c $PROG -f -n '__fish_{{ $prog }}_needs_command' -a {{ .Name }} -d 'Command: {{ .Usage }}' +{{- $cmd := .Name -}} +{{- range .Subcommands }} +{{- $subcmd := .Name }} +complete -c $PROG -f -n '__fish_{{ $prog }}_uses_command {{ $cmd }}' -a {{ $subcmd }} -d 'Subcommand: {{ .Usage }}' +{{- if or (eq $cmd "copy") (eq $cmd "move") (eq $cmd "delete") (eq $cmd "show") }}complete -c $PROG -f -n '__fish_{{ $prog }}_uses_command {{ $cmd }}' -a "(__fish_{{ $prog }}_print_entries)"{{ end -}} +{{- range .Flags }} +complete -c $PROG -f -n '__fish_{{ $prog }}_uses_command {{ $cmd }} {{ $subcmd }} {{ if ne (. | formatShortFlag) "" }}-s {{ . | formatShortFlag }} {{ end }}-l {{ . | formatLongFlag }} -d "{{ . | formatFlagUsage }}"' +{{- end }} +{{- range $gflags }} +complete -c $PROG -f -n '__fish_{{ $prog }}_uses_command {{ $cmd }} {{ $subcmd }} {{ if ne (. | formatShortFlag) "" }}-s {{ . | formatShortFlag }} {{ end }}-l {{ . | formatLongFlag }} -d "{{ . | formatFlagUsage }}"' +{{- end }} +{{- end }} +{{- end }}` diff --git a/utils/completion/zsh/completion.go b/utils/completion/zsh/completion.go new file mode 100644 index 0000000000..bb0d35c6d2 --- /dev/null +++ b/utils/completion/zsh/completion.go @@ -0,0 +1,69 @@ +package zsh + +import ( + "bytes" + "fmt" + "html/template" + "strings" + + "github.com/urfave/cli" +) + +func longName(name string) string { + parts := strings.Split(name, ",") + if len(parts) < 1 { + return "" + } + return strings.TrimSpace(parts[0]) +} + +func formatFlag(name, usage string) string { + return fmt.Sprintf("--%s[%s]", longName(name), usage) +} + +func formatFlagFunc() func(cli.Flag) (string, error) { + return func(f cli.Flag) (string, error) { + switch ft := f.(type) { + case cli.BoolFlag: + return formatFlag(ft.Name, ft.Usage), nil + case cli.Float64Flag: + return formatFlag(ft.Name, ft.Usage), nil + case cli.GenericFlag: + return formatFlag(ft.Name, ft.Usage), nil + case cli.Int64Flag: + return formatFlag(ft.Name, ft.Usage), nil + case cli.Int64SliceFlag: + return formatFlag(ft.Name, ft.Usage), nil + case cli.IntFlag: + return formatFlag(ft.Name, ft.Usage), nil + case cli.IntSliceFlag: + return formatFlag(ft.Name, ft.Usage), nil + case cli.StringFlag: + return formatFlag(ft.Name, ft.Usage), nil + case cli.StringSliceFlag: + return formatFlag(ft.Name, ft.Usage), nil + case cli.Uint64Flag: + return formatFlag(ft.Name, ft.Usage), nil + case cli.UintFlag: + return formatFlag(ft.Name, ft.Usage), nil + default: + return "", fmt.Errorf("unknown type: '%T'", f) + } + } +} + +// GetCompletion returns a zsh completion script +func GetCompletion(a *cli.App) (string, error) { + tplFuncs := template.FuncMap{ + "formatFlag": formatFlagFunc(), + } + tpl, err := template.New("zsh").Funcs(tplFuncs).Parse(zshTemplate) + if err != nil { + return "", err + } + buf := &bytes.Buffer{} + if err := tpl.Execute(buf, a); err != nil { + return "", err + } + return buf.String(), nil +} diff --git a/utils/completion/zsh/completion_test.go b/utils/completion/zsh/completion_test.go new file mode 100644 index 0000000000..da6a542d13 --- /dev/null +++ b/utils/completion/zsh/completion_test.go @@ -0,0 +1,31 @@ +package zsh + +import ( + "testing" + + "github.com/urfave/cli" +) + +func TestFormatFlag(t *testing.T) { + for _, tc := range []struct { + Name string + Usage string + Out string + }{ + {"print, p", "Print", "--print[Print]"}, + } { + out := formatFlag(tc.Name, tc.Usage) + if out != tc.Out { + t.Errorf("'%s' != '%s'", out, tc.Out) + } + } +} + +func TestGetCompletion(t *testing.T) { + app := cli.NewApp() + sv, err := GetCompletion(app) + if err != nil { + t.Fatalf("Error: %s", err) + } + t.Logf("Output: %s", sv) +} diff --git a/utils/completion/zsh/template.go b/utils/completion/zsh/template.go new file mode 100644 index 0000000000..a8d3f27864 --- /dev/null +++ b/utils/completion/zsh/template.go @@ -0,0 +1,53 @@ +package zsh + +// see http://zsh.sourceforge.net/Doc/Release/Completion-System.html +const zshTemplate = `{{ $prog := .Name }}#compdef {{ $prog }} + +_{{ $prog }} () { + local cmd + if (( CURRENT > 2)); then + cmd=${words[2]} + curcontext="${curcontext%:*:*}:{{ $prog }}-$cmd" + (( CURRENT-- )) + shift words + case "${cmd}" in +{{- range .Commands }} + {{ .Name }}{{ range .Aliases }}|{{ . }}{{ end }}) + {{- if .Subcommands }} + local -a subcommands + subcommands=({{ range .Subcommands }} + "{{ .Name }}:{{ .Usage }}"{{ end }} + ) + {{- end }} + {{ if .Flags }}_arguments :{{ range .Flags }} "{{ . | formatFlag }}"{{ end }}{{ end }} + _describe -t commands "{{ $prog }} {{ .Name }}" subcommands + {{ if or (eq .Name "copy") (eq .Name "move") (eq .Name "delete") (eq .Name "show") }}_{{ $prog }}_complete_passwords{{ end -}} + ;; +{{- end }} + *) + _{{ $prog }}_complete_passwords + ;; + esac + else + local -a subcommands + subcommands=({{ range .Commands }} + "{{ .Name }}:{{ .Usage }}"{{ end }} + ) + _describe -t command '{{ $prog }}' subcommands + _arguments : {{ range .Flags }}"{{ . | formatFlag }}" {{ end }} + _{{ $prog }}_complete_passwords + fi +} + +_{{ $prog }}_complete_keys () { + local IFS=$'\n' + _values 'gpg keys' $(gpg2 --list-secret-keys --with-colons 2> /dev/null | cut -d : -f 10 | sort -u | sed '/^$/d') +} + +_{{ $prog }}_complete_passwords () { + _arguments : \ + "--clip[Copy the first line of the secret into the clipboard]" + _values 'passwords' $({{ $prog }} ls --flat) +} + +_{{ $prog }}`