lockbox

password manager
Log | Files | Refs | README | LICENSE

commit 38c784898cf1af0597482f9e1ff4aa3f288f7746
parent c3ac881cb98bfaf603b5c890383f4be2620c0005
Author: Sean Enck <sean@ttypty.com>
Date:   Tue, 25 Jul 2023 19:49:10 -0400

restructure to move cli -> app

Diffstat:
MMakefile | 2+-
Mcmd/main.go | 27+++++++++++++--------------
Minternal/app/core.go | 251+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ainternal/app/core_test.go | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rinternal/cli/completions.bash -> internal/app/doc/bash | 0
Rinternal/cli/doc.txt -> internal/app/doc/details | 0
Rinternal/cli/completions.zsh -> internal/app/doc/zsh | 0
Minternal/app/info.go | 17++++++++---------
Minternal/app/rekey.go | 5++---
Dinternal/cli/core.go | 251-------------------------------------------------------------------------------
Dinternal/cli/core_test.go | 72------------------------------------------------------------------------
Minternal/totp/core.go | 13++++++-------
12 files changed, 353 insertions(+), 357 deletions(-)

diff --git a/Makefile b/Makefile @@ -6,7 +6,7 @@ all: $(TARGET) build: $(TARGET) -$(TARGET): cmd/main.go internal/**/*.go go.* internal/cli/completions* +$(TARGET): cmd/main.go internal/**/*.go go.* internal/app/doc/* ifeq ($(VERSION),) $(error version not set) endif diff --git a/cmd/main.go b/cmd/main.go @@ -10,7 +10,6 @@ import ( "time" "github.com/enckse/lockbox/internal/app" - "github.com/enckse/lockbox/internal/cli" "github.com/enckse/lockbox/internal/inputs" "github.com/enckse/lockbox/internal/platform" "github.com/enckse/lockbox/internal/system" @@ -34,10 +33,10 @@ func handleEarly(command string, args []string) (bool, error) { return true, nil } switch command { - case cli.VersionCommand: + case app.VersionCommand: fmt.Printf("version: %s\n", version) return true, nil - case cli.ClearCommand: + case app.ClearCommand: return true, clearClipboard() } return false, nil @@ -62,31 +61,31 @@ func run() error { return err } switch command { - case cli.ReKeyCommand: + case app.ReKeyCommand: keyer, err := app.NewDefaultKeyer() if err != nil { return err } return app.ReKey(p, keyer) - case cli.ListCommand: + case app.ListCommand: return app.List(p) - case cli.MoveCommand: + case app.MoveCommand: return app.Move(p) - case cli.InsertCommand, cli.MultiLineCommand: + case app.InsertCommand, app.MultiLineCommand: mode := app.SingleLineInsert - if command == cli.MultiLineCommand { + if command == app.MultiLineCommand { mode = app.MultiLineInsert } return app.Insert(p, mode) - case cli.RemoveCommand: + case app.RemoveCommand: return app.Remove(p) - case cli.JSONCommand: + case app.JSONCommand: return app.JSON(p) - case cli.ShowCommand, cli.ClipCommand: - return app.ShowClip(p, command == cli.ShowCommand) - case cli.ConvCommand: + case app.ShowCommand, app.ClipCommand: + return app.ShowClip(p, command == app.ShowCommand) + case app.ConvCommand: return app.Conv(p) - case cli.TOTPCommand: + case app.TOTPCommand: args, err := totp.NewArguments(sub, inputs.TOTPToken()) if err != nil { return err diff --git a/internal/app/core.go b/internal/app/core.go @@ -2,14 +2,83 @@ package app import ( + "bytes" + "embed" "fmt" "io" "os" + "path/filepath" + "sort" + "strings" + "text/template" "github.com/enckse/lockbox/internal/backend" + "github.com/enckse/lockbox/internal/inputs" "github.com/enckse/lockbox/internal/system" ) +const ( + // TOTPCommand is the parent of totp and by defaults generates a rotating token + TOTPCommand = "totp" + // ConvCommand handles text conversion of the data store + ConvCommand = "conv" + // ClearCommand is a callback to manage clipboard clearing + ClearCommand = "clear" + // ClipCommand will copy values to the clipboard + ClipCommand = "clip" + // FindCommand is for simplistic searching of entries + FindCommand = "find" + // InsertCommand adds a value + InsertCommand = "insert" + // ListCommand lists all entries + ListCommand = "ls" + // MoveCommand will move source to destination + MoveCommand = "mv" + // ShowCommand will show the value in an entry + ShowCommand = "show" + // VersionCommand displays version information + VersionCommand = "version" + // HelpCommand shows usage + HelpCommand = "help" + // HelpAdvancedCommand shows advanced help + HelpAdvancedCommand = "verbose" + // RemoveCommand removes an entry + RemoveCommand = "rm" + // EnvCommand shows environment information used by lockbox + EnvCommand = "env" + // TOTPClipCommand is the argument for copying totp codes to clipboard + TOTPClipCommand = ClipCommand + // TOTPMinimalCommand is the argument for getting the short version of a code + TOTPMinimalCommand = "minimal" + // TOTPListCommand will list the totp-enabled entries + TOTPListCommand = ListCommand + // TOTPOnceCommand will perform like a normal totp request but not refresh + TOTPOnceCommand = "once" + // EnvDefaultsCommand will display the default env variables, not those set + EnvDefaultsCommand = "defaults" + // BashCommand is the command to generate bash completions + BashCommand = "bash" + // BashDefaultsCommand will generate environment agnostic completions + BashDefaultsCommand = "defaults" + // ReKeyCommand will rekey the underlying database + ReKeyCommand = "rekey" + // MultiLineCommand handles multi-line inserts (when not piped) + MultiLineCommand = "multiline" + // TOTPShowCommand is for showing the TOTP token + TOTPShowCommand = ShowCommand + // TOTPInsertCommand is for inserting totp tokens + TOTPInsertCommand = InsertCommand + // JSONCommand handles JSON outputs + JSONCommand = "json" + // ZshCommand is the command to generate zsh completions + ZshCommand = "zsh" + // ZshDefaultsCommand will generate environment agnostic completions + ZshDefaultsCommand = "defaults" +) + +//go:embed doc/* +var docs embed.FS + type ( // CommandOptions define how commands operate as an application CommandOptions interface { @@ -24,6 +93,28 @@ type ( args []string tx *backend.Transaction } + // Completions handles the inputs to completions for templating + Completions struct { + Options []string + CanClip bool + CanTOTP bool + ReadOnly bool + InsertCommand string + TOTPSubCommands []string + TOTPListCommand string + RemoveCommand string + ClipCommand string + ShowCommand string + MultiLineCommand string + MoveCommand string + TOTPCommand string + DoTOTPList string + DoList string + Executable string + JSONCommand string + HelpCommand string + HelpAdvancedCommand string + } ) // NewDefaultCommand creates a new app command @@ -79,3 +170,163 @@ func (a *DefaultCommand) IsPipe() bool { func (a *DefaultCommand) Input(pipe, multi bool) ([]byte, error) { return system.GetUserInputPassword(pipe, multi) } + +func subCommand(parent, name, args, desc string) string { + return commandText(args, fmt.Sprintf("%s %s", parent, name), desc) +} + +func command(name, args, desc string) string { + return commandText(args, name, desc) +} + +func commandText(args, name, desc string) string { + arguments := "" + if len(args) > 0 { + arguments = fmt.Sprintf("[%s]", args) + } + return fmt.Sprintf(" %-15s %-10s %s", name, arguments, desc) +} + +func exeName() (string, error) { + n, err := os.Executable() + if err != nil { + return "", err + } + return filepath.Base(n), nil +} + +// GenerateCompletions handles creating shell completion outputs +func GenerateCompletions(isBash, defaults bool) ([]string, error) { + name, err := exeName() + if err != nil { + return nil, err + } + c := Completions{ + Executable: name, + InsertCommand: InsertCommand, + RemoveCommand: RemoveCommand, + TOTPSubCommands: []string{TOTPMinimalCommand, TOTPOnceCommand, TOTPShowCommand}, + TOTPListCommand: TOTPListCommand, + ClipCommand: ClipCommand, + ShowCommand: ShowCommand, + MultiLineCommand: MultiLineCommand, + JSONCommand: JSONCommand, + HelpCommand: HelpCommand, + HelpAdvancedCommand: HelpAdvancedCommand, + TOTPCommand: TOTPCommand, + MoveCommand: MoveCommand, + DoList: fmt.Sprintf("%s %s", name, ListCommand), + DoTOTPList: fmt.Sprintf("%s %s %s", name, TOTPCommand, TOTPListCommand), + Options: []string{MultiLineCommand, EnvCommand, HelpCommand, ListCommand, ShowCommand, VersionCommand, JSONCommand}, + } + isReadOnly := false + isClip := true + isTOTP := true + if !defaults { + ro, err := inputs.IsReadOnly() + if err != nil { + return nil, err + } + isReadOnly = ro + noClip, err := inputs.IsNoClipEnabled() + if err != nil { + return nil, err + } + if noClip { + isClip = false + } + noTOTP, err := inputs.IsNoTOTP() + if err != nil { + return nil, err + } + if noTOTP { + isTOTP = false + } + } + c.CanClip = isClip + c.ReadOnly = isReadOnly + c.CanTOTP = isTOTP + if c.CanClip { + c.Options = append(c.Options, ClipCommand) + c.TOTPSubCommands = append(c.TOTPSubCommands, TOTPClipCommand) + } + if !c.ReadOnly { + c.Options = append(c.Options, MoveCommand, RemoveCommand, InsertCommand) + c.TOTPSubCommands = append(c.TOTPSubCommands, TOTPInsertCommand) + } + if c.CanTOTP { + c.Options = append(c.Options, TOTPCommand) + } + using, err := readDoc("zsh") + if err != nil { + return nil, err + } + if isBash { + using, err = readDoc("bash") + if err != nil { + return nil, err + } + } + t, err := template.New("t").Parse(using) + if err != nil { + return nil, err + } + var buf bytes.Buffer + if err := t.Execute(&buf, c); err != nil { + return nil, err + } + return []string{buf.String()}, nil +} + +// Usage return usage information +func Usage(verbose bool) ([]string, error) { + name, err := exeName() + if err != nil { + return nil, err + } + var results []string + results = append(results, command(BashCommand, "", "generate user environment bash completion")) + results = append(results, subCommand(BashCommand, BashDefaultsCommand, "", "generate default bash completion")) + results = append(results, command(ClipCommand, "entry", "copy the entry's value into the clipboard")) + 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, 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")) + results = append(results, command(MoveCommand, "src dst", "move an entry from source to destination")) + results = append(results, command(MultiLineCommand, "entry", "insert a multiline entry into the store")) + results = append(results, command(RemoveCommand, "entry", "remove an entry from the store")) + results = append(results, command(ShowCommand, "entry", "show the entry's value")) + results = append(results, command(TOTPCommand, "entry", "display an updating totp generated code")) + results = append(results, subCommand(TOTPCommand, TOTPClipCommand, "entry", "copy totp code to clipboard")) + results = append(results, subCommand(TOTPCommand, TOTPInsertCommand, "entry", "insert a new totp entry into the store")) + results = append(results, subCommand(TOTPCommand, TOTPListCommand, "", "list entries with totp settings")) + results = append(results, subCommand(TOTPCommand, TOTPOnceCommand, "entry", "display the first generated code")) + 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, subCommand(ZshCommand, ZshDefaultsCommand, "", "generate default zsh completion")) + sort.Strings(results) + usage := []string{fmt.Sprintf("%s usage:", name)} + if verbose { + results = append(results, "") + doc, err := readDoc("details") + if err != nil { + return nil, err + } + results = append(results, strings.Split(strings.TrimSpace(doc), "\n")...) + results = append(results, "") + results = append(results, inputs.ListEnvironmentVariables(false)...) + } + return append(usage, results...), nil +} + +func readDoc(doc string) (string, error) { + b, err := docs.ReadFile(filepath.Join("doc", doc)) + if err != nil { + return "", err + } + return string(b), err +} diff --git a/internal/app/core_test.go b/internal/app/core_test.go @@ -0,0 +1,72 @@ +package app_test + +import ( + "os" + "strings" + "testing" + + "github.com/enckse/lockbox/internal/app" +) + +func TestUsage(t *testing.T) { + u, _ := app.Usage(false) + if len(u) != 24 { + t.Errorf("invalid usage, out of date? %d", len(u)) + } + u, _ = app.Usage(true) + if len(u) != 95 { + t.Errorf("invalid verbose usage, out of date? %d", len(u)) + } + for _, usage := range u { + for _, l := range strings.Split(usage, "\n") { + if len(l) > 79 { + t.Errorf("usage line > 79 (%d), line: %s", len(l), l) + } + } + } +} + +func TestGenerateCompletions(t *testing.T) { + testCompletions(t, true) + testCompletions(t, false) +} + +func testCompletions(t *testing.T, bash bool) { + os.Setenv("LOCKBOX_NOTOTP", "yes") + os.Setenv("LOCKBOX_READONLY", "yes") + os.Setenv("LOCKBOX_NOCLIP", "yes") + defaults, _ := app.GenerateCompletions(bash, true) + roNoTOTPClip, _ := app.GenerateCompletions(bash, false) + if roNoTOTPClip[0] == defaults[0] { + t.Error("should not match defaults") + } + os.Setenv("LOCKBOX_NOTOTP", "") + roNoClip, _ := app.GenerateCompletions(bash, false) + if roNoClip[0] == defaults[0] || roNoClip[0] == roNoTOTPClip[0] { + t.Error("should not equal defaults nor no totp/clip") + } + os.Setenv("LOCKBOX_READONLY", "") + os.Setenv("LOCKBOX_NOCLIP", "yes") + noClip, _ := app.GenerateCompletions(bash, false) + if roNoClip[0] == noClip[0] || noClip[0] == defaults[0] || noClip[0] == roNoTOTPClip[0] { + t.Error("readonly/noclip != noclip (nor defaults, nor ro/no totp/clip)") + } + os.Setenv("LOCKBOX_READONLY", "yes") + os.Setenv("LOCKBOX_NOCLIP", "") + ro, _ := app.GenerateCompletions(bash, false) + if roNoClip[0] == ro[0] || noClip[0] == ro[0] || ro[0] == defaults[0] || ro[0] == roNoTOTPClip[0] { + t.Error("readonly/noclip != ro (nor ro == noclip, nor ro == defaults)") + } + os.Setenv("LOCKBOX_READONLY", "") + os.Setenv("LOCKBOX_NOCLIP", "") + os.Setenv("LOCKBOX_NOTOTP", "") + isDefaultsToo, _ := app.GenerateCompletions(bash, false) + if isDefaultsToo[0] != defaults[0] { + t.Error("defaults should match env defaults") + } + for _, confirm := range [][]string{defaults, roNoClip, noClip, ro, isDefaultsToo} { + if len(confirm) != 1 { + t.Error("completions returned an invalid array") + } + } +} diff --git a/internal/cli/completions.bash b/internal/app/doc/bash diff --git a/internal/cli/doc.txt b/internal/app/doc/details diff --git a/internal/cli/completions.zsh b/internal/app/doc/zsh diff --git a/internal/app/info.go b/internal/app/info.go @@ -7,7 +7,6 @@ import ( "io" "strings" - "github.com/enckse/lockbox/internal/cli" "github.com/enckse/lockbox/internal/inputs" ) @@ -26,28 +25,28 @@ func Info(w io.Writer, command string, args []string) (bool, error) { func info(command string, args []string) ([]string, error) { switch command { - case cli.HelpCommand: + case HelpCommand: if len(args) > 1 { return nil, errors.New("invalid help command") } isAdvanced := false if len(args) == 1 { - if args[0] == cli.HelpAdvancedCommand { + if args[0] == HelpAdvancedCommand { isAdvanced = true } else { return nil, errors.New("invalid help option") } } - results, err := cli.Usage(isAdvanced) + results, err := Usage(isAdvanced) if err != nil { return nil, err } return results, nil - case cli.EnvCommand, cli.BashCommand, cli.ZshCommand: - defaultFlag := cli.BashDefaultsCommand - isEnv := command == cli.EnvCommand + case EnvCommand, BashCommand, ZshCommand: + defaultFlag := BashDefaultsCommand + isEnv := command == EnvCommand if isEnv { - defaultFlag = cli.EnvDefaultsCommand + defaultFlag = EnvDefaultsCommand } defaults, err := getInfoDefault(args, defaultFlag) if err != nil { @@ -56,7 +55,7 @@ func info(command string, args []string) ([]string, error) { if isEnv { return inputs.ListEnvironmentVariables(!defaults), nil } - return cli.GenerateCompletions(command == cli.BashCommand, defaults) + return GenerateCompletions(command == BashCommand, defaults) } return nil, nil } diff --git a/internal/app/rekey.go b/internal/app/rekey.go @@ -9,7 +9,6 @@ import ( "strings" "github.com/enckse/lockbox/internal/backend" - "github.com/enckse/lockbox/internal/cli" "github.com/enckse/lockbox/internal/inputs" ) @@ -42,7 +41,7 @@ func NewDefaultKeyer() (DefaultKeyer, error) { // JSON will get the JSON backing entries func (r DefaultKeyer) JSON() (map[string]backend.JSON, error) { - out, err := exec.Command(r.exe, cli.JSONCommand).Output() + out, err := exec.Command(r.exe, JSONCommand).Output() if err != nil { return nil, err } @@ -55,7 +54,7 @@ func (r DefaultKeyer) JSON() (map[string]backend.JSON, error) { // Insert will insert the rekeying entry func (r DefaultKeyer) Insert(entry ReKeyEntry) error { - cmd := exec.Command(r.exe, cli.InsertCommand, entry.Path) + cmd := exec.Command(r.exe, InsertCommand, entry.Path) cmd.Env = append(os.Environ(), entry.Env...) in, err := cmd.StdinPipe() if nil != err { diff --git a/internal/cli/core.go b/internal/cli/core.go @@ -1,251 +0,0 @@ -// Package cli handles CLI helpers/commands -package cli - -import ( - "bytes" - _ "embed" - "fmt" - "os" - "path/filepath" - "sort" - "strings" - "text/template" - - "github.com/enckse/lockbox/internal/inputs" -) - -const ( - // TOTPCommand is the parent of totp and by defaults generates a rotating token - TOTPCommand = "totp" - // ConvCommand handles text conversion of the data store - ConvCommand = "conv" - // ClearCommand is a callback to manage clipboard clearing - ClearCommand = "clear" - // ClipCommand will copy values to the clipboard - ClipCommand = "clip" - // FindCommand is for simplistic searching of entries - FindCommand = "find" - // InsertCommand adds a value - InsertCommand = "insert" - // ListCommand lists all entries - ListCommand = "ls" - // MoveCommand will move source to destination - MoveCommand = "mv" - // ShowCommand will show the value in an entry - ShowCommand = "show" - // VersionCommand displays version information - VersionCommand = "version" - // HelpCommand shows usage - HelpCommand = "help" - // HelpAdvancedCommand shows advanced help - HelpAdvancedCommand = "verbose" - // RemoveCommand removes an entry - RemoveCommand = "rm" - // EnvCommand shows environment information used by lockbox - EnvCommand = "env" - // TOTPClipCommand is the argument for copying totp codes to clipboard - TOTPClipCommand = ClipCommand - // TOTPMinimalCommand is the argument for getting the short version of a code - TOTPMinimalCommand = "minimal" - // TOTPListCommand will list the totp-enabled entries - TOTPListCommand = ListCommand - // TOTPOnceCommand will perform like a normal totp request but not refresh - TOTPOnceCommand = "once" - // EnvDefaultsCommand will display the default env variables, not those set - EnvDefaultsCommand = "defaults" - // BashCommand is the command to generate bash completions - BashCommand = "bash" - // BashDefaultsCommand will generate environment agnostic completions - BashDefaultsCommand = "defaults" - // ReKeyCommand will rekey the underlying database - ReKeyCommand = "rekey" - // MultiLineCommand handles multi-line inserts (when not piped) - MultiLineCommand = "multiline" - // TOTPShowCommand is for showing the TOTP token - TOTPShowCommand = ShowCommand - // TOTPInsertCommand is for inserting totp tokens - TOTPInsertCommand = InsertCommand - // JSONCommand handles JSON outputs - JSONCommand = "json" - // ZshCommand is the command to generate zsh completions - ZshCommand = "zsh" - // ZshDefaultsCommand will generate environment agnostic completions - ZshDefaultsCommand = "defaults" -) - -var ( - //go:embed "completions.bash" - bashCompletions string - //go:embed "completions.zsh" - zshCompletions string - - //go:embed "doc.txt" - docSection string -) - -type ( - // Completions handles the inputs to completions for templating - Completions struct { - Options []string - CanClip bool - CanTOTP bool - ReadOnly bool - InsertCommand string - TOTPSubCommands []string - TOTPListCommand string - RemoveCommand string - ClipCommand string - ShowCommand string - MultiLineCommand string - MoveCommand string - TOTPCommand string - DoTOTPList string - DoList string - Executable string - JSONCommand string - HelpCommand string - HelpAdvancedCommand string - } -) - -func subCommand(parent, name, args, desc string) string { - return commandText(args, fmt.Sprintf("%s %s", parent, name), desc) -} - -func command(name, args, desc string) string { - return commandText(args, name, desc) -} - -func commandText(args, name, desc string) string { - arguments := "" - if len(args) > 0 { - arguments = fmt.Sprintf("[%s]", args) - } - return fmt.Sprintf(" %-15s %-10s %s", name, arguments, desc) -} - -func exeName() (string, error) { - n, err := os.Executable() - if err != nil { - return "", err - } - return filepath.Base(n), nil -} - -// GenerateCompletions handles creating shell completion outputs -func GenerateCompletions(isBash, defaults bool) ([]string, error) { - name, err := exeName() - if err != nil { - return nil, err - } - c := Completions{ - Executable: name, - InsertCommand: InsertCommand, - RemoveCommand: RemoveCommand, - TOTPSubCommands: []string{TOTPMinimalCommand, TOTPOnceCommand, TOTPShowCommand}, - TOTPListCommand: TOTPListCommand, - ClipCommand: ClipCommand, - ShowCommand: ShowCommand, - MultiLineCommand: MultiLineCommand, - JSONCommand: JSONCommand, - HelpCommand: HelpCommand, - HelpAdvancedCommand: HelpAdvancedCommand, - TOTPCommand: TOTPCommand, - MoveCommand: MoveCommand, - DoList: fmt.Sprintf("%s %s", name, ListCommand), - DoTOTPList: fmt.Sprintf("%s %s %s", name, TOTPCommand, TOTPListCommand), - Options: []string{MultiLineCommand, EnvCommand, HelpCommand, ListCommand, ShowCommand, VersionCommand, JSONCommand}, - } - isReadOnly := false - isClip := true - isTOTP := true - if !defaults { - ro, err := inputs.IsReadOnly() - if err != nil { - return nil, err - } - isReadOnly = ro - noClip, err := inputs.IsNoClipEnabled() - if err != nil { - return nil, err - } - if noClip { - isClip = false - } - noTOTP, err := inputs.IsNoTOTP() - if err != nil { - return nil, err - } - if noTOTP { - isTOTP = false - } - } - c.CanClip = isClip - c.ReadOnly = isReadOnly - c.CanTOTP = isTOTP - if c.CanClip { - c.Options = append(c.Options, ClipCommand) - c.TOTPSubCommands = append(c.TOTPSubCommands, TOTPClipCommand) - } - if !c.ReadOnly { - c.Options = append(c.Options, MoveCommand, RemoveCommand, InsertCommand) - c.TOTPSubCommands = append(c.TOTPSubCommands, TOTPInsertCommand) - } - if c.CanTOTP { - c.Options = append(c.Options, TOTPCommand) - } - using := zshCompletions - if isBash { - using = bashCompletions - } - t, err := template.New("t").Parse(using) - if err != nil { - return nil, err - } - var buf bytes.Buffer - if err := t.Execute(&buf, c); err != nil { - return nil, err - } - return []string{buf.String()}, nil -} - -// Usage return usage information -func Usage(verbose bool) ([]string, error) { - name, err := exeName() - if err != nil { - return nil, err - } - var results []string - results = append(results, command(BashCommand, "", "generate user environment bash completion")) - results = append(results, subCommand(BashCommand, BashDefaultsCommand, "", "generate default bash completion")) - results = append(results, command(ClipCommand, "entry", "copy the entry's value into the clipboard")) - 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, 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")) - results = append(results, command(MoveCommand, "src dst", "move an entry from source to destination")) - results = append(results, command(MultiLineCommand, "entry", "insert a multiline entry into the store")) - results = append(results, command(RemoveCommand, "entry", "remove an entry from the store")) - results = append(results, command(ShowCommand, "entry", "show the entry's value")) - results = append(results, command(TOTPCommand, "entry", "display an updating totp generated code")) - results = append(results, subCommand(TOTPCommand, TOTPClipCommand, "entry", "copy totp code to clipboard")) - results = append(results, subCommand(TOTPCommand, TOTPInsertCommand, "entry", "insert a new totp entry into the store")) - results = append(results, subCommand(TOTPCommand, TOTPListCommand, "", "list entries with totp settings")) - results = append(results, subCommand(TOTPCommand, TOTPOnceCommand, "entry", "display the first generated code")) - 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, subCommand(ZshCommand, ZshDefaultsCommand, "", "generate default zsh completion")) - sort.Strings(results) - usage := []string{fmt.Sprintf("%s usage:", name)} - if verbose { - results = append(results, "") - results = append(results, strings.Split(strings.TrimSpace(docSection), "\n")...) - results = append(results, "") - results = append(results, inputs.ListEnvironmentVariables(false)...) - } - return append(usage, results...), nil -} diff --git a/internal/cli/core_test.go b/internal/cli/core_test.go @@ -1,72 +0,0 @@ -package cli_test - -import ( - "os" - "strings" - "testing" - - "github.com/enckse/lockbox/internal/cli" -) - -func TestUsage(t *testing.T) { - u, _ := cli.Usage(false) - if len(u) != 24 { - t.Errorf("invalid usage, out of date? %d", len(u)) - } - u, _ = cli.Usage(true) - if len(u) != 95 { - t.Errorf("invalid verbose usage, out of date? %d", len(u)) - } - for _, usage := range u { - for _, l := range strings.Split(usage, "\n") { - if len(l) > 79 { - t.Errorf("usage line > 79 (%d), line: %s", len(l), l) - } - } - } -} - -func TestGenerateCompletions(t *testing.T) { - testCompletions(t, true) - testCompletions(t, false) -} - -func testCompletions(t *testing.T, bash bool) { - os.Setenv("LOCKBOX_NOTOTP", "yes") - os.Setenv("LOCKBOX_READONLY", "yes") - os.Setenv("LOCKBOX_NOCLIP", "yes") - defaults, _ := cli.GenerateCompletions(bash, true) - roNoTOTPClip, _ := cli.GenerateCompletions(bash, false) - if roNoTOTPClip[0] == defaults[0] { - t.Error("should not match defaults") - } - os.Setenv("LOCKBOX_NOTOTP", "") - roNoClip, _ := cli.GenerateCompletions(bash, false) - if roNoClip[0] == defaults[0] || roNoClip[0] == roNoTOTPClip[0] { - t.Error("should not equal defaults nor no totp/clip") - } - os.Setenv("LOCKBOX_READONLY", "") - os.Setenv("LOCKBOX_NOCLIP", "yes") - noClip, _ := cli.GenerateCompletions(bash, false) - if roNoClip[0] == noClip[0] || noClip[0] == defaults[0] || noClip[0] == roNoTOTPClip[0] { - t.Error("readonly/noclip != noclip (nor defaults, nor ro/no totp/clip)") - } - os.Setenv("LOCKBOX_READONLY", "yes") - os.Setenv("LOCKBOX_NOCLIP", "") - ro, _ := cli.GenerateCompletions(bash, false) - if roNoClip[0] == ro[0] || noClip[0] == ro[0] || ro[0] == defaults[0] || ro[0] == roNoTOTPClip[0] { - t.Error("readonly/noclip != ro (nor ro == noclip, nor ro == defaults)") - } - os.Setenv("LOCKBOX_READONLY", "") - os.Setenv("LOCKBOX_NOCLIP", "") - os.Setenv("LOCKBOX_NOTOTP", "") - isDefaultsToo, _ := cli.GenerateCompletions(bash, false) - if isDefaultsToo[0] != defaults[0] { - t.Error("defaults should match env defaults") - } - for _, confirm := range [][]string{defaults, roNoClip, noClip, ro, isDefaultsToo} { - if len(confirm) != 1 { - t.Error("completions returned an invalid array") - } - } -} diff --git a/internal/totp/core.go b/internal/totp/core.go @@ -12,7 +12,6 @@ import ( "github.com/enckse/lockbox/internal/app" "github.com/enckse/lockbox/internal/backend" - "github.com/enckse/lockbox/internal/cli" "github.com/enckse/lockbox/internal/inputs" "github.com/enckse/lockbox/internal/platform" "github.com/enckse/lockbox/internal/system" @@ -260,21 +259,21 @@ func NewArguments(args []string, tokenType string) (*Arguments, error) { sub := args[0] needs := true switch sub { - case cli.TOTPListCommand: + case app.TOTPListCommand: needs = false if len(args) != 1 { return nil, errors.New("list takes no arguments") } opts.Mode = ListMode - case cli.TOTPInsertCommand: + case app.TOTPInsertCommand: opts.Mode = InsertMode - case cli.TOTPShowCommand: + case app.TOTPShowCommand: opts.Mode = ShowMode - case cli.TOTPClipCommand: + case app.TOTPClipCommand: opts.Mode = ClipMode - case cli.TOTPMinimalCommand: + case app.TOTPMinimalCommand: opts.Mode = MinimalMode - case cli.TOTPOnceCommand: + case app.TOTPOnceCommand: opts.Mode = OnceMode default: return nil, ErrUnknownTOTPMode