forked from TrueCloudLab/rclone
vendor: add github.com/a8m/tree
This commit is contained in:
parent
da9926d574
commit
bfef0bc2e9
19 changed files with 1302 additions and 1 deletions
395
vendor/github.com/a8m/tree/node.go
generated
vendored
Normal file
395
vendor/github.com/a8m/tree/node.go
generated
vendored
Normal file
|
@ -0,0 +1,395 @@
|
|||
package tree
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Node represent some node in the tree
|
||||
// contains FileInfo, and its childs
|
||||
type Node struct {
|
||||
os.FileInfo
|
||||
path string
|
||||
depth int
|
||||
err error
|
||||
nodes Nodes
|
||||
vpaths map[string]bool
|
||||
}
|
||||
|
||||
// List of nodes
|
||||
type Nodes []*Node
|
||||
|
||||
// To use this package programmatically, you must implement this
|
||||
// interface.
|
||||
// For example: PTAL on 'cmd/tree/tree.go'
|
||||
type Fs interface {
|
||||
Stat(path string) (os.FileInfo, error)
|
||||
ReadDir(path string) ([]string, error)
|
||||
}
|
||||
|
||||
// Options store the configuration for specific tree.
|
||||
// Note, that 'Fs', and 'OutFile' are required (OutFile can be os.Stdout).
|
||||
type Options struct {
|
||||
Fs Fs
|
||||
OutFile io.Writer
|
||||
// List
|
||||
All bool
|
||||
DirsOnly bool
|
||||
FullPath bool
|
||||
IgnoreCase bool
|
||||
FollowLink bool
|
||||
DeepLevel int
|
||||
Pattern string
|
||||
IPattern string
|
||||
// File
|
||||
ByteSize bool
|
||||
UnitSize bool
|
||||
FileMode bool
|
||||
ShowUid bool
|
||||
ShowGid bool
|
||||
LastMod bool
|
||||
Quotes bool
|
||||
Inodes bool
|
||||
Device bool
|
||||
// Sort
|
||||
NoSort bool
|
||||
VerSort bool
|
||||
ModSort bool
|
||||
DirSort bool
|
||||
NameSort bool
|
||||
SizeSort bool
|
||||
CTimeSort bool
|
||||
ReverSort bool
|
||||
// Graphics
|
||||
NoIndent bool
|
||||
Colorize bool
|
||||
}
|
||||
|
||||
// New get path and create new node(root).
|
||||
func New(path string) *Node {
|
||||
return &Node{path: path, vpaths: make(map[string]bool)}
|
||||
}
|
||||
|
||||
// Visit all files under the given node.
|
||||
func (node *Node) Visit(opts *Options) (dirs, files int) {
|
||||
// visited paths
|
||||
if path, err := filepath.Abs(node.path); err == nil {
|
||||
path = filepath.Clean(path)
|
||||
node.vpaths[path] = true
|
||||
}
|
||||
// stat
|
||||
fi, err := opts.Fs.Stat(node.path)
|
||||
if err != nil {
|
||||
node.err = err
|
||||
return
|
||||
}
|
||||
node.FileInfo = fi
|
||||
if !fi.IsDir() {
|
||||
return 0, 1
|
||||
}
|
||||
// DeepLevel option
|
||||
if opts.DeepLevel > 0 && opts.DeepLevel <= node.depth {
|
||||
return 1, 0
|
||||
}
|
||||
names, err := opts.Fs.ReadDir(node.path)
|
||||
if err != nil {
|
||||
node.err = err
|
||||
return
|
||||
}
|
||||
node.nodes = make(Nodes, 0)
|
||||
for _, name := range names {
|
||||
// "all" option
|
||||
if !opts.All && strings.HasPrefix(name, ".") {
|
||||
continue
|
||||
}
|
||||
nnode := &Node{
|
||||
path: filepath.Join(node.path, name),
|
||||
depth: node.depth + 1,
|
||||
vpaths: node.vpaths,
|
||||
}
|
||||
d, f := nnode.Visit(opts)
|
||||
if nnode.err == nil && !nnode.IsDir() {
|
||||
// "dirs only" option
|
||||
if opts.DirsOnly {
|
||||
continue
|
||||
}
|
||||
var rePrefix string
|
||||
if opts.IgnoreCase {
|
||||
rePrefix = "(?i)"
|
||||
}
|
||||
// Pattern matching
|
||||
if opts.Pattern != "" {
|
||||
re, err := regexp.Compile(rePrefix + opts.Pattern)
|
||||
if err == nil && !re.MatchString(name) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// IPattern matching
|
||||
if opts.IPattern != "" {
|
||||
re, err := regexp.Compile(rePrefix + opts.IPattern)
|
||||
if err == nil && re.MatchString(name) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
node.nodes = append(node.nodes, nnode)
|
||||
dirs, files = dirs+d, files+f
|
||||
}
|
||||
// Sorting
|
||||
if !opts.NoSort {
|
||||
node.sort(opts)
|
||||
}
|
||||
return dirs + 1, files
|
||||
}
|
||||
|
||||
func (node *Node) sort(opts *Options) {
|
||||
var fn SortFunc
|
||||
switch {
|
||||
case opts.ModSort:
|
||||
fn = ModSort
|
||||
case opts.CTimeSort:
|
||||
fn = CTimeSort
|
||||
case opts.DirSort:
|
||||
fn = DirSort
|
||||
case opts.VerSort:
|
||||
fn = VerSort
|
||||
case opts.SizeSort:
|
||||
fn = SizeSort
|
||||
case opts.NameSort:
|
||||
fn = NameSort
|
||||
default:
|
||||
fn = NameSort // Default should be sorted, not unsorted.
|
||||
}
|
||||
if fn != nil {
|
||||
if opts.ReverSort {
|
||||
sort.Sort(sort.Reverse(ByFunc{node.nodes, fn}))
|
||||
} else {
|
||||
sort.Sort(ByFunc{node.nodes, fn})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Print nodes based on the given configuration.
|
||||
func (node *Node) Print(opts *Options) { node.print("", opts) }
|
||||
|
||||
func dirRecursiveSize(opts *Options, node *Node) (size int64, err error) {
|
||||
if opts.DeepLevel > 0 && node.depth >= opts.DeepLevel {
|
||||
err = errors.New("Depth too high")
|
||||
}
|
||||
|
||||
for _, nnode := range node.nodes {
|
||||
if nnode.err != nil {
|
||||
err = nnode.err
|
||||
continue
|
||||
}
|
||||
|
||||
if !nnode.IsDir() {
|
||||
size += nnode.Size()
|
||||
} else {
|
||||
nsize, e := dirRecursiveSize(opts, nnode)
|
||||
size += nsize
|
||||
if e != nil {
|
||||
err = e
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (node *Node) print(indent string, opts *Options) {
|
||||
if node.err != nil {
|
||||
err := node.err.Error()
|
||||
if msgs := strings.Split(err, ": "); len(msgs) > 1 {
|
||||
err = msgs[1]
|
||||
}
|
||||
fmt.Printf("%s [%s]\n", node.path, err)
|
||||
return
|
||||
}
|
||||
if !node.IsDir() {
|
||||
var props []string
|
||||
ok, inode, device, uid, gid := getStat(node)
|
||||
// inodes
|
||||
if ok && opts.Inodes {
|
||||
props = append(props, fmt.Sprintf("%d", inode))
|
||||
}
|
||||
// device
|
||||
if ok && opts.Device {
|
||||
props = append(props, fmt.Sprintf("%3d", device))
|
||||
}
|
||||
// Mode
|
||||
if opts.FileMode {
|
||||
props = append(props, node.Mode().String())
|
||||
}
|
||||
// Owner/Uid
|
||||
if ok && opts.ShowUid {
|
||||
uidStr := strconv.Itoa(int(uid))
|
||||
if u, err := user.LookupId(uidStr); err != nil {
|
||||
props = append(props, fmt.Sprintf("%-8s", uidStr))
|
||||
} else {
|
||||
props = append(props, fmt.Sprintf("%-8s", u.Username))
|
||||
}
|
||||
}
|
||||
// Gorup/Gid
|
||||
// TODO: support groupname
|
||||
if ok && opts.ShowGid {
|
||||
gidStr := strconv.Itoa(int(gid))
|
||||
props = append(props, fmt.Sprintf("%-4s", gidStr))
|
||||
}
|
||||
// Size
|
||||
if opts.ByteSize || opts.UnitSize {
|
||||
var size string
|
||||
if opts.UnitSize {
|
||||
size = fmt.Sprintf("%4s", formatBytes(node.Size()))
|
||||
} else {
|
||||
size = fmt.Sprintf("%11d", node.Size())
|
||||
}
|
||||
props = append(props, size)
|
||||
}
|
||||
// Last modification
|
||||
if opts.LastMod {
|
||||
props = append(props, node.ModTime().Format("Jan 02 15:04"))
|
||||
}
|
||||
// Print properties
|
||||
if len(props) > 0 {
|
||||
fmt.Fprintf(opts.OutFile, "[%s] ", strings.Join(props, " "))
|
||||
}
|
||||
} else {
|
||||
var props []string
|
||||
// Size
|
||||
if opts.ByteSize || opts.UnitSize {
|
||||
var size string
|
||||
rsize, err := dirRecursiveSize(opts, node)
|
||||
if err != nil && rsize <= 0 {
|
||||
if opts.UnitSize {
|
||||
size = "????"
|
||||
} else {
|
||||
size = "???????????"
|
||||
}
|
||||
} else if opts.UnitSize {
|
||||
size = fmt.Sprintf("%4s", formatBytes(rsize))
|
||||
} else {
|
||||
size = fmt.Sprintf("%11d", rsize)
|
||||
}
|
||||
props = append(props, size)
|
||||
}
|
||||
// Print properties
|
||||
if len(props) > 0 {
|
||||
fmt.Fprintf(opts.OutFile, "[%s] ", strings.Join(props, " "))
|
||||
}
|
||||
}
|
||||
// name/path
|
||||
var name string
|
||||
if node.depth == 0 || opts.FullPath {
|
||||
name = node.path
|
||||
} else {
|
||||
name = node.Name()
|
||||
}
|
||||
// Quotes
|
||||
if opts.Quotes {
|
||||
name = fmt.Sprintf("\"%s\"", name)
|
||||
}
|
||||
// Colorize
|
||||
if opts.Colorize {
|
||||
name = ANSIColor(node, name)
|
||||
}
|
||||
// IsSymlink
|
||||
if node.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
vtarget, err := os.Readlink(node.path)
|
||||
if err != nil {
|
||||
vtarget = node.path
|
||||
}
|
||||
targetPath, err := filepath.EvalSymlinks(node.path)
|
||||
if err != nil {
|
||||
targetPath = vtarget
|
||||
}
|
||||
fi, err := opts.Fs.Stat(targetPath)
|
||||
if opts.Colorize && fi != nil {
|
||||
vtarget = ANSIColor(&Node{FileInfo: fi, path: vtarget}, vtarget)
|
||||
}
|
||||
name = fmt.Sprintf("%s -> %s", name, vtarget)
|
||||
// Follow symbolic links like directories
|
||||
if opts.FollowLink {
|
||||
path, err := filepath.Abs(targetPath)
|
||||
if err == nil && fi != nil && fi.IsDir() {
|
||||
if _, ok := node.vpaths[filepath.Clean(path)]; !ok {
|
||||
inf := &Node{FileInfo: fi, path: targetPath}
|
||||
inf.vpaths = node.vpaths
|
||||
inf.Visit(opts)
|
||||
node.nodes = inf.nodes
|
||||
} else {
|
||||
name += " [recursive, not followed]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Print file details
|
||||
// the main idea of the print logic came from here: github.com/campoy/tools/tree
|
||||
fmt.Fprintln(opts.OutFile, name)
|
||||
add := "│ "
|
||||
for i, nnode := range node.nodes {
|
||||
if opts.NoIndent {
|
||||
add = ""
|
||||
} else {
|
||||
if i == len(node.nodes)-1 {
|
||||
fmt.Fprintf(opts.OutFile, indent+"└── ")
|
||||
add = " "
|
||||
} else {
|
||||
fmt.Fprintf(opts.OutFile, indent+"├── ")
|
||||
}
|
||||
}
|
||||
nnode.print(indent+add, opts)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
_ = iota // ignore first value by assigning to blank identifier
|
||||
KB int64 = 1 << (10 * iota)
|
||||
MB
|
||||
GB
|
||||
TB
|
||||
PB
|
||||
EB
|
||||
)
|
||||
|
||||
// Convert bytes to human readable string. Like a 2 MB, 64.2 KB, 52 B
|
||||
func formatBytes(i int64) (result string) {
|
||||
var n float64
|
||||
sFmt, eFmt := "%.01f", ""
|
||||
switch {
|
||||
case i > EB:
|
||||
eFmt = "E"
|
||||
n = float64(i) / float64(EB)
|
||||
case i > PB:
|
||||
eFmt = "P"
|
||||
n = float64(i) / float64(PB)
|
||||
case i > TB:
|
||||
eFmt = "T"
|
||||
n = float64(i) / float64(TB)
|
||||
case i > GB:
|
||||
eFmt = "G"
|
||||
n = float64(i) / float64(GB)
|
||||
case i > MB:
|
||||
eFmt = "M"
|
||||
n = float64(i) / float64(MB)
|
||||
case i > KB:
|
||||
eFmt = "K"
|
||||
n = float64(i) / float64(KB)
|
||||
default:
|
||||
sFmt = "%.0f"
|
||||
n = float64(i)
|
||||
}
|
||||
if eFmt != "" && n >= 10 {
|
||||
sFmt = "%.0f"
|
||||
}
|
||||
result = fmt.Sprintf(sFmt+eFmt, n)
|
||||
result = strings.Trim(result, " ")
|
||||
return
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue