mirror of
https://github.com/gopasspw/gopass.git
synced 2025-12-08 19:24:54 +00:00
* [chore] Migrate to golangci-lint v2 Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * [chore] Fix more lint issues Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * [chore] Fix more lint issue Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * [chore] Fix more lint issues Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * [chore] Add more package comments. Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * [chore] Fix golangci-lint config and the remaining checks Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * [fix] Use Go 1.24 Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * [fix] Fix container builds Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * Fix more failing tests Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * Fix test failure Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * Fix another len assertion Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * Move location tests Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * [fix] Fix most remaining lint issues Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * [fix] Only run XDG specific tests on linux Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * [fix] Attempt to address on source of flaky failures Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> --------- Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
224 lines
5.2 KiB
Go
224 lines
5.2 KiB
Go
package action
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/ergochat/readline"
|
|
"github.com/gopasspw/gopass/internal/out"
|
|
"github.com/gopasspw/gopass/internal/tree"
|
|
"github.com/gopasspw/gopass/pkg/debug"
|
|
shellquote "github.com/kballard/go-shellquote"
|
|
"github.com/urfave/cli/v2"
|
|
)
|
|
|
|
type mindFlagsCompleter struct {
|
|
base readline.AutoCompleter
|
|
}
|
|
|
|
func (f *mindFlagsCompleter) Do(line []rune, pos int) ([][]rune, int) {
|
|
// Get the portion of the line up to the cursor and split to tokens
|
|
origInput := string(line[:pos])
|
|
tokens := strings.Fields(origInput)
|
|
filtered := make([]string, 0, len(tokens))
|
|
for _, tok := range tokens {
|
|
// Skip flag tokens
|
|
if strings.HasPrefix(tok, "-") {
|
|
continue
|
|
}
|
|
filtered = append(filtered, tok)
|
|
}
|
|
sanitized := strings.Join(filtered, " ")
|
|
// Add additional space if had one on original line
|
|
if len(origInput) > 0 && origInput[len(origInput)-1] == ' ' {
|
|
sanitized += " "
|
|
}
|
|
|
|
return f.base.Do([]rune(sanitized), len([]rune(sanitized)))
|
|
}
|
|
|
|
func (s *Action) entriesForCompleter(ctx context.Context) ([]*readline.PrefixCompleter, error) {
|
|
args := []*readline.PrefixCompleter{}
|
|
list, err := s.Store.List(ctx, tree.INF)
|
|
if err != nil {
|
|
return args, err
|
|
}
|
|
for _, v := range list {
|
|
args = append(args, readline.PcItem(v))
|
|
}
|
|
|
|
return args, nil
|
|
}
|
|
|
|
func (s *Action) replCompleteRecipients(ctx context.Context, cmd *cli.Command) []*readline.PrefixCompleter {
|
|
subCmds := []*readline.PrefixCompleter{}
|
|
if cmd.Name == "remove" {
|
|
for _, r := range s.recipientsList(ctx) {
|
|
subCmds = append(subCmds, readline.PcItem(r))
|
|
}
|
|
}
|
|
args := []*readline.PrefixCompleter{}
|
|
args = append(args, readline.PcItem(cmd.Name, subCmds...))
|
|
for _, alias := range cmd.Aliases {
|
|
args = append(args, readline.PcItem(alias, subCmds...))
|
|
}
|
|
|
|
return args
|
|
}
|
|
|
|
func (s *Action) replCompleteTemplates(ctx context.Context, cmd *cli.Command) []*readline.PrefixCompleter {
|
|
subCmds := []*readline.PrefixCompleter{}
|
|
for _, r := range s.templatesList(ctx) {
|
|
subCmds = append(subCmds, readline.PcItem(r))
|
|
}
|
|
args := []*readline.PrefixCompleter{}
|
|
args = append(args, readline.PcItem(cmd.Name, subCmds...))
|
|
for _, alias := range cmd.Aliases {
|
|
args = append(args, readline.PcItem(alias, subCmds...))
|
|
}
|
|
|
|
return args
|
|
}
|
|
|
|
func (s *Action) prefixCompleter(c *cli.Context) *readline.PrefixCompleter {
|
|
secrets, err := s.entriesForCompleter(c.Context)
|
|
if err != nil {
|
|
debug.Log("failed to list secrets: %s", err)
|
|
}
|
|
cmds := []*readline.PrefixCompleter{}
|
|
for _, cmd := range c.App.Commands {
|
|
if cmd.Hidden {
|
|
continue
|
|
}
|
|
subCmds := []*readline.PrefixCompleter{}
|
|
switch cmd.Name {
|
|
case "config":
|
|
for _, k := range s.configKeys() {
|
|
subCmds = append(subCmds, readline.PcItem(k))
|
|
}
|
|
case "recipients":
|
|
subCmds = append(subCmds, s.replCompleteRecipients(c.Context, cmd)...)
|
|
case "templates":
|
|
subCmds = append(subCmds, s.replCompleteTemplates(c.Context, cmd)...)
|
|
case "cat":
|
|
fallthrough
|
|
case "delete":
|
|
fallthrough
|
|
case "edit":
|
|
fallthrough
|
|
case "generate":
|
|
fallthrough
|
|
case "history":
|
|
fallthrough
|
|
case "list":
|
|
fallthrough
|
|
case "move":
|
|
fallthrough
|
|
case "otp":
|
|
fallthrough
|
|
case "show":
|
|
subCmds = append(subCmds, secrets...)
|
|
default:
|
|
}
|
|
for _, scmd := range cmd.Subcommands {
|
|
subCmds = append(subCmds, readline.PcItem(scmd.Name))
|
|
}
|
|
cmds = append(cmds, readline.PcItem(cmd.Name, subCmds...))
|
|
for _, alias := range cmd.Aliases {
|
|
cmds = append(cmds, readline.PcItem(alias, subCmds...))
|
|
}
|
|
}
|
|
|
|
return readline.NewPrefixCompleter(cmds...)
|
|
}
|
|
|
|
// REPL implements a read-execute-print-line shell
|
|
// with readline support and autocompletion.
|
|
func (s *Action) REPL(c *cli.Context) error {
|
|
c.App.ExitErrHandler = func(c *cli.Context, err error) {
|
|
if err == nil {
|
|
return
|
|
}
|
|
out.Errorf(c.Context, "%s", err)
|
|
}
|
|
|
|
out.Printf(c.Context, logo)
|
|
out.Printf(c.Context, "🌟 Welcome to gopass!")
|
|
out.Printf(c.Context, "⚠ This is the built-in shell. Type 'help' for a list of commands.")
|
|
|
|
rl, err := readline.New("gopass> ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer func() {
|
|
_ = rl.Close()
|
|
}()
|
|
|
|
READ:
|
|
for {
|
|
// check for context cancellation
|
|
select {
|
|
case <-c.Done():
|
|
return fmt.Errorf("user aborted")
|
|
default:
|
|
}
|
|
|
|
// we need to update the completer on every loop since
|
|
// the list of secrets may have changed, e.g. due to
|
|
// the user adding a new secret.
|
|
cfg := rl.GetConfig()
|
|
cfg.AutoComplete = &mindFlagsCompleter{base: s.prefixCompleter(c)}
|
|
if err := rl.SetConfig(cfg); err != nil {
|
|
debug.Log("Failed to set readline config: %s", err)
|
|
|
|
break
|
|
}
|
|
|
|
line, err := rl.Readline()
|
|
if err != nil {
|
|
debug.Log("Readline error: %s", err)
|
|
|
|
break
|
|
}
|
|
args, err := shellquote.Split(line)
|
|
if err != nil {
|
|
out.Printf(c.Context, "Error: %s", err)
|
|
|
|
continue
|
|
}
|
|
if len(args) < 1 {
|
|
continue
|
|
}
|
|
switch strings.ToLower(args[0]) {
|
|
case "quit":
|
|
break READ
|
|
case "lock":
|
|
s.replLock(c.Context)
|
|
|
|
continue
|
|
case "clear":
|
|
rl.ClearScreen()
|
|
|
|
continue
|
|
default:
|
|
}
|
|
|
|
if err := c.App.RunContext(c.Context, append([]string{"gopass"}, args...)); err != nil {
|
|
continue
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Action) replLock(ctx context.Context) {
|
|
if err := s.Store.Lock(); err != nil {
|
|
out.Errorf(ctx, "Failed to lock stores: %s", err)
|
|
|
|
return
|
|
}
|
|
out.OKf(ctx, "Locked")
|
|
}
|