mirror of
https://github.com/gopasspw/gopass.git
synced 2026-10-01 11:57:06 +02:00
This commit adds yet another config handler for gopass. It is based on the format used by git itself. This has the potential to address a lot of long standing issues, but it also causes a lot of changes to how we handle configuration, so bugs are inevitable. Fixes #1567 Fixes #1764 Fixes #1819 Fixes #1878 Fixes #2387 RELEASE_NOTES=[BREAKING] New config format based on git config. Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> Co-authored-by: Yolan Romailler <AnomalRoil@users.noreply.github.com>
188 lines
4.3 KiB
Go
188 lines
4.3 KiB
Go
package action
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/chzyer/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"
|
|
)
|
|
|
|
func (s *Action) entriesForCompleter(ctx context.Context) ([]readline.PrefixCompleterInterface, error) {
|
|
args := []readline.PrefixCompleterInterface{}
|
|
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.PrefixCompleterInterface {
|
|
subCmds := []readline.PrefixCompleterInterface{}
|
|
if cmd.Name == "remove" {
|
|
for _, r := range s.recipientsList(ctx) {
|
|
subCmds = append(subCmds, readline.PcItem(r))
|
|
}
|
|
}
|
|
args := []readline.PrefixCompleterInterface{}
|
|
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.PrefixCompleterInterface {
|
|
subCmds := []readline.PrefixCompleterInterface{}
|
|
for _, r := range s.templatesList(ctx) {
|
|
subCmds = append(subCmds, readline.PcItem(r))
|
|
}
|
|
args := []readline.PrefixCompleterInterface{}
|
|
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.PrefixCompleterInterface{}
|
|
for _, cmd := range c.App.Commands {
|
|
if cmd.Hidden {
|
|
continue
|
|
}
|
|
subCmds := []readline.PrefixCompleterInterface{}
|
|
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 cancelation
|
|
select {
|
|
case <-c.Context.Done():
|
|
return fmt.Errorf("user aborted")
|
|
default:
|
|
}
|
|
rl.Config.AutoComplete = s.prefixCompleter(c)
|
|
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":
|
|
readline.ClearScreen(stdout) //nolint:errcheck
|
|
|
|
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")
|
|
}
|