commit 815d90dfcfe120ff5e07e4fcb003d29edde75d24
parent d0fc87bdd745a33a434989e940a0b0019c8ff56e
Author: Sean Enck <sean@ttypty.com>
Date: Fri, 19 Apr 2024 22:32:54 -0400
completions are a subcommand now
Diffstat:
9 files changed, 103 insertions(+), 97 deletions(-)
diff --git a/internal/app/completions.go b/internal/app/completions.go
@@ -122,7 +122,11 @@ func loadProfiles(exe string, canFilter bool) []Profile {
// GenerateCompletions handles creating shell completion outputs
func GenerateCompletions(completionType string, isHelp bool, exe string) ([]string, error) {
if isHelp {
- var h []string
+ h := []string{"completions are available for:"}
+ for _, s := range []string{CompletionsBashCommand, CompletionsFishCommand, CompletionsZshCommand} {
+ h = append(h, fmt.Sprintf(" - %s", s))
+ }
+ h = append(h, "")
for _, p := range loadProfiles(exe, false) {
if p.IsDefault {
continue
@@ -155,7 +159,6 @@ when %s=<unknown>
JSONCommand: JSONCommand,
HelpCommand: HelpCommand,
HelpAdvancedCommand: HelpAdvancedCommand,
- HelpShellCommand: HelpShellCommand,
TOTPCommand: TOTPCommand,
MoveCommand: MoveCommand,
DoList: fmt.Sprintf("%s %s", exe, ListCommand),
diff --git a/internal/app/core.go b/internal/app/core.go
@@ -55,10 +55,12 @@ const (
TOTPListCommand = ListCommand
// TOTPOnceCommand will perform like a normal totp request but not refresh
TOTPOnceCommand = "once"
- // BashCommand is the command to generate bash completions
- BashCommand = "bash"
- // HelpShellCommand is the help output about shell variables
- HelpShellCommand = "shell"
+ // CompletionsBashCommand is the command to generate bash completions
+ CompletionsBashCommand = "bash"
+ // CompletionsCommand are used to generate shell completions
+ CompletionsCommand = "completions"
+ // CompletionsHelpCommand displays information about shell completions
+ CompletionsHelpCommand = "help"
// ReKeyCommand will rekey the underlying database
ReKeyCommand = "rekey"
// MultiLineCommand handles multi-line inserts (when not piped)
@@ -69,12 +71,12 @@ const (
TOTPInsertCommand = InsertCommand
// JSONCommand handles JSON outputs
JSONCommand = "json"
- // ZshCommand is the command to generate zsh completions
- ZshCommand = "zsh"
- // FishCommand is the command to generate fish completions
- FishCommand = "fish"
- docDir = "doc"
- textFile = ".txt"
+ // CompletionsZshCommand is the command to generate zsh completions
+ CompletionsZshCommand = "zsh"
+ // CompletionsFishCommand is the command to generate fish completions
+ CompletionsFishCommand = "fish"
+ docDir = "doc"
+ textFile = ".txt"
)
//go:embed doc/*
@@ -96,13 +98,13 @@ type (
}
// Documentation is how documentation segments are templated
Documentation struct {
- Executable string
- MoveCommand string
- RemoveCommand string
- ReKeyCommand string
- HelpCommand string
- HelpShellCommand string
- ReKey struct {
+ Executable string
+ MoveCommand string
+ RemoveCommand string
+ ReKeyCommand string
+ CompletionsCommand string
+ CompletionsHelpCommand string
+ ReKey struct {
Store string
KeyFile string
Key string
@@ -184,12 +186,15 @@ func commandText(args, name, desc string) string {
// Usage return usage information
func Usage(verbose bool, exe string) ([]string, error) {
var results []string
- results = append(results, command(BashCommand, "", "generate user environment bash completion"))
results = append(results, command(ClipCommand, "entry", "copy the entry's value into the clipboard"))
+ results = append(results, command(CompletionsCommand, "<shell>", "generate completions via auto-detection of shell"))
+ results = append(results, subCommand(CompletionsCommand, CompletionsBashCommand, "", "generate bash completions"))
+ results = append(results, subCommand(CompletionsCommand, CompletionsFishCommand, "", "generate fish completions"))
+ results = append(results, subCommand(CompletionsCommand, CompletionsHelpCommand, "", "show help information about completions"))
+ results = append(results, subCommand(CompletionsCommand, CompletionsZshCommand, "", "generate zsh completions"))
results = append(results, command(EnvCommand, "", "display environment variable information"))
results = append(results, command(HelpCommand, "", "show this usage information"))
results = append(results, subCommand(HelpCommand, HelpAdvancedCommand, "", "display verbose help information"))
- results = append(results, subCommand(HelpCommand, HelpShellCommand, "", "display shell variable help information"))
results = append(results, command(InsertCommand, "entry", "insert a new entry into the store"))
results = append(results, command(JSONCommand, "filter", "display detailed information"))
results = append(results, command(ListCommand, "", "list entries"))
@@ -206,19 +211,17 @@ func Usage(verbose bool, exe string) ([]string, error) {
results = append(results, subCommand(TOTPCommand, TOTPMinimalCommand, "entry", "display the first generated code (no details)"))
results = append(results, subCommand(TOTPCommand, TOTPShowCommand, "entry", "show the totp entry"))
results = append(results, command(VersionCommand, "", "display version information"))
- results = append(results, command(ZshCommand, "", "generate user environment zsh completion"))
- results = append(results, command(FishCommand, "", "generate user environment fish completion"))
sort.Strings(results)
usage := []string{fmt.Sprintf("%s usage:", exe)}
if verbose {
results = append(results, "")
document := Documentation{
- Executable: filepath.Base(exe),
- MoveCommand: MoveCommand,
- RemoveCommand: RemoveCommand,
- ReKeyCommand: ReKeyCommand,
- HelpShellCommand: HelpShellCommand,
- HelpCommand: HelpCommand,
+ Executable: filepath.Base(exe),
+ MoveCommand: MoveCommand,
+ RemoveCommand: RemoveCommand,
+ ReKeyCommand: ReKeyCommand,
+ CompletionsCommand: CompletionsCommand,
+ CompletionsHelpCommand: CompletionsHelpCommand,
}
document.ReKey.Store = setDocFlag(config.ReKeyStoreFlag)
document.ReKey.Key = setDocFlag(config.ReKeyKeyFlag)
diff --git a/internal/app/core_test.go b/internal/app/core_test.go
@@ -9,11 +9,11 @@ import (
func TestUsage(t *testing.T) {
u, _ := app.Usage(false, "lb")
- if len(u) != 25 {
+ if len(u) != 26 {
t.Errorf("invalid usage, out of date? %d", len(u))
}
u, _ = app.Usage(true, "lb")
- if len(u) != 108 {
+ if len(u) != 109 {
t.Errorf("invalid verbose usage, out of date? %d", len(u))
}
for _, usage := range u {
diff --git a/internal/app/doc/bash.sh b/internal/app/doc/bash.sh
@@ -17,7 +17,7 @@
if [ "$COMP_CWORD" -eq 2 ]; then
case ${COMP_WORDS[1]} in
"{{ $.HelpCommand }}")
- opts="{{ $.HelpAdvancedCommand }} {{ $.HelpShellCommand }}"
+ opts="{{ $.HelpAdvancedCommand }}"
;;
{{- if not $profile.ReadOnly }}
{{- if $profile.CanList }}
diff --git a/internal/app/doc/completions.txt b/internal/app/doc/completions.txt
@@ -1,5 +1,5 @@
Completions are available for certain shells and, by default, assume all
features of `{{ $.Executable }}` are enabled and available. When changing certain environment
flags it may be useful to change the completion profile to more closely match
-the restricted command options, run `{{ $.Executable }} {{ $.HelpCommand }} {{ $.HelpShellCommand }}` for information
+the restricted command options, run `{{ $.Executable }} {{ $.CompletionsCommand }} {{ $.CompletionsHelpCommand }}` for information
on how best to alter completion outputs.
diff --git a/internal/app/doc/fish.sh b/internal/app/doc/fish.sh
@@ -5,7 +5,7 @@ complete -c {{ $.Executable }} -f
function {{ $profile.Name }}
set -l commands {{ range $idx, $value := $profile.Options }}{{ if gt $idx 0}} {{ end }}{{ $value }}{{ end }}
complete -c {{ $.Executable }} -n "not __fish_seen_subcommand_from $commands" -a "$commands"
- complete -c {{ $.Executable }} -n "__fish_seen_subcommand_from {{ $.HelpCommand }}; and test (count (commandline -opc)) -lt 3" -a "{{ $.HelpAdvancedCommand }} {{ $.HelpShellCommand }}"
+ complete -c {{ $.Executable }} -n "__fish_seen_subcommand_from {{ $.HelpCommand }}; and test (count (commandline -opc)) -lt 3" -a "{{ $.HelpAdvancedCommand }}"
{{- if not $profile.ReadOnly }}
{{- if $profile.CanList }}
complete -c {{ $.Executable }} -n "__fish_seen_subcommand_from {{ $.InsertCommand }} {{ $.MultiLineCommand }} {{ $.RemoveCommand }}; and test (count (commandline -opc)) -lt 3" -a "({{ $.DoList }})"
diff --git a/internal/app/doc/zsh.sh b/internal/app/doc/zsh.sh
@@ -21,7 +21,6 @@
case $words[2] in
"{{ $.HelpCommand }}")
if [ "$len" -eq 3 ]; then
- compadd "$@" "{{ $.HelpShellCommand }}"
compadd "$@" "{{ $.HelpAdvancedCommand }}"
fi
;;
diff --git a/internal/app/info.go b/internal/app/info.go
@@ -40,20 +40,18 @@ func info(command string, args []string) ([]string, error) {
return nil, errors.New("invalid help command")
}
isAdvanced := false
- exe, err := exeName()
- if err != nil {
- return nil, err
- }
if len(args) == 1 {
switch args[0] {
case HelpAdvancedCommand:
isAdvanced = true
- case HelpShellCommand:
- return GenerateCompletions("", true, exe)
default:
return nil, errors.New("invalid help option")
}
}
+ exe, err := exeName()
+ if err != nil {
+ return nil, err
+ }
results, err := Usage(isAdvanced, exe)
if err != nil {
return nil, err
@@ -64,15 +62,31 @@ func info(command string, args []string) ([]string, error) {
return nil, errors.New("invalid env command")
}
return config.Environ(), nil
- case BashCommand, ZshCommand, FishCommand:
- if len(args) != 0 {
- return nil, fmt.Errorf("invalid %s command", command)
- }
+ case CompletionsCommand:
+ shell := ""
exe, err := exeName()
if err != nil {
return nil, err
}
- return GenerateCompletions(command, false, exe)
+ switch len(args) {
+ case 0:
+ shell = filepath.Base(os.Getenv("SHELL"))
+ case 1:
+ sub := args[0]
+ if sub == CompletionsHelpCommand {
+ return GenerateCompletions("", true, exe)
+ }
+ shell = sub
+ default:
+ return nil, errors.New("invalid completions subcommand")
+ }
+ switch shell {
+ case CompletionsZshCommand, CompletionsBashCommand, CompletionsFishCommand:
+ break
+ default:
+ return nil, fmt.Errorf("unknown completion type: %s", shell)
+ }
+ return GenerateCompletions(shell, false, exe)
}
return nil, nil
}
diff --git a/internal/app/info_test.go b/internal/app/info_test.go
@@ -3,6 +3,7 @@ package app_test
import (
"bytes"
"os"
+ "strings"
"testing"
"github.com/enckse/lockbox/internal/app"
@@ -41,36 +42,6 @@ func TestHelpInfo(t *testing.T) {
if _, err = app.Info(&buf, "help", []string{"verbose", "A"}); err.Error() != "invalid help command" {
t.Errorf("invalid error: %v", err)
}
- old = buf.String()
- buf = bytes.Buffer{}
- ok, err = app.Info(&buf, "help", []string{"shell"})
- if !ok || err != nil {
- t.Errorf("invalid error: %v", err)
- }
- if buf.String() == "" || old == buf.String() {
- t.Error("nothing written")
- }
-}
-
-func TestBashInfo(t *testing.T) {
- os.Clearenv()
- var buf bytes.Buffer
- ok, err := app.Info(&buf, "bash", []string{})
- if !ok || err != nil {
- t.Errorf("invalid error: %v", err)
- }
- if buf.String() == "" {
- t.Error("nothing written")
- }
- if _, err = app.Info(&buf, "bash", []string{"defaults"}); err.Error() != "invalid bash command" {
- t.Errorf("invalid error: %v", err)
- }
- if _, err = app.Info(&buf, "bash", []string{"test", "default"}); err.Error() != "invalid bash command" {
- t.Errorf("invalid error: %v", err)
- }
- if _, err = app.Info(&buf, "bash", []string{"short"}); err.Error() != "invalid bash command" {
- t.Errorf("invalid error: %v", err)
- }
}
func TestEnvInfo(t *testing.T) {
@@ -99,38 +70,54 @@ func TestEnvInfo(t *testing.T) {
}
}
-func TestZshInfo(t *testing.T) {
- os.Clearenv()
+func TestCompletionInfo(t *testing.T) {
+ defer os.Clearenv()
+ for k, v := range map[string]string{
+ "zsh": "typeset -A opt_args",
+ "fish": "set -l commands",
+ "bash": "local cur opts",
+ } {
+ for _, b := range []bool{true, false} {
+ os.Clearenv()
+ sub := []string{k}
+ os.Setenv("SHELL", "invalid")
+ if b {
+ sub = []string{}
+ os.Setenv("SHELL", k)
+ }
+ var buf bytes.Buffer
+ ok, err := app.Info(&buf, "completions", sub)
+ if !ok || err != nil {
+ t.Errorf("invalid error: %v", err)
+ }
+ s := buf.String()
+ if s == "" {
+ t.Error("nothing written")
+ }
+ if !strings.Contains(s, v) {
+ t.Errorf("invalid completions for %s", k)
+ }
+ }
+ }
var buf bytes.Buffer
- ok, err := app.Info(&buf, "zsh", []string{})
+ ok, err := app.Info(&buf, "completions", []string{"help"})
if !ok || err != nil {
t.Errorf("invalid error: %v", err)
}
- if buf.String() == "" {
+ s := buf.String()
+ if s == "" {
t.Error("nothing written")
}
- if _, err = app.Info(&buf, "zsh", []string{"defaults"}); err.Error() != "invalid zsh command" {
- t.Errorf("invalid error: %v", err)
- }
- if _, err = app.Info(&buf, "zsh", []string{"test", "default"}); err.Error() != "invalid zsh command" {
- t.Errorf("invalid error: %v", err)
- }
-}
-func TestFishInfo(t *testing.T) {
- os.Clearenv()
- var buf bytes.Buffer
- ok, err := app.Info(&buf, "fish", []string{})
- if !ok || err != nil {
+ if _, err = app.Info(&buf, "completions", []string{"helps"}); err.Error() != "unknown completion type: helps" {
t.Errorf("invalid error: %v", err)
}
- if buf.String() == "" {
- t.Error("nothing written")
- }
- if _, err = app.Info(&buf, "fish", []string{"help"}); err.Error() != "invalid fish command" {
+ os.Clearenv()
+ os.Setenv("SHELL", "bad")
+ if _, err = app.Info(&buf, "completions", []string{}); err.Error() != "unknown completion type: bad" {
t.Errorf("invalid error: %v", err)
}
- if _, err = app.Info(&buf, "fish", []string{"test", "default"}); err.Error() != "invalid fish command" {
+ if _, err = app.Info(&buf, "completions", []string{"bash"}); err != nil {
t.Errorf("invalid error: %v", err)
}
}