gopass/internal/tree/root.go
Dominik Schulz 7c5db4d144
[feat] Add verbosity levels to the debug package (#2851)
* [feat] Add verbosity levels to the debug package

Use debug.V(N).Log instead of debug.Log to indicate message
verbosity (higher numbers indicate more verbose messages).

Use GOPASS_DEBUG_VERBOSE=N to control the desired level
of verbosity in the log output.

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

* Document the verbosity env vars.

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

* Allow negative verbosity values

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

---------

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
2024-03-29 19:18:36 +01:00

183 lines
4.1 KiB
Go

package tree
import (
"fmt"
"path/filepath"
"strings"
"github.com/fatih/color"
"github.com/gopasspw/gopass/pkg/debug"
)
const (
symEmpty = " "
symBranch = "├── "
symLeaf = "└── "
symVert = "│ "
)
var (
// ErrNotFound is returned when a node is not found.
ErrNotFound = fmt.Errorf("not found")
colMount = color.New(color.FgCyan, color.Bold).SprintfFunc()
colDir = color.New(color.FgBlue, color.Bold).SprintfFunc()
colTpl = color.New(color.FgGreen, color.Bold).SprintfFunc()
colShadow = color.New(color.FgRed, color.Bold).SprintfFunc()
// sep is intentionally NOT platform-agnostic. This is used for the CLI output
// and should always be a regular slash.
sep = "/"
)
// Root is the root of a tree. It contains a name and a subtree.
type Root struct {
Name string
Subtree *Tree
Prefix string
}
// New creates a new tree.
func New(name string) *Root {
return &Root{
Name: name,
Subtree: NewTree(),
}
}
// AddFile adds a new file to the tree.
func (r *Root) AddFile(path string, _ string) error {
return r.insert(path, false, "")
}
// AddMount adds a new mount point to the tree.
func (r *Root) AddMount(path, dest string) error {
return r.insert(path, false, dest)
}
// AddTemplate adds a template to the tree.
func (r *Root) AddTemplate(path string) error {
return r.insert(path, true, "")
}
func (r *Root) insert(path string, template bool, mountPath string) error {
t := r.Subtree
debug.V(4).Log("adding: %s [tpl: %t, mp: %q]", path, template, mountPath)
// split the path into its components, iterate over them and create
// the tree structure. Everything but the last element is a folder.
p := strings.Split(path, "/")
for i, e := range p {
n := &Node{
Name: e,
Subtree: NewTree(),
}
// this is the final element (a leaf)
if i == len(p)-1 {
n.Leaf = true
n.Subtree = nil
n.Template = template
if mountPath != "" {
n.Mount = true
n.Path = mountPath
}
}
debug.V(4).Log("[%d] %s -> Node: %+v", i, e, n)
node := t.Insert(n)
debug.V(4).Log("node after insert: %+v", node)
// do we need to extend an existing subtree?
if i < len(p)-1 && node.Subtree == nil {
node.Subtree = NewTree()
}
// re-root t to the new subtree
t = node.Subtree
}
return nil
}
// Format returns a pretty printed string of all nodes in and below
// this node, e.g. `├── baz`.
func (r *Root) Format(maxDepth int) string {
var sb strings.Builder
// any mount will be colored and include the on-disk path
_, _ = sb.WriteString(colDir(r.Name))
// finish this folders output
_, _ = sb.WriteString("\n")
// let our children format themselves
for i, node := range r.Subtree.Nodes {
last := i == len(r.Subtree.Nodes)-1
_, _ = sb.WriteString(node.format("", last, maxDepth, 1))
}
return sb.String()
}
// List returns a flat list of all files in this tree.
func (r *Root) List(maxDepth int) []string {
out := make([]string, 0, r.Len())
for _, t := range r.Subtree.Nodes {
out = append(out, t.list(r.Prefix, maxDepth, 0, true)...)
}
return out
}
// ListFolders returns a flat list of all folders in this tree.
func (r *Root) ListFolders(maxDepth int) []string {
out := make([]string, 0, r.Len())
for _, t := range r.Subtree.Nodes {
out = append(out, t.list(r.Prefix, maxDepth, 0, false)...)
}
return out
}
// String returns the name of this tree.
func (r *Root) String() string {
return r.Name
}
// FindFolder returns the subtree rooted at path.
func (r *Root) FindFolder(path string) (*Root, error) {
path = strings.TrimSuffix(path, "/")
t := r.Subtree
p := strings.Split(path, "/")
prefix := ""
for _, e := range p {
_, node := t.findPositionFor(e)
if node == nil || node.Subtree == nil {
return nil, ErrNotFound
}
t = node.Subtree
prefix = filepath.Join(prefix, e)
}
return &Root{Name: r.Name, Subtree: t, Prefix: prefix}, nil
}
// SetName changes the name of this tree.
func (r *Root) SetName(n string) {
r.Name = n
}
// Len returns the number of entries in this folder and all subfolder including
// this folder itself.
func (r *Root) Len() int {
var l int
for _, t := range r.Subtree.Nodes {
l += t.Len()
}
return l
}