mirror of
https://github.com/gopasspw/gopass.git
synced 2025-12-08 19:24:54 +00:00
* [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>
236 lines
5.5 KiB
Go
236 lines
5.5 KiB
Go
package tree
|
|
|
|
import (
|
|
"bytes"
|
|
|
|
"github.com/gopasspw/gopass/pkg/debug"
|
|
)
|
|
|
|
// Node is a tree node.
|
|
type Node struct {
|
|
Name string
|
|
Leaf bool
|
|
Template bool
|
|
Mount bool
|
|
Path string
|
|
Subtree *Tree
|
|
}
|
|
|
|
const (
|
|
// INF allows to have a full recursion until the leaves of a tree.
|
|
INF = -1
|
|
)
|
|
|
|
// Nodes is a slice of nodes which can be sorted.
|
|
type Nodes []*Node
|
|
|
|
func (n Nodes) Len() int {
|
|
return len(n)
|
|
}
|
|
|
|
func (n Nodes) Less(i, j int) bool {
|
|
return n[i].Name < n[j].Name
|
|
}
|
|
|
|
func (n Nodes) Swap(i, j int) {
|
|
n[i], n[j] = n[j], n[i]
|
|
}
|
|
|
|
// Equals compares to another node.
|
|
func (n Node) Equals(other Node) bool {
|
|
if n.Name != other.Name {
|
|
return false
|
|
}
|
|
|
|
if n.Leaf != other.Leaf {
|
|
return false
|
|
}
|
|
|
|
if n.Subtree != nil {
|
|
if other.Subtree == nil {
|
|
return false
|
|
}
|
|
|
|
if !n.Subtree.Equals(other.Subtree) {
|
|
return false
|
|
}
|
|
} else if other.Subtree != nil {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Merge will merge two nodes into a new node. Does not change either of the two
|
|
// input nodes. The merged node will be in the returned node. Implements semantics
|
|
// specific to gopass' tree model, i.e. mounts shadow (erase) everything below
|
|
// a mount point, nodes within a tree can be leafs (i.e. contain a secret as well
|
|
// as subdirectories) and any node can also contain a template.
|
|
func (n Node) Merge(other Node) *Node {
|
|
r := Node{
|
|
Name: n.Name,
|
|
Leaf: n.Leaf,
|
|
Template: n.Template,
|
|
Mount: n.Mount,
|
|
Path: n.Path,
|
|
Subtree: n.Subtree,
|
|
}
|
|
|
|
// During a merge we can't change the name.
|
|
|
|
// If either of the nodes is a leaf (i.e. contains a secret) the
|
|
// merged node will be a leaf.
|
|
if other.Leaf {
|
|
r.Leaf = true
|
|
}
|
|
|
|
// If either node has a template the merged has a template, too.
|
|
if other.Template {
|
|
r.Template = true
|
|
}
|
|
|
|
// Handling of mounts is a bit more tricky. See the comment above.
|
|
// If we're adding a mount to the tree this shadows (erases) everything
|
|
// that was on this branch before a replaces it with the mount.
|
|
// Think of Unix mount semantics here.
|
|
if other.Mount {
|
|
r.Mount = true
|
|
// anything at the mount point, including a secret at the root
|
|
// of the mount point will become inaccessible.
|
|
r.Leaf = false
|
|
r.Path = other.Path
|
|
// existing templates will become invisible
|
|
r.Template = false
|
|
// the subtree from the mount overlays (shadows) the original tree
|
|
r.Subtree = other.Subtree
|
|
}
|
|
// Merging can't change the path (except a mount, see above)
|
|
// If the other node has a subtree we use that, otherwise
|
|
// this method shouldn't have been called in the first place.
|
|
if r.Subtree == nil && other.Subtree != nil {
|
|
r.Subtree = other.Subtree
|
|
}
|
|
|
|
debug.V(4).Log("merged %+v and %+v into %+v", n, other, r)
|
|
|
|
return &r
|
|
}
|
|
|
|
// format returns a pretty printed string of all nodes in and below
|
|
// this node, e.g. `├── baz`.
|
|
func (n *Node) format(prefix string, last bool, maxDepth, curDepth int) string {
|
|
if maxDepth > INF && (curDepth > maxDepth+1) {
|
|
return ""
|
|
}
|
|
|
|
out := bytes.NewBufferString(prefix)
|
|
// adding either an L or a T, depending if this is the last node
|
|
// or not
|
|
if last {
|
|
_, _ = out.WriteString(symLeaf)
|
|
} else {
|
|
_, _ = out.WriteString(symBranch)
|
|
}
|
|
// the next levels prefix needs to be extended depending if
|
|
// this is the last node in a group or not
|
|
if last {
|
|
prefix += symEmpty
|
|
} else {
|
|
prefix += symVert
|
|
}
|
|
|
|
// any mount will be colored and include the on-disk path
|
|
switch {
|
|
case n.Mount:
|
|
_, _ = out.WriteString(colMount(n.Name + " (" + n.Path + ")"))
|
|
case n.Subtree != nil:
|
|
_, _ = out.WriteString(colDir(n.Name + sep))
|
|
default:
|
|
_, _ = out.WriteString(n.Name)
|
|
}
|
|
// mark templates
|
|
if n.Template {
|
|
_, _ = out.WriteString(" " + colTpl("(template)"))
|
|
}
|
|
// mark shadowed entries
|
|
if n.Leaf && n.Subtree != nil && !n.Mount {
|
|
_, _ = out.WriteString(" " + colShadow("(shadowed)"))
|
|
}
|
|
// finish this output
|
|
_, _ = out.WriteString("\n")
|
|
|
|
if n.Subtree == nil {
|
|
return out.String()
|
|
}
|
|
|
|
// let our children format themselves
|
|
for i, node := range n.Subtree.Nodes {
|
|
last := i == len(n.Subtree.Nodes)-1
|
|
_, _ = out.WriteString(node.format(prefix, last, maxDepth, curDepth+1))
|
|
}
|
|
|
|
return out.String()
|
|
}
|
|
|
|
// Len returns the length of this subtree.
|
|
func (n *Node) Len() int {
|
|
if n.Subtree == nil {
|
|
return 1
|
|
}
|
|
|
|
var l int
|
|
|
|
// this node might point to a secret itself so we must account for that
|
|
if n.Leaf {
|
|
l++
|
|
}
|
|
|
|
// and for any secret it's subtree might contain
|
|
for _, t := range n.Subtree.Nodes {
|
|
l += t.Len()
|
|
}
|
|
|
|
return l
|
|
}
|
|
|
|
func (n *Node) list(prefix string, maxDepth, curDepth int, files bool) []string {
|
|
if maxDepth >= 0 && curDepth > maxDepth {
|
|
return nil
|
|
}
|
|
|
|
if prefix != "" {
|
|
prefix += sep
|
|
}
|
|
|
|
prefix += n.Name
|
|
|
|
out := make([]string, 0, n.Len())
|
|
// if it's a file and we are looking for files
|
|
if n.Leaf && !n.Mount && files {
|
|
// we return the file
|
|
out = append(out, prefix)
|
|
} else if curDepth == maxDepth && n.Subtree != nil {
|
|
// otherwise if we are "at the bottom" and it's not a file
|
|
// we return the directory name with a separator at the end
|
|
return []string{prefix + sep}
|
|
}
|
|
|
|
// if we don't have subitems, then it's a leaf and we return
|
|
// (notice that this is what ends the recursion when maxDepth is set to -1)
|
|
if n.Subtree == nil {
|
|
return out
|
|
}
|
|
|
|
// this is the part that will list the subdirectories on their own line when using the -d option
|
|
if !files {
|
|
out = append(out, prefix+sep)
|
|
}
|
|
|
|
// we keep listing the subtree nodes if we haven't exited yet.
|
|
for _, t := range n.Subtree.Nodes {
|
|
out = append(out, t.list(prefix, maxDepth, curDepth+1, files)...)
|
|
}
|
|
|
|
return out
|
|
}
|