lockbox

password manager
Log | Files | Refs | README | LICENSE

commit e9247b213d948a41586e1a6063fe0030caec312c
parent 587457304e4818e048cbc9f4efbfc2b31dea10b3
Author: Sean Enck <sean@ttypty.com>
Date:   Sun,  6 Oct 2024 17:08:27 -0400

rebuild zsh/bash logic to be much simpler to control features on/off (fish is wip)

Diffstat:
MMakefile | 2+-
Minternal/app/completions.go | 143++++++++++++++++++++++++++-----------------------------------------------------
Minternal/app/completions_test.go | 45---------------------------------------------
Minternal/app/core.go | 8+++-----
Minternal/app/core_test.go | 4++--
Minternal/app/info.go | 2+-
Minternal/app/info_test.go | 1-
Minternal/app/shell/bash.sh | 62++++++++++++++++++++++++++++++++------------------------------
Dinternal/app/shell/fish.sh | 32--------------------------------
Dinternal/app/shell/shell.sh | 17-----------------
Minternal/app/shell/zsh.sh | 64++++++++++++++++++++++++++++++++++++++++------------------------
Minternal/config/core.go | 39++++++++++++++-------------------------
Minternal/config/core_test.go | 7+++++++
Minternal/config/vars.go | 86++-----------------------------------------------------------------------------
Minternal/config/vars_test.go | 22----------------------
15 files changed, 148 insertions(+), 386 deletions(-)

