commit af6dc8e4f4c9860d6245875a43dfc9bd0f9c74e2
parent 7364430a241411ff44467bd90c869ae86a2639b3
Author: Sean Enck <sean@ttypty.com>
Date: Thu, 2 Mar 2023 18:13:51 -0500
move app code
Diffstat:
6 files changed, 369 insertions(+), 366 deletions(-)
diff --git a/Makefile b/Makefile
@@ -7,7 +7,7 @@ all: $(TARGET)
build: $(TARGET)
$(TARGET): cmd/main.go internal/**/*.go go.* internal/cli/completions*
- ./scripts/version/configure cmd/vers.txt
+ ./scripts/version/configure internal/app/vers.txt
go build $(GOFLAGS) -o $@ cmd/main.go
unittest:
diff --git a/cmd/main.go b/cmd/main.go
@@ -2,367 +2,12 @@
package main
import (
- _ "embed"
- "errors"
- "fmt"
- "os"
- "os/exec"
- "strings"
- "time"
-
- "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/totp"
+ "github.com/enckse/lockbox/internal/app"
"github.com/enckse/lockbox/internal/util"
)
-//go:embed "vers.txt"
-var version string
-
-type (
- callbackFunction func([]string) error
-)
-
-func internalCallback(name string) callbackFunction {
- switch name {
- case cli.TOTPCommand:
- return totp.Call
- case cli.HashCommand:
- return hashText
- case cli.ClearCommand:
- return clearClipboard
- }
- return nil
-}
-
func main() {
- if err := run(); err != nil {
- util.Fatal(err)
- }
-}
-
-func getInfoDefault(args []string, possibleArg string) (bool, error) {
- defaults := false
- invalid := false
- switch len(args) {
- case 2:
- break
- case 3:
- if args[2] == possibleArg {
- defaults = true
- } else {
- invalid = true
- }
- default:
- invalid = true
- }
- if invalid {
- return false, errors.New("invalid argument")
- }
- return defaults, nil
-}
-
-func processInfoCommands(command string, args []string) ([]string, error) {
- switch command {
- case cli.HelpCommand:
- if len(args) > 3 {
- return nil, errors.New("invalid help command")
- }
- isAdvanced := false
- if len(args) == 3 {
- if args[2] == cli.HelpAdvancedCommand {
- isAdvanced = true
- } else {
- return nil, errors.New("invalid help option")
- }
- }
- results, err := cli.Usage(isAdvanced)
- if err != nil {
- return nil, err
- }
- return results, nil
- case cli.VersionCommand:
- return []string{fmt.Sprintf("version: %s", version)}, nil
- case cli.EnvCommand, cli.BashCommand:
- defaultFlag := cli.BashDefaultsCommand
- isEnv := command == cli.EnvCommand
- if isEnv {
- defaultFlag = cli.EnvDefaultsCommand
- }
- defaults, err := getInfoDefault(args, defaultFlag)
- if err != nil {
- return nil, err
- }
- if isEnv {
- return inputs.ListEnvironmentVariables(!defaults), nil
- }
- return cli.BashCompletions(defaults)
- }
- return nil, nil
-}
-
-func wrapped(message string, err error) error {
- return fmt.Errorf("%s (%w)", message, err)
-}
-
-func run() error {
- args := os.Args
- if len(args) < 2 {
- return errors.New("requires subcommand")
- }
- command := args[1]
- info, err := processInfoCommands(command, args)
- if err != nil {
- return err
- }
- if info != nil {
- fmt.Println(strings.Join(info, "\n"))
- return nil
- }
- t, err := backend.NewTransaction()
- if err != nil {
- return wrapped("unable to build transaction model", err)
- }
- switch command {
- case cli.ReKeyCommand:
- if confirm("proceed with rekey") {
- if err := t.ReKey(); err != nil {
- return wrapped("unable to rekey", err)
- }
- }
- case cli.ListCommand, cli.FindCommand:
- opts := backend.QueryOptions{}
- opts.Mode = backend.ListMode
- if command == cli.FindCommand {
- opts.Mode = backend.FindMode
- if len(args) < 3 {
- return errors.New("find requires search term")
- }
- opts.Criteria = args[2]
- }
- e, err := t.QueryCallback(opts)
- if err != nil {
- return wrapped("unable to list files", err)
- }
- for _, f := range e {
- fmt.Println(f.Path)
- }
- case cli.MoveCommand:
- if len(args) != 4 {
- return errors.New("src/dst required for move")
- }
- src := args[2]
- dst := args[3]
- srcExists, err := t.Get(src, backend.SecretValue)
- if err != nil {
- return errors.New("unable to get source entry")
- }
- if srcExists == nil {
- return errors.New("no source object found")
- }
- dstExists, err := t.Get(dst, backend.BlankValue)
- if err != nil {
- return errors.New("unable to get destination object")
- }
- if dstExists != nil {
- if !confirm("overwrite destination") {
- return nil
- }
- }
- if err := t.Move(*srcExists, dst); err != nil {
- return wrapped("unable to move object", err)
- }
- case cli.InsertCommand:
- multi := false
- isTOTP := false
- idx := 2
- switch len(args) {
- case 2:
- return errors.New("insert requires an entry")
- case 3:
- case 4:
- opt := args[2]
- switch opt {
- case cli.InsertMultiCommand:
- multi = true
- case cli.InsertTOTPCommand:
- off, err := inputs.IsNoTOTP()
- if err != nil {
- return err
- }
- if off {
- return totp.ErrNoTOTP
- }
- isTOTP = true
- default:
- return errors.New("unknown argument")
- }
- multi = true
- idx = 3
- default:
- return errors.New("too many arguments")
- }
- isPipe := inputs.IsInputFromPipe()
- entry := args[idx]
- if isTOTP {
- totpToken := inputs.TOTPToken()
- if !strings.HasSuffix(entry, backend.NewSuffix(totpToken)) {
- entry = backend.NewPath(entry, totpToken)
- }
- }
- existing, err := t.Get(entry, backend.BlankValue)
- if err != nil {
- return wrapped("unable to check for existing entry", err)
- }
- if existing != nil {
- if !isPipe {
- if !confirm("overwrite existing") {
- return nil
- }
- }
- }
- password, err := inputs.GetUserInputPassword(isPipe, multi)
- if err != nil {
- return wrapped("invalid input", err)
- }
- p := strings.TrimSpace(string(password))
- if err := t.Insert(entry, p); err != nil {
- return wrapped("failed to insert", err)
- }
- if !isPipe {
- fmt.Println()
- }
- case cli.RemoveCommand:
- if len(args) != 3 {
- return errors.New("remove requires an entry")
- }
- deleting := args[2]
- postfixRemove := "y"
- existings, err := t.MatchPath(deleting)
- if err != nil {
- return wrapped("unable to get entry", err)
- }
-
- if len(existings) > 1 {
- postfixRemove = "ies"
- fmt.Println("selected entities:")
- for _, e := range existings {
- fmt.Printf(" %s\n", e.Path)
- }
- fmt.Println("")
- }
- if confirm(fmt.Sprintf("delete entr%s", postfixRemove)) {
- if err := t.RemoveAll(existings); err != nil {
- return wrapped("unable to remove entry", err)
- }
- }
- case cli.ShowCommand, cli.ClipCommand, cli.StatsCommand:
- if len(args) != 3 {
- return errors.New("entry required")
- }
- entry := args[2]
- if command == cli.StatsCommand {
- v, err := t.Get(entry, backend.StatsValue)
- if err != nil {
- return wrapped("unable to get stats", err)
- }
- if v != nil {
- fmt.Println(v.Value)
- }
- return nil
- }
- clipboard := platform.Clipboard{}
- isShow := command == cli.ShowCommand
- if !isShow {
- clipboard, err = platform.NewClipboard()
- if err != nil {
- return wrapped("unable to get clipboard", err)
- }
- }
- existing, err := t.Get(entry, backend.SecretValue)
- if err != nil {
- return wrapped("unable to get entry", err)
- }
- if existing == nil {
- return errors.New("entry not found")
- }
- if isShow {
- fmt.Println(existing.Value)
- return nil
- }
- if err := clipboard.CopyTo(existing.Value); err != nil {
- return wrapped("clipboard operation failed", err)
- }
- default:
- if len(args) < 2 {
- return errors.New("missing required arguments")
- }
- a := args[2:]
- callback := internalCallback(command)
- if callback != nil {
- if err := callback(a); err != nil {
- return wrapped(fmt.Sprintf("%s command failure", command), err)
- }
- return nil
- }
- return fmt.Errorf("unknown command: %s", command)
- }
- return nil
-}
-
-func hashText(args []string) error {
- if len(args) == 0 {
- return errors.New("hash requires a file")
- }
- t, err := backend.Load(args[len(args)-1])
- if err != nil {
- return err
- }
- e, err := t.QueryCallback(backend.QueryOptions{Mode: backend.ListMode, Values: backend.HashedValue})
- if err != nil {
- return err
- }
- for _, item := range e {
- fmt.Printf("%s:\n %s\n\n", item.Path, strings.ReplaceAll(item.Value, "\n", "\n "))
- }
- return nil
-}
-
-func clearClipboard(args []string) error {
- idx := 0
- val, err := inputs.Stdin(false)
- if err != nil {
- return err
- }
- clipboard, err := platform.NewClipboard()
- if err != nil {
- return err
- }
- pCmd, pArgs, valid := clipboard.Args(false)
- if !valid {
- return nil
- }
- val = strings.TrimSpace(val)
- for idx < clipboard.MaxTime {
- idx++
- time.Sleep(1 * time.Second)
- out, err := exec.Command(pCmd, pArgs...).Output()
- if err != nil {
- continue
- }
- if strings.TrimSpace(string(out)) != val {
- return nil
- }
- }
- return clipboard.CopyTo("")
-}
-
-func confirm(prompt string) bool {
- yesNo, err := inputs.ConfirmYesNoPrompt(prompt)
- if err != nil {
- util.Dief("failed to read stdin for confirmation: %v", err)
+ if err := app.Run(); err != nil {
+ util.Die(err)
}
- return yesNo
}
diff --git a/cmd/vers.txt b/cmd/vers.txt
@@ -1 +0,0 @@
-v23.03.00
-\ No newline at end of file
diff --git a/internal/app/core.go b/internal/app/core.go
@@ -0,0 +1,363 @@
+// Package app runs the commands/ui
+package app
+
+import (
+ _ "embed"
+ "errors"
+ "fmt"
+ "os"
+ "os/exec"
+ "strings"
+ "time"
+
+ "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/totp"
+ "github.com/enckse/lockbox/internal/util"
+)
+
+//go:embed "vers.txt"
+var version string
+
+type (
+ callbackFunction func([]string) error
+)
+
+func internalCallback(name string) callbackFunction {
+ switch name {
+ case cli.TOTPCommand:
+ return totp.Call
+ case cli.HashCommand:
+ return hashText
+ case cli.ClearCommand:
+ return clearClipboard
+ }
+ return nil
+}
+
+func getInfoDefault(args []string, possibleArg string) (bool, error) {
+ defaults := false
+ invalid := false
+ switch len(args) {
+ case 2:
+ break
+ case 3:
+ if args[2] == possibleArg {
+ defaults = true
+ } else {
+ invalid = true
+ }
+ default:
+ invalid = true
+ }
+ if invalid {
+ return false, errors.New("invalid argument")
+ }
+ return defaults, nil
+}
+
+func processInfoCommands(command string, args []string) ([]string, error) {
+ switch command {
+ case cli.HelpCommand:
+ if len(args) > 3 {
+ return nil, errors.New("invalid help command")
+ }
+ isAdvanced := false
+ if len(args) == 3 {
+ if args[2] == cli.HelpAdvancedCommand {
+ isAdvanced = true
+ } else {
+ return nil, errors.New("invalid help option")
+ }
+ }
+ results, err := cli.Usage(isAdvanced)
+ if err != nil {
+ return nil, err
+ }
+ return results, nil
+ case cli.VersionCommand:
+ return []string{fmt.Sprintf("version: %s", version)}, nil
+ case cli.EnvCommand, cli.BashCommand:
+ defaultFlag := cli.BashDefaultsCommand
+ isEnv := command == cli.EnvCommand
+ if isEnv {
+ defaultFlag = cli.EnvDefaultsCommand
+ }
+ defaults, err := getInfoDefault(args, defaultFlag)
+ if err != nil {
+ return nil, err
+ }
+ if isEnv {
+ return inputs.ListEnvironmentVariables(!defaults), nil
+ }
+ return cli.BashCompletions(defaults)
+ }
+ return nil, nil
+}
+
+func wrapped(message string, err error) error {
+ return fmt.Errorf("%s (%w)", message, err)
+}
+
+// Run invokes the app
+func Run() error {
+ args := os.Args
+ if len(args) < 2 {
+ return errors.New("requires subcommand")
+ }
+ command := args[1]
+ info, err := processInfoCommands(command, args)
+ if err != nil {
+ return err
+ }
+ if info != nil {
+ fmt.Println(strings.Join(info, "\n"))
+ return nil
+ }
+ t, err := backend.NewTransaction()
+ if err != nil {
+ return wrapped("unable to build transaction model", err)
+ }
+ switch command {
+ case cli.ReKeyCommand:
+ if confirm("proceed with rekey") {
+ if err := t.ReKey(); err != nil {
+ return wrapped("unable to rekey", err)
+ }
+ }
+ case cli.ListCommand, cli.FindCommand:
+ opts := backend.QueryOptions{}
+ opts.Mode = backend.ListMode
+ if command == cli.FindCommand {
+ opts.Mode = backend.FindMode
+ if len(args) < 3 {
+ return errors.New("find requires search term")
+ }
+ opts.Criteria = args[2]
+ }
+ e, err := t.QueryCallback(opts)
+ if err != nil {
+ return wrapped("unable to list files", err)
+ }
+ for _, f := range e {
+ fmt.Println(f.Path)
+ }
+ case cli.MoveCommand:
+ if len(args) != 4 {
+ return errors.New("src/dst required for move")
+ }
+ src := args[2]
+ dst := args[3]
+ srcExists, err := t.Get(src, backend.SecretValue)
+ if err != nil {
+ return errors.New("unable to get source entry")
+ }
+ if srcExists == nil {
+ return errors.New("no source object found")
+ }
+ dstExists, err := t.Get(dst, backend.BlankValue)
+ if err != nil {
+ return errors.New("unable to get destination object")
+ }
+ if dstExists != nil {
+ if !confirm("overwrite destination") {
+ return nil
+ }
+ }
+ if err := t.Move(*srcExists, dst); err != nil {
+ return wrapped("unable to move object", err)
+ }
+ case cli.InsertCommand:
+ multi := false
+ isTOTP := false
+ idx := 2
+ switch len(args) {
+ case 2:
+ return errors.New("insert requires an entry")
+ case 3:
+ case 4:
+ opt := args[2]
+ switch opt {
+ case cli.InsertMultiCommand:
+ multi = true
+ case cli.InsertTOTPCommand:
+ off, err := inputs.IsNoTOTP()
+ if err != nil {
+ return err
+ }
+ if off {
+ return totp.ErrNoTOTP
+ }
+ isTOTP = true
+ default:
+ return errors.New("unknown argument")
+ }
+ multi = true
+ idx = 3
+ default:
+ return errors.New("too many arguments")
+ }
+ isPipe := inputs.IsInputFromPipe()
+ entry := args[idx]
+ if isTOTP {
+ totpToken := inputs.TOTPToken()
+ if !strings.HasSuffix(entry, backend.NewSuffix(totpToken)) {
+ entry = backend.NewPath(entry, totpToken)
+ }
+ }
+ existing, err := t.Get(entry, backend.BlankValue)
+ if err != nil {
+ return wrapped("unable to check for existing entry", err)
+ }
+ if existing != nil {
+ if !isPipe {
+ if !confirm("overwrite existing") {
+ return nil
+ }
+ }
+ }
+ password, err := inputs.GetUserInputPassword(isPipe, multi)
+ if err != nil {
+ return wrapped("invalid input", err)
+ }
+ p := strings.TrimSpace(string(password))
+ if err := t.Insert(entry, p); err != nil {
+ return wrapped("failed to insert", err)
+ }
+ if !isPipe {
+ fmt.Println()
+ }
+ case cli.RemoveCommand:
+ if len(args) != 3 {
+ return errors.New("remove requires an entry")
+ }
+ deleting := args[2]
+ postfixRemove := "y"
+ existings, err := t.MatchPath(deleting)
+ if err != nil {
+ return wrapped("unable to get entry", err)
+ }
+
+ if len(existings) > 1 {
+ postfixRemove = "ies"
+ fmt.Println("selected entities:")
+ for _, e := range existings {
+ fmt.Printf(" %s\n", e.Path)
+ }
+ fmt.Println("")
+ }
+ if confirm(fmt.Sprintf("delete entr%s", postfixRemove)) {
+ if err := t.RemoveAll(existings); err != nil {
+ return wrapped("unable to remove entry", err)
+ }
+ }
+ case cli.ShowCommand, cli.ClipCommand, cli.StatsCommand:
+ if len(args) != 3 {
+ return errors.New("entry required")
+ }
+ entry := args[2]
+ if command == cli.StatsCommand {
+ v, err := t.Get(entry, backend.StatsValue)
+ if err != nil {
+ return wrapped("unable to get stats", err)
+ }
+ if v != nil {
+ fmt.Println(v.Value)
+ }
+ return nil
+ }
+ clipboard := platform.Clipboard{}
+ isShow := command == cli.ShowCommand
+ if !isShow {
+ clipboard, err = platform.NewClipboard()
+ if err != nil {
+ return wrapped("unable to get clipboard", err)
+ }
+ }
+ existing, err := t.Get(entry, backend.SecretValue)
+ if err != nil {
+ return wrapped("unable to get entry", err)
+ }
+ if existing == nil {
+ return errors.New("entry not found")
+ }
+ if isShow {
+ fmt.Println(existing.Value)
+ return nil
+ }
+ if err := clipboard.CopyTo(existing.Value); err != nil {
+ return wrapped("clipboard operation failed", err)
+ }
+ default:
+ if len(args) < 2 {
+ return errors.New("missing required arguments")
+ }
+ a := args[2:]
+ callback := internalCallback(command)
+ if callback != nil {
+ if err := callback(a); err != nil {
+ return wrapped(fmt.Sprintf("%s command failure", command), err)
+ }
+ return nil
+ }
+ return fmt.Errorf("unknown command: %s", command)
+ }
+ return nil
+}
+
+func hashText(args []string) error {
+ if len(args) == 0 {
+ return errors.New("hash requires a file")
+ }
+ t, err := backend.Load(args[len(args)-1])
+ if err != nil {
+ return err
+ }
+ e, err := t.QueryCallback(backend.QueryOptions{Mode: backend.ListMode, Values: backend.HashedValue})
+ if err != nil {
+ return err
+ }
+ for _, item := range e {
+ fmt.Printf("%s:\n %s\n\n", item.Path, strings.ReplaceAll(item.Value, "\n", "\n "))
+ }
+ return nil
+}
+
+func clearClipboard(args []string) error {
+ idx := 0
+ val, err := inputs.Stdin(false)
+ if err != nil {
+ return err
+ }
+ clipboard, err := platform.NewClipboard()
+ if err != nil {
+ return err
+ }
+ pCmd, pArgs, valid := clipboard.Args(false)
+ if !valid {
+ return nil
+ }
+ val = strings.TrimSpace(val)
+ for idx < clipboard.MaxTime {
+ idx++
+ time.Sleep(1 * time.Second)
+ out, err := exec.Command(pCmd, pArgs...).Output()
+ if err != nil {
+ continue
+ }
+ if strings.TrimSpace(string(out)) != val {
+ return nil
+ }
+ }
+ return clipboard.CopyTo("")
+}
+
+func confirm(prompt string) bool {
+ yesNo, err := inputs.ConfirmYesNoPrompt(prompt)
+ if err != nil {
+ util.Dief("failed to read stdin for confirmation: %v", err)
+ }
+ return yesNo
+}
diff --git a/internal/app/vers.txt b/internal/app/vers.txt
@@ -0,0 +1 @@
+v23.03.01
+\ No newline at end of file
diff --git a/internal/util/core.go b/internal/util/core.go
@@ -18,11 +18,6 @@ func PathExists(file string) bool {
return true
}
-// Fatal will call Die but without a message
-func Fatal(err error) {
- Die(err)
-}
-
// Dief will for a message and die
func Dief(format string, a ...any) {
Die(fmt.Sprintf(format, a...))