mirror of
https://github.com/gopasspw/gopass.git
synced 2025-12-08 19:24:54 +00:00
* Add ov pager RELEASE_NOTES=[ENHANCEMENT] Add internal pager (ov). Fixes #2507 Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * Document output.internal-pager. Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * Fix default value. Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * Update license check Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> --------- Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
160 lines
3.7 KiB
Go
160 lines
3.7 KiB
Go
package action
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
"github.com/fatih/color"
|
|
"github.com/gopasspw/gopass/internal/action/exit"
|
|
"github.com/gopasspw/gopass/internal/config"
|
|
"github.com/gopasspw/gopass/internal/store/leaf"
|
|
"github.com/gopasspw/gopass/internal/tree"
|
|
"github.com/gopasspw/gopass/pkg/ctxutil"
|
|
shellquote "github.com/kballard/go-shellquote"
|
|
"github.com/noborus/ov/oviewer"
|
|
"github.com/urfave/cli/v2"
|
|
"golang.org/x/term"
|
|
)
|
|
|
|
// List all secrets as a tree. If the filter argument is non-empty
|
|
// display only those that have this prefix.
|
|
func (s *Action) List(c *cli.Context) error {
|
|
ctx := ctxutil.WithGlobalFlags(c)
|
|
filter := c.Args().First()
|
|
flat := c.Bool("flat")
|
|
stripPrefix := c.Bool("strip-prefix")
|
|
folders := c.Bool("folders")
|
|
|
|
// print the path if the argument is a direct hit.
|
|
if s.Store.Exists(ctx, filter) && !s.Store.IsDir(ctx, filter) {
|
|
fmt.Println(filter)
|
|
|
|
return nil
|
|
}
|
|
|
|
// we only support listing folders in flat mode currently.
|
|
if folders {
|
|
flat = true
|
|
}
|
|
|
|
l, err := s.Store.Tree(ctx)
|
|
if err != nil {
|
|
return exit.Error(exit.List, err, "failed to list store: %s", err)
|
|
}
|
|
|
|
// set limit to infinite by default unless it's set with the flag
|
|
limit := tree.INF
|
|
if c.IsSet("limit") {
|
|
limit = c.Int("limit")
|
|
}
|
|
|
|
return s.listFiltered(ctx, l, limit, flat, folders, stripPrefix, filter)
|
|
}
|
|
|
|
func (s *Action) listFiltered(ctx context.Context, l *tree.Root, limit int, flat, folders, stripPrefix bool, filter string) error {
|
|
sep := leaf.Sep
|
|
|
|
if filter == "" || filter == sep {
|
|
// We list all entries then.
|
|
stripPrefix = true
|
|
} else {
|
|
// If the filter ends with a separator, we still want to find the content of that folder.
|
|
if strings.HasSuffix(filter, sep) {
|
|
filter = filter[:len(filter)-1]
|
|
}
|
|
// To avoid shadowing l since we need it outside of the else scope.
|
|
var err error
|
|
// We restrict ourselves to the filter.
|
|
l, err = l.FindFolder(filter)
|
|
if err != nil {
|
|
return exit.Error(exit.NotFound, nil, "Entry %q not found", filter)
|
|
}
|
|
l.SetName(filter + sep)
|
|
}
|
|
|
|
if flat {
|
|
listOver := l.List
|
|
if folders {
|
|
listOver = l.ListFolders
|
|
}
|
|
for _, e := range listOver(limit) {
|
|
if stripPrefix {
|
|
e = strings.TrimPrefix(e, filter+sep)
|
|
}
|
|
fmt.Fprintln(stdout, e)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// we may need to redirect stdout for the pager support.
|
|
so, buf := redirectPager(ctx, l)
|
|
|
|
fmt.Fprintln(so, l.Format(limit))
|
|
if buf != nil {
|
|
if err := s.pager(ctx, buf); err != nil {
|
|
return exit.Error(exit.Unknown, err, "failed to invoke pager: %s", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// redirectPager returns a redirected io.Writer if the output would exceed
|
|
// the terminal size.
|
|
func redirectPager(ctx context.Context, subtree *tree.Root) (io.Writer, *bytes.Buffer) {
|
|
if config.Bool(ctx, "core.nopager") {
|
|
return stdout, nil
|
|
}
|
|
_, rows, err := term.GetSize(0)
|
|
if err != nil {
|
|
return stdout, nil
|
|
}
|
|
if subtree == nil || subtree.Len() < rows {
|
|
return stdout, nil
|
|
}
|
|
if pager := os.Getenv("PAGER"); pager == "" {
|
|
return stdout, nil
|
|
}
|
|
color.NoColor = true
|
|
buf := &bytes.Buffer{}
|
|
|
|
return buf, buf
|
|
}
|
|
|
|
// pager invokes the default pager with the given content.
|
|
func (s *Action) pager(ctx context.Context, buf io.Reader) error {
|
|
if config.Bool(ctx, "output.internal-pager") {
|
|
ov, err := oviewer.NewRoot(buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return ov.Run()
|
|
}
|
|
|
|
pager := os.Getenv("PAGER")
|
|
if pager == "" {
|
|
fmt.Fprintln(stdout, buf)
|
|
|
|
return nil
|
|
}
|
|
|
|
args, err := shellquote.Split(pager)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to split pager command: %w", err)
|
|
}
|
|
|
|
cmd := exec.CommandContext(ctx, args[0], args[1:]...)
|
|
cmd.Stdin = buf
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
return cmd.Run()
|
|
}
|