diff --git a/Makefile b/Makefile @@ -10,7 +10,7 @@ all: $(TARGET) build: $(TARGET) -$(TARGET): cmd/main.go internal/**/*.go go.* internal/app/doc/* +$(TARGET): cmd/main.go internal/**/*.go go.* internal/app/doc/* internal/app/shell/* ifeq ($(VERSION),) $(error version not set) endif diff --git a/internal/app/completions.go b/internal/app/completions.go @@ -6,7 +6,6 @@ import ( "embed" "fmt" "slices" - "strings" "text/template" "github.com/seanenck/lockbox/internal/config" @@ -30,97 +29,41 @@ type ( DefaultCompletion string HelpCommand string HelpAdvancedCommand string - Profiles []Profile - Shell string - CompletionEnv string - IsYes string - DefaultProfile Profile - IsFish bool + Options []CompletionOption + TOTPSubCommands []CompletionOption } - - // Profile is a completion profile - Profile struct { - Name string - CanClip bool - CanTOTP bool - CanList bool - CanGenerate bool - ReadOnly bool - IsDefault bool + // CompletionOption are conditional wrapped logic for options that may be disabled + CompletionOption struct { Conditional string + Key string } ) //go:embed shell/* var shell embed.FS -// Options will list the profile options -func (p Profile) Options() []string { - opts := []string{EnvCommand, HelpCommand, ListCommand, ShowCommand, VersionCommand, JSONCommand} - if p.CanClip { - opts = append(opts, ClipCommand) - } - if !p.ReadOnly { - opts = append(opts, MoveCommand, RemoveCommand, InsertCommand, MultiLineCommand) - } - if p.CanTOTP { - opts = append(opts, TOTPCommand) - } - if p.CanGenerate { - opts = append(opts, PasswordGenerateCommand) - } - return opts +func newConditional(left, right string) string { + return fmt.Sprintf("[ \"%s\" != \"%s\" ]", left, right) } -// TOTPSubCommands are the list of sub commands for TOTP within the profile -func (p Profile) TOTPSubCommands() []string { - totp := []string{TOTPMinimalCommand, TOTPOnceCommand, TOTPShowCommand} - if p.CanClip { - totp = append(totp, TOTPClipCommand) - } - if !p.ReadOnly { - totp = append(totp, TOTPInsertCommand) - } - return totp +func genOption(to []CompletionOption, command, left, right string) []CompletionOption { + conditional := newConditional(left, right) + return append(to, CompletionOption{conditional, command}) } -func loadProfiles(exe string) []Profile { - profiles := config.LoadCompletionProfiles() - conditionals := make(map[int][]Profile) - maxCount := 0 - for _, p := range profiles { - name := p.Name - if p.Default { - name = "default" - } - n := Profile{Name: fmt.Sprintf("_%s-%s", exe, name)} - n.CanClip = p.Clip - n.CanList = p.List - n.CanTOTP = p.TOTP - n.ReadOnly = !p.Write - n.IsDefault = p.Default - n.CanGenerate = p.Generate - var sub []string - for _, e := range p.Env { - sub = append(sub, fmt.Sprintf("[ %s ]", e)) - } - n.Conditional = strings.Join(sub, " && ") - count := len(p.Env) - val := conditionals[count] - conditionals[count] = append(val, n) - if count > maxCount { - maxCount = count - } +func newGenOptions(defaults ...string) []CompletionOption { + opt := []CompletionOption{} + for _, a := range defaults { + opt = genOption(opt, a, "1", "0") } - var res []Profile - for maxCount >= 0 { - val, ok := conditionals[maxCount] - if ok { - res = append(res, val...) - } - maxCount-- + return opt +} + +func genOptionKeyValues(to []CompletionOption, kv map[string]string) []CompletionOption { + for key, env := range kv { + to = genOption(to, key, fmt.Sprintf("$%s", env), config.YesValue) } - return res + return to } // GenerateCompletions handles creating shell completion outputs @@ -143,31 +86,37 @@ func GenerateCompletions(completionType, exe string) ([]string, error) { MoveCommand: MoveCommand, DoList: fmt.Sprintf("%s %s", exe, ListCommand), DoTOTPList: fmt.Sprintf("%s %s %s", exe, TOTPCommand, TOTPListCommand), - DefaultCompletion: fmt.Sprintf("$%s", config.EnvDefaultCompletionKey), - IsYes: config.YesValue, - IsFish: completionType == CompletionsFishCommand, } + // TOTPSubCommands: []string{TOTPMinimalCommand, TOTPOnceCommand, TOTPShowCommand, TOTPClipCommand, TOTPInsertCommand}, + cmds := newGenOptions( + EnvCommand, + HelpCommand, + ListCommand, + ShowCommand, + VersionCommand, + JSONCommand, + ) + cmds = genOptionKeyValues(cmds, map[string]string{ + ClipCommand: config.EnvNoClip.Key(), + TOTPCommand: config.EnvNoTOTP.Key(), + MoveCommand: config.EnvReadOnly.Key(), + RemoveCommand: config.EnvReadOnly.Key(), + InsertCommand: config.EnvReadOnly.Key(), + MultiLineCommand: config.EnvReadOnly.Key(), + PasswordGenerateCommand: config.EnvNoPasswordGen.Key(), + }) + c.Options = cmds + totp := newGenOptions(TOTPMinimalCommand, TOTPOnceCommand, TOTPShowCommand) + totp = genOptionKeyValues(totp, map[string]string{ + TOTPClipCommand: config.EnvNoClip.Key(), + TOTPInsertCommand: config.EnvReadOnly.Key(), + }) + c.TOTPSubCommands = totp using, err := readShell(completionType) if err != nil { return nil, err } - shellScript, err := readShell("shell") - if err != nil { - return nil, err - } - c.Profiles = loadProfiles(exe) - for _, p := range c.Profiles { - if p.IsDefault { - c.DefaultProfile = p - break - } - } - shell, err := templateScript(shellScript, c) - if err != nil { - return nil, err - } - c.Shell = shell s, err := templateScript(using, c) if err != nil { return nil, err diff --git a/internal/app/completions_test.go b/internal/app/completions_test.go @@ -10,7 +10,6 @@ import ( func TestCompletions(t *testing.T) { for k, v := range map[string]string{ "zsh": "typeset -A opt_args", - "fish": "set -l commands", "bash": "local cur opts", } { testCompletion(t, k, v) @@ -29,47 +28,3 @@ func testCompletion(t *testing.T, completionMode, need string) { t.Errorf("invalid output, bad shell generation: %v", v) } } - -func TestProfileOptions(t *testing.T) { - p := app.Profile{Name: "_abc-test-awera-zzz"} - p.CanClip = true - p.CanTOTP = true - if len(p.Options()) != 12 { - t.Errorf("invalid options: %v", p.Options()) - } - p.CanClip = false - if len(p.Options()) != 11 { - t.Errorf("invalid options: %v", p.Options()) - } - p.CanClip = true - p.CanTOTP = false - if len(p.Options()) != 11 { - t.Errorf("invalid options: %v", p.Options()) - } - p.CanTOTP = true - p.ReadOnly = true - if len(p.Options()) != 8 { - t.Errorf("invalid options: %v", p.Options()) - } - p.CanGenerate = true - if len(p.Options()) != 9 { - t.Errorf("invalid options: %v", p.Options()) - } -} - -func TestProfileTOTPSubOptions(t *testing.T) { - p := app.Profile{Name: "_abc-test-awera-zzz"} - p.CanClip = true - if len(p.TOTPSubCommands()) != 5 { - t.Errorf("invalid options: %v", p.TOTPSubCommands()) - } - p.CanClip = false - if len(p.TOTPSubCommands()) != 4 { - t.Errorf("invalid options: %v", p.TOTPSubCommands()) - } - p.CanClip = true - p.ReadOnly = true - if len(p.TOTPSubCommands()) != 4 { - t.Errorf("invalid options: %v", p.TOTPSubCommands()) - } -} diff --git a/internal/app/core.go b/internal/app/core.go @@ -71,10 +71,8 @@ const ( JSONCommand = "json" // CompletionsZshCommand is the command to generate zsh completions CompletionsZshCommand = "zsh" - // CompletionsFishCommand is the command to generate fish completions - CompletionsFishCommand = "fish" - docDir = "doc" - textFile = ".txt" + docDir = "doc" + textFile = ".txt" // PasswordGenerateCommand is the command to do password generation PasswordGenerateCommand = "pwgen" ) @@ -82,7 +80,7 @@ const ( var ( //go:embed doc/* docs embed.FS - completionTypes = []string{CompletionsBashCommand, CompletionsFishCommand, CompletionsZshCommand} + completionTypes = []string{CompletionsBashCommand, CompletionsZshCommand} ) type ( 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) != 26 { + if len(u) != 25 { t.Errorf("invalid usage, out of date? %d", len(u)) } u, _ = app.Usage(true, "lb") - if len(u) != 115 { + if len(u) != 114 { t.Errorf("invalid verbose usage, out of date? %d", len(u)) } for _, usage := range u { diff --git a/internal/app/info.go b/internal/app/info.go @@ -77,7 +77,7 @@ func info(command string, args []string) ([]string, error) { return nil, errors.New("invalid completions subcommand") } switch shell { - case CompletionsZshCommand, CompletionsBashCommand, CompletionsFishCommand: + case CompletionsZshCommand, CompletionsBashCommand: break default: return nil, fmt.Errorf("unknown completion type: %s", shell) diff --git a/internal/app/info_test.go b/internal/app/info_test.go @@ -74,7 +74,6 @@ 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} { diff --git a/internal/app/shell/bash.sh b/internal/app/shell/bash.sh @@ -1,68 +1,71 @@ # {{ $.Executable }} completion -{{ $.Shell }} - -{{- range $idx, $profile := $.Profiles }} - -{{ $profile.Name }}() { - local cur opts +_{{ $.Executable }}() { + local cur opts chosen found cur=${COMP_WORDS[COMP_CWORD]} if [ "$COMP_CWORD" -eq 1 ]; then -{{- range $idx, $value := $profile.Options }} - opts="${opts}{{ $value }} " +{{- range $idx, $value := $.Options }} + if {{ $value.Conditional }}; then + opts="${opts}{{ $value.Key }} " + fi {{- end}} # shellcheck disable=SC2207 COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) else + if [ "$COMP_CWORD" -lt 2 ]; then + return + fi + chosen=${COMP_WORDS[1]} + found=0 +{{- range $idx, $value := $.Options }} + if {{ $value.Conditional }}; then + if [ "$chosen" == "{{ $value.Key }}" ]; then + found=1 + fi + fi +{{- end}} + if [ "$found" -eq 0 ]; then + return + fi if [ "$COMP_CWORD" -eq 2 ]; then - case ${COMP_WORDS[1]} in + case "$chosen" in "{{ $.HelpCommand }}") opts="{{ $.HelpAdvancedCommand }}" ;; -{{- if not $profile.ReadOnly }} -{{- if $profile.CanList }} "{{ $.InsertCommand }}" | "{{ $.MultiLineCommand }}" | "{{ $.MoveCommand }}" | "{{ $.RemoveCommand }}") opts="$opts $({{ $.DoList }})" ;; -{{- end}} -{{- end}} -{{- if $profile.CanTOTP }} "{{ $.TOTPCommand }}") opts="{{ $.TOTPListCommand }} " {{- range $key, $value := .TOTPSubCommands }} - opts="$opts {{ $value }}" + if {{ $value.Conditional }}; then + opts="$opts {{ $value.Key }}" + fi {{- end}} ;; -{{- end}} -{{- if $profile.CanList }} - "{{ $.ShowCommand }}" | "{{ $.JSONCommand }}"{{ if $profile.CanClip }} | "{{ $.ClipCommand }}" {{end}}) + "{{ $.ShowCommand }}" | "{{ $.JSONCommand }}" | "{{ $.ClipCommand }}") opts=$({{ $.DoList }}) ;; -{{- end}} esac -{{- if $profile.CanList }} else if [ "$COMP_CWORD" -eq 3 ]; then - case "${COMP_WORDS[1]}" in -{{- if not $profile.ReadOnly }} + case "$chosen" in "{{ $.MoveCommand }}") opts=$({{ $.DoList }}) ;; -{{- end }} -{{- if $profile.CanTOTP }} "{{ $.TOTPCommand }}") case "${COMP_WORDS[2]}" in -{{- range $key, $value := $profile.TOTPSubCommands }} - "{{ $value }}") - opts=$({{ $.DoTOTPList }}) +{{- range $key, $value := $.TOTPSubCommands }} + "{{ $value.Key }}") + if {{ $value.Conditional }}; then + opts=$({{ $.DoTOTPList }}) + fi ;; {{- end}} esac ;; -{{- end}} esac fi -{{- end}} fi if [ -n "$opts" ]; then # shellcheck disable=SC2207 @@ -70,6 +73,5 @@ fi fi } -{{- end}} complete -F _{{ $.Executable }} -o bashdefault {{ $.Executable }} diff --git a/internal/app/shell/fish.sh b/internal/app/shell/fish.sh @@ -1,32 +0,0 @@ -complete -c {{ $.Executable }} -f - -{{- range $idx, $profile := $.Profiles }} - -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 }}" -{{- 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 }})" - complete -c {{ $.Executable }} -n "__fish_seen_subcommand_from {{ $.MoveCommand }}; and test (count (commandline -opc)) -lt 4" -a "({{ $.DoList }})" -{{- end}} -{{- end}} -{{- if $profile.CanTOTP }} - set -l totps {{ $.TOTPListCommand }}{{ range $key, $value := .TOTPSubCommands }} {{ $value }}{{ end }} - complete -c {{ $.Executable }} -n "__fish_seen_subcommand_from {{ $.TOTPCommand }}; and not __fish_seen_subcommand_from $totps" -a "$totps" -{{- if $profile.CanList }} -complete -c {{ $.Executable }} -n "__fish_seen_subcommand_from {{ $.TOTPCommand }}; and __fish_seen_subcommand_from $totps; and test (count (commandline -opc)) -lt 4" -a "({{ $.DoTOTPList }})" -{{- end}} -{{- end}} -{{- if $profile.CanList }} - complete -c {{ $.Executable }} -n "__fish_seen_subcommand_from {{ $.ShowCommand }} {{ $.JSONCommand }}{{ if $profile.CanClip }} {{ $.ClipCommand }} {{end}}; and test (count (commandline -opc)) -lt 3" -a "({{ $.DoList}})" -{{- end}} -end -{{- end}} - -function {{ $.Executable }}-completions - {{ $.Shell }} -end - -{{ $.Executable }}-completions diff --git a/internal/app/shell/shell.sh b/internal/app/shell/shell.sh @@ -1,17 +0,0 @@ -{{- if not $.IsFish }} -_{{ $.Executable }}() { -{{- end }} - if [ -z "{{ $.DefaultCompletion }}" ] || [ "{{ $.DefaultCompletion }}" != "{{ $.IsYes }}" ]{{ if not $.IsFish }}; then{{ end }} - {{- range $idx, $prof := $.Profiles }} - {{- if not $prof.IsDefault }} - if {{ $prof.Conditional }}{{ if not $.IsFish }}; then {{ end }} - {{ $prof.Name }} - return - {{ if $.IsFish }}end{{ else }}fi{{ end }} - {{- end }} - {{- end }} - {{ if $.IsFish }}end{{ else }}fi{{ end }} - {{ $.DefaultProfile.Name }} -{{- if not $.IsFish }} -} -{{- end }} diff --git a/internal/app/shell/zsh.sh b/internal/app/shell/zsh.sh @@ -1,11 +1,7 @@ #compdef _{{ $.Executable }} {{ $.Executable }} -{{ $.Shell }} - -{{- range $idx, $profile := $.Profiles }} - -{{ $profile.Name }}() { - local curcontext="$curcontext" state len +_{{ $.Executable }}() { + local curcontext="$curcontext" state len chosen found args typeset -A opt_args _arguments \ @@ -15,17 +11,39 @@ len=${#words[@]} case $state in main) - _arguments '1:main:({{ range $idx, $value := $profile.Options }}{{ if gt $idx 0}} {{ end }}{{ $value }}{{ end }})' + args="" +{{- range $idx, $value := $.Options }} + if {{ $value.Conditional }}; then + if [ -n "$args" ]; then + args="$args " + fi + args="${args}{{ $value.Key }}" + fi +{{- end }} + _arguments "1:main:($args)" ;; *) - case $words[2] in + if [ "$len" -lt 2 ]; then + return + fi + chosen=$words[2] + found=0 +{{- range $idx, $value := $.Options }} + if {{ $value.Conditional }}; then + if [[ "$chosen" == "{{ $value.Key }}" ]]; then + found=1 + fi + fi +{{- end }} + if [ "$found" -eq 0 ]; then + return + fi + case $chosen in "{{ $.HelpCommand }}") if [ "$len" -eq 3 ]; then compadd "$@" "{{ $.HelpAdvancedCommand }}" fi ;; -{{- if not $profile.ReadOnly }} -{{- if $profile.CanList }} "{{ $.InsertCommand }}" | "{{ $.MultiLineCommand }}" | "{{ $.RemoveCommand }}") if [ "$len" -eq 3 ]; then compadd "$@" $({{ $.DoList }}) @@ -38,37 +56,35 @@ ;; esac ;; -{{- end}} -{{- end}} -{{- if $profile.CanTOTP }} "{{ $.TOTPCommand }}") case "$len" in 3) - compadd "$@" {{ $.TOTPListCommand }}{{ range $key, $value := .TOTPSubCommands }} {{ $value }}{{ end }} + compadd "$@" {{ $.TOTPListCommand }} +{{- range $key, $value := .TOTPSubCommands }} + if {{ $value.Conditional }}; then + compadd "$@" {{ $value.Key }} + fi +{{ end }} ;; -{{- if $profile.CanList }} 4) case $words[3] in {{- range $key, $value := .TOTPSubCommands }} - "{{ $value }}") - compadd "$@" $({{ $.DoTOTPList }}) + "{{ $value.Key }}") + if {{ $value.Conditional }}; then + compadd "$@" $({{ $.DoTOTPList }}) + fi ;; {{- end}} esac -{{- end}} esac ;; -{{- end}} -{{- if $profile.CanList }} - "{{ $.ShowCommand }}" | "{{ $.JSONCommand }}"{{ if $profile.CanClip }} | "{{ $.ClipCommand }}" {{end}}) + "{{ $.ShowCommand }}" | "{{ $.JSONCommand }}" | "{{ $.ClipCommand }}") if [ "$len" -eq 3 ]; then compadd "$@" $({{ $.DoList }}) fi ;; -{{- end}} esac esac } -{{- end}} -compdef _lb lb +compdef _{{ $.Executable }} {{ $.Executable }} diff --git a/internal/config/core.go b/internal/config/core.go @@ -93,17 +93,6 @@ type ( Start int End int } - // CompletionProfile are shell completion definitions with backing environment information - CompletionProfile struct { - Clip bool - TOTP bool - List bool - Write bool - Name string - Env []string - Default bool - Generate bool - } // ReKeyArgs are the arguments for rekeying ReKeyArgs struct { NoKey bool @@ -140,13 +129,13 @@ func environOrDefault(envKey, defaultValue string) string { return val } -func (e environmentBase) key() string { +func (e environmentBase) Key() string { return fmt.Sprintf("LOCKBOX_%s%s", string(e.cat), e.subKey) } // Get will get the boolean value for the setting func (e EnvironmentBool) Get() (bool, error) { - read := strings.ToLower(strings.TrimSpace(getExpand(e.key()))) + read := strings.ToLower(strings.TrimSpace(getExpand(e.Key()))) switch read { case no: return false, nil @@ -156,13 +145,13 @@ func (e EnvironmentBool) Get() (bool, error) { return e.defaultValue, nil } - return false, fmt.Errorf("invalid yes/no env value for %s", e.key()) + return false, fmt.Errorf("invalid yes/no env value for %s", e.Key()) } // Get will get the integer value for the setting func (e EnvironmentInt) Get() (int, error) { val := e.defaultValue - use := getExpand(e.key()) + use := getExpand(e.Key()) if use != "" { i, err := strconv.Atoi(use) if err != nil { @@ -190,14 +179,14 @@ func (e EnvironmentInt) Get() (int, error) { // Get will read the string from the environment func (e EnvironmentString) Get() string { if !e.canDefault { - return getExpand(e.key()) + return getExpand(e.Key()) } - return environOrDefault(e.key(), e.defaultValue) + return environOrDefault(e.Key(), e.defaultValue) } // Get will read (and shlex) the value if set func (e EnvironmentCommand) Get() ([]string, error) { - value := environOrDefault(e.key(), "") + value := environOrDefault(e.Key(), "") if strings.TrimSpace(value) == "" { return nil, nil } @@ -206,24 +195,24 @@ func (e EnvironmentCommand) Get() ([]string, error) { // KeyValue will get the string representation of the key+value func (e environmentBase) KeyValue(value string) string { - return fmt.Sprintf("%s=%s", e.key(), value) + return fmt.Sprintf("%s=%s", e.Key(), value) } // Setenv will do an environment set for the value to key func (e environmentBase) Set(value string) error { - unset, err := IsUnset(e.key(), value) + unset, err := IsUnset(e.Key(), value) if err != nil { return err } if unset { return nil } - return os.Setenv(e.key(), value) + return os.Setenv(e.Key(), value) } // Get will retrieve the value with the formatted input included func (e EnvironmentFormatter) Get(value string) string { - return e.fxn(e.key(), value) + return e.fxn(e.Key(), value) } func (e EnvironmentString) values() (string, []string) { @@ -357,8 +346,8 @@ func Environ() []string { var results []string for _, k := range os.Environ() { for _, r := range registeredEnv { - key := r.self().key() - if key == EnvConfig.key() { + key := r.self().Key() + if key == EnvConfig.Key() { continue } key = fmt.Sprintf("%s=", key) @@ -382,7 +371,7 @@ func ExpandParsed(inputs map[string]string) (map[string]string, error) { } var err error var cycles int - possibleCycles, ok := inputs[envConfigExpands.key()] + possibleCycles, ok := inputs[envConfigExpands.Key()] if ok { cycles, err = strconv.Atoi(possibleCycles) } else { diff --git a/internal/config/core_test.go b/internal/config/core_test.go @@ -52,6 +52,13 @@ func TestKeyValue(t *testing.T) { } } +func TestKey(t *testing.T) { + val := config.EnvStore.Key() + if val != "LOCKBOX_STORE" { + t.Errorf("invalid key") + } +} + func TestNewPlatform(t *testing.T) { for _, item := range config.Platforms.List() { os.Setenv("LOCKBOX_PLATFORM", item) diff --git a/internal/config/vars.go b/internal/config/vars.go @@ -17,11 +17,6 @@ const ( fileExample = "<file>" detectedValue = "<detected>" requiredKeyOrKeyFile = "a key, a key file, or both must be set" - askProfile = "ask" - roProfile = "readonly" - noTOTPProfile = "nototp" - noClipProfile = "noclip" - noGenProfile = "nopwgen" // ModTimeFormat is the expected modtime format ModTimeFormat = time.RFC3339 ) @@ -125,7 +120,7 @@ var ( }), }) // EnvDefaultCompletionKey is the key for default completion handling - EnvDefaultCompletionKey = EnvDefaultCompletion.key() + EnvDefaultCompletionKey = EnvDefaultCompletion.Key() // EnvNoColor indicates if color outputs are disabled EnvNoColor = environmentRegister( EnvironmentBool{ @@ -435,7 +430,7 @@ func ListEnvironmentVariables() []string { if r != "" { requirement = r } - text := fmt.Sprintf("\n%s\n%s requirement: %s\n default: %s\n options: %s\n", env.key(), description, requirement, value, strings.Join(allow, "|")) + text := fmt.Sprintf("\n%s\n%s requirement: %s\n default: %s\n options: %s\n", env.Key(), description, requirement, value, strings.Join(allow, "|")) results = append(results, text) } sort.Strings(results) @@ -479,83 +474,6 @@ func ParseJSONOutput() (JSONOutputMode, error) { return JSONOutputs.Blank, fmt.Errorf("invalid JSON output mode: %s", val) } -func exportProfileKeyValue(e environmentBase, val string) string { - return fmt.Sprintf("\"$%s\" = \"%s\"", e.key(), val) -} - -func newProfile(keys []string) CompletionProfile { - p := CompletionProfile{} - p.Clip = true - p.List = true - p.TOTP = true - p.Write = true - p.Generate = true - name := "" - sort.Strings(keys) - var e []string - for _, k := range keys { - name = fmt.Sprintf("%s%s-", name, k) - switch k { - case askProfile: - e = append(e, exportProfileKeyValue(envKeyMode.environmentBase, string(askKeyMode))) - p.List = false - case noTOTPProfile: - e = append(e, exportProfileKeyValue(EnvNoTOTP.environmentBase, yes)) - p.TOTP = false - case noClipProfile: - e = append(e, exportProfileKeyValue(EnvNoClip.environmentBase, yes)) - p.Clip = false - case roProfile: - e = append(e, exportProfileKeyValue(EnvReadOnly.environmentBase, yes)) - p.Write = false - case noGenProfile: - e = append(e, exportProfileKeyValue(EnvNoPasswordGen.environmentBase, yes)) - p.Generate = false - } - } - sort.Strings(e) - p.Env = e - p.Name = strings.TrimSuffix(name, "-") - return p -} - -func generateProfiles(keys []string) map[string]CompletionProfile { - m := make(map[string]CompletionProfile) - if len(keys) == 0 { - return m - } - p := newProfile(keys) - m[p.Name] = p - for _, cur := range keys { - var subset []string - for _, key := range keys { - if key == cur { - continue - } - subset = append(subset, key) - } - - for _, p := range generateProfiles(subset) { - m[p.Name] = p - } - } - return m -} - -// LoadCompletionProfiles will generate known completion profile with backing env information -func LoadCompletionProfiles() []CompletionProfile { - loaded := generateProfiles([]string{noClipProfile, roProfile, noTOTPProfile, askProfile, noGenProfile}) - var profiles []CompletionProfile - for _, v := range loaded { - profiles = append(profiles, v) - } - sort.Slice(profiles, func(i, j int) bool { - return strings.Compare(profiles[i].Name, profiles[j].Name) < 0 - }) - profiles = append(profiles, CompletionProfile{Generate: true, Clip: true, Write: true, TOTP: true, List: true, Default: true}) - return profiles -} - // CanColor indicates if colorized output is allowed (or disabled) func CanColor() (bool, error) { if _, noColor := os.LookupEnv("NO_COLOR"); noColor { diff --git a/internal/config/vars_test.go b/internal/config/vars_test.go @@ -269,28 +269,6 @@ func TestEnvironDefinitions(t *testing.T) { } } -func TestLoadCompletionProfiles(t *testing.T) { - p := config.LoadCompletionProfiles() - if len(p) != 32 { - t.Errorf("invalid completion count: %d", len(p)) - } - exp := len(p) - 1 - for idx, prof := range p { - if prof.Default { - if idx != exp { - t.Error("profile defaulted incorrectly") - } - if prof.Name != "" { - t.Error("default profile is unnamed") - } - } else { - if len(prof.Env) == 0 { - t.Error("profile has no environment information") - } - } - } -} - func TestCanColor(t *testing.T) { defer os.Clearenv() os.Clearenv()