From bfef0bc2e9fa32111dcdce729a992078cbe6f7fe Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Wed, 26 Jul 2017 23:00:20 +0100 Subject: [PATCH] vendor: add github.com/a8m/tree --- Gopkg.lock | 8 +- vendor/github.com/a8m/tree/.gitignore | 2 + vendor/github.com/a8m/tree/.travis.yml | 12 + vendor/github.com/a8m/tree/README.md | 41 ++ vendor/github.com/a8m/tree/cmd/tree/tree.go | 200 +++++++++ vendor/github.com/a8m/tree/color.go | 74 ++++ vendor/github.com/a8m/tree/color_test.go | 53 +++ vendor/github.com/a8m/tree/compileall.sh | 14 + vendor/github.com/a8m/tree/csort_bsd.go | 13 + vendor/github.com/a8m/tree/csort_generic.go | 6 + vendor/github.com/a8m/tree/csort_unix.go | 13 + vendor/github.com/a8m/tree/modes_bsd.go | 7 + vendor/github.com/a8m/tree/modes_unix.go | 7 + .../github.com/a8m/tree/modes_unsupported.go | 5 + vendor/github.com/a8m/tree/node.go | 395 ++++++++++++++++++ vendor/github.com/a8m/tree/node_test.go | 324 ++++++++++++++ vendor/github.com/a8m/tree/sort.go | 100 +++++ vendor/github.com/a8m/tree/stat_unix.go | 20 + .../github.com/a8m/tree/stat_unsupported.go | 9 + 19 files changed, 1302 insertions(+), 1 deletion(-) create mode 100644 vendor/github.com/a8m/tree/.gitignore create mode 100644 vendor/github.com/a8m/tree/.travis.yml create mode 100644 vendor/github.com/a8m/tree/README.md create mode 100644 vendor/github.com/a8m/tree/cmd/tree/tree.go create mode 100644 vendor/github.com/a8m/tree/color.go create mode 100644 vendor/github.com/a8m/tree/color_test.go create mode 100755 vendor/github.com/a8m/tree/compileall.sh create mode 100644 vendor/github.com/a8m/tree/csort_bsd.go create mode 100644 vendor/github.com/a8m/tree/csort_generic.go create mode 100644 vendor/github.com/a8m/tree/csort_unix.go create mode 100644 vendor/github.com/a8m/tree/modes_bsd.go create mode 100644 vendor/github.com/a8m/tree/modes_unix.go create mode 100644 vendor/github.com/a8m/tree/modes_unsupported.go create mode 100644 vendor/github.com/a8m/tree/node.go create mode 100644 vendor/github.com/a8m/tree/node_test.go create mode 100644 vendor/github.com/a8m/tree/sort.go create mode 100644 vendor/github.com/a8m/tree/stat_unix.go create mode 100644 vendor/github.com/a8m/tree/stat_unsupported.go diff --git a/Gopkg.lock b/Gopkg.lock index e625dd6fc..03ffe224e 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -25,6 +25,12 @@ packages = ["."] revision = "4cc8cc5a2a44f01d31b303a7280e20e00a6eafdb" +[[projects]] + branch = "master" + name = "github.com/a8m/tree" + packages = ["."] + revision = "fb478f41c87d959e328f2eac0c1b40f17a2f3e00" + [[projects]] branch = "master" name = "github.com/aws/aws-sdk-go" @@ -244,6 +250,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "d1197786e4b7133a2e775df76d12dc57d690f2136871f8b0ad3f793c48b4ab08" + inputs-digest = "b04805ec8dc2bf704d0ad0c73e2ce9f13f3770b946a70bda8fd602886ff124a4" solver-name = "gps-cdcl" solver-version = 1 diff --git a/vendor/github.com/a8m/tree/.gitignore b/vendor/github.com/a8m/tree/.gitignore new file mode 100644 index 000000000..793bf2ebc --- /dev/null +++ b/vendor/github.com/a8m/tree/.gitignore @@ -0,0 +1,2 @@ +draft +coverage diff --git a/vendor/github.com/a8m/tree/.travis.yml b/vendor/github.com/a8m/tree/.travis.yml new file mode 100644 index 000000000..bde8206a9 --- /dev/null +++ b/vendor/github.com/a8m/tree/.travis.yml @@ -0,0 +1,12 @@ +language: go +sudo: false +go: + - 1.6.4 + - 1.7.4 + - 1.8.3 + - tip +install: + - go get -t -v ./... +script: + - go test -v ./... + - ./compileall.sh diff --git a/vendor/github.com/a8m/tree/README.md b/vendor/github.com/a8m/tree/README.md new file mode 100644 index 000000000..9c221e5f7 --- /dev/null +++ b/vendor/github.com/a8m/tree/README.md @@ -0,0 +1,41 @@ +tree [![Build status][travis-image]][travis-url] [![License][license-image]][license-url] +--- +> An implementation of the [`tree`](http://mama.indstate.edu/users/ice/tree/) command written in Go, that can be used programmatically. + +#### Installation: +```sh +$ go get github.com/a8m/tree/cmd/tree +``` + +#### How to use `tree` programmatically ? +You can take a look on [`cmd/tree`](https://github.com/a8m/tree/blob/master/cmd/tree/tree.go), and [s3tree](http://github.com/a8m/s3tree) or see the example below. +```go +import ( + "github.com/a8m/tree" +) + +func main() { + opts := &tree.Options{ + // Fs, and OutFile are required fields. + // fs should implement the tree file-system interface(see: tree.Fs), + // and OutFile should be type io.Writer + Fs: fs, + OutFile: os.Stdout, + // ... + } + inf.New("root-dir") + // Visit all nodes recursively + inf.Visit(opts) + // Print nodes + inf.Print(opts) +} +``` + +### License +MIT + + +[travis-image]: https://img.shields.io/travis/a8m/tree.svg?style=flat-square +[travis-url]: https://travis-ci.org/a8m/tree +[license-image]: http://img.shields.io/npm/l/deep-keys.svg?style=flat-square +[license-url]: LICENSE diff --git a/vendor/github.com/a8m/tree/cmd/tree/tree.go b/vendor/github.com/a8m/tree/cmd/tree/tree.go new file mode 100644 index 000000000..eaa6f4aae --- /dev/null +++ b/vendor/github.com/a8m/tree/cmd/tree/tree.go @@ -0,0 +1,200 @@ +package main + +import ( + "errors" + "flag" + "fmt" + "github.com/a8m/tree" + "os" +) + +var ( + // List + a = flag.Bool("a", false, "") + d = flag.Bool("d", false, "") + f = flag.Bool("f", false, "") + ignorecase = flag.Bool("ignore-case", false, "") + noreport = flag.Bool("noreport", false, "") + l = flag.Bool("l", false, "") + L = flag.Int("L", 3, "") + P = flag.String("P", "", "") + I = flag.String("I", "", "") + o = flag.String("o", "", "") + // Files + s = flag.Bool("s", false, "") + h = flag.Bool("h", false, "") + p = flag.Bool("p", false, "") + u = flag.Bool("u", false, "") + g = flag.Bool("g", false, "") + Q = flag.Bool("Q", false, "") + D = flag.Bool("D", false, "") + inodes = flag.Bool("inodes", false, "") + device = flag.Bool("device", false, "") + // Sort + U = flag.Bool("U", false, "") + v = flag.Bool("v", false, "") + t = flag.Bool("t", false, "") + c = flag.Bool("c", false, "") + r = flag.Bool("r", false, "") + dirsfirst = flag.Bool("dirsfirst", false, "") + sort = flag.String("sort", "", "") + // Graphics + i = flag.Bool("i", false, "") + C = flag.Bool("C", false, "") +) + +var usage = `Usage: tree [options...] [paths...] + +Options: + ------- Listing options ------- + -a All files are listed. + -d List directories only. + -l Follow symbolic links like directories. + -f Print the full path prefix for each file. + -L Descend only level directories deep. + -P List only those files that match the pattern given. + -I Do not list files that match the given pattern. + --ignore-case Ignore case when pattern matching. + --noreport Turn off file/directory count at end of tree listing. + -o filename Output to file instead of stdout. + -------- File options --------- + -Q Quote filenames with double quotes. + -p Print the protections for each file. + -u Displays file owner or UID number. + -g Displays file group owner or GID number. + -s Print the size in bytes of each file. + -h Print the size in a more human readable way. + -D Print the date of last modification or (-c) status change. + --inodes Print inode number of each file. + --device Print device ID number to which each file belongs. + ------- Sorting options ------- + -v Sort files alphanumerically by version. + -t Sort files by last modification time. + -c Sort files by last status change time. + -U Leave files unsorted. + -r Reverse the order of the sort. + --dirsfirst List directories before files (-U disables). + --sort X Select sort: name,version,size,mtime,ctime. + ------- Graphics options ------ + -i Don't print indentation lines. + -C Turn colorization on always. +` + +type fs struct{} + +func (f *fs) Stat(path string) (os.FileInfo, error) { + return os.Lstat(path) +} +func (f *fs) ReadDir(path string) ([]string, error) { + dir, err := os.Open(path) + if err != nil { + return nil, err + } + names, err := dir.Readdirnames(-1) + dir.Close() + if err != nil { + return nil, err + } + return names, nil +} + +func main() { + flag.Usage = func() { fmt.Fprint(os.Stderr, usage) } + var nd, nf int + var dirs = []string{"."} + flag.Parse() + // Make it work with leading dirs + if args := flag.Args(); len(args) > 0 { + dirs = args + } + // Output file + var outFile = os.Stdout + var err error + if *o != "" { + outFile, err = os.Create(*o) + if err != nil { + errAndExit(err) + } + } + defer outFile.Close() + // Check sort-type + if *sort != "" { + switch *sort { + case "version", "mtime", "ctime", "name", "size": + default: + msg := fmt.Sprintf("sort type '%s' not valid, should be one of: "+ + "name,version,size,mtime,ctime", *sort) + errAndExit(errors.New(msg)) + } + } + // Set options + opts := &tree.Options{ + // Required + Fs: new(fs), + OutFile: outFile, + // List + All: *a, + DirsOnly: *d, + FullPath: *f, + DeepLevel: *L, + FollowLink: *l, + Pattern: *P, + IPattern: *I, + IgnoreCase: *ignorecase, + // Files + ByteSize: *s, + UnitSize: *h, + FileMode: *p, + ShowUid: *u, + ShowGid: *g, + LastMod: *D, + Quotes: *Q, + Inodes: *inodes, + Device: *device, + // Sort + NoSort: *U, + ReverSort: *r, + DirSort: *dirsfirst, + VerSort: *v || *sort == "version", + ModSort: *t || *sort == "mtime", + CTimeSort: *c || *sort == "ctime", + NameSort: *sort == "name", + SizeSort: *sort == "size", + // Graphics + NoIndent: *i, + Colorize: *C, + } + for _, dir := range dirs { + inf := tree.New(dir) + if d, f := inf.Visit(opts); f != 0 { + if d > 0 { + d -= 1 + } + nd, nf = nd+d, nf+f + } + inf.Print(opts) + } + // Print footer report + if !*noreport { + footer := fmt.Sprintf("\n%d directories", nd) + if !opts.DirsOnly { + footer += fmt.Sprintf(", %d files", nf) + } + fmt.Fprintln(outFile, footer) + } +} + +func usageAndExit(msg string) { + if msg != "" { + fmt.Fprintf(os.Stderr, msg) + fmt.Fprintf(os.Stderr, "\n\n") + } + flag.Usage() + fmt.Fprintf(os.Stderr, "\n") + os.Exit(1) +} + +func errAndExit(err error) { + fmt.Fprintf(os.Stderr, "tree: \"%s\"\n", err) + os.Exit(1) +} diff --git a/vendor/github.com/a8m/tree/color.go b/vendor/github.com/a8m/tree/color.go new file mode 100644 index 000000000..65bdf27dc --- /dev/null +++ b/vendor/github.com/a8m/tree/color.go @@ -0,0 +1,74 @@ +package tree + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +const Escape = "\x1b" +const ( + Reset int = 0 + // Not used, remove. + Bold int = 1 + Black int = iota + 28 + Red + Green + Yellow + Blue + Magenta + Cyan + White +) + +// ANSIColor +func ANSIColor(node *Node, s string) string { + var style string + var mode = node.Mode() + var ext = filepath.Ext(node.Name()) + switch { + case contains([]string{".bat", ".btm", ".cmd", ".com", ".dll", ".exe"}, ext): + style = "1;32" + case contains([]string{".arj", ".bz2", ".deb", ".gz", ".lzh", ".rpm", + ".tar", ".taz", ".tb2", ".tbz2", ".tbz", ".tgz", ".tz", ".tz2", ".z", + ".zip", ".zoo"}, ext): + style = "1;31" + case contains([]string{".asf", ".avi", ".bmp", ".flac", ".gif", ".jpg", + "jpeg", ".m2a", ".m2v", ".mov", ".mp3", ".mpeg", ".mpg", ".ogg", ".ppm", + ".rm", ".tga", ".tif", ".wav", ".wmv", + ".xbm", ".xpm"}, ext): + style = "1;35" + case node.IsDir() || mode&os.ModeDir != 0: + style = "1;34" + case mode&os.ModeNamedPipe != 0: + style = "40;33" + case mode&os.ModeSocket != 0: + style = "40;1;35" + case mode&os.ModeDevice != 0 || mode&os.ModeCharDevice != 0: + style = "40;1;33" + case mode&os.ModeSymlink != 0: + if _, err := filepath.EvalSymlinks(node.path); err != nil { + style = "40;1;31" + } else { + style = "1;36" + } + case mode&modeExecute != 0: + style = "1;32" + default: + return s + } + return fmt.Sprintf("%s[%sm%s%s[%dm", Escape, style, s, Escape, Reset) +} + +// case-insensitive contains helper +func contains(slice []string, str string) bool { + for _, val := range slice { + if val == strings.ToLower(str) { + return true + } + } + return false +} + +// TODO: HTMLColor diff --git a/vendor/github.com/a8m/tree/color_test.go b/vendor/github.com/a8m/tree/color_test.go new file mode 100644 index 000000000..d2bcfb85d --- /dev/null +++ b/vendor/github.com/a8m/tree/color_test.go @@ -0,0 +1,53 @@ +package tree + +import ( + "os" + "syscall" + "testing" +) + +var extsTests = []struct { + name string + expected string +}{ + {"foo.jpg", "\x1b[1;35mfoo.jpg\x1b[0m"}, + {"bar.tar", "\x1b[1;31mbar.tar\x1b[0m"}, + {"baz.exe", "\x1b[1;32mbaz.exe\x1b[0m"}, +} + +func TestExtension(t *testing.T) { + for _, test := range extsTests { + fi := &file{name: test.name} + no := &Node{FileInfo: fi} + if actual := ANSIColor(no, fi.name); actual != test.expected { + t.Errorf("\ngot:\n%+v\nexpected:\n%+v", actual, test.expected) + } + } +} + +var modeTests = []struct { + path string + name string + expected string + mode os.FileMode +}{ + {"", "simple", "simple", os.FileMode(0)}, + {"", "dir", "\x1b[1;34mdir\x1b[0m", os.ModeDir}, + {"", "socket", "\x1b[40;1;35msocket\x1b[0m", os.ModeSocket}, + {"", "fifo", "\x1b[40;33mfifo\x1b[0m", os.ModeNamedPipe}, + {"", "block", "\x1b[40;1;33mblock\x1b[0m", os.ModeDevice}, + {"", "char", "\x1b[40;1;33mchar\x1b[0m", os.ModeCharDevice}, + {"", "exist-symlink", "\x1b[1;36mexist-symlink\x1b[0m", os.ModeSymlink}, + {"fake-path-a8m", "fake-path", "\x1b[40;1;31mfake-path\x1b[0m", os.ModeSymlink}, + {"", "exec", "\x1b[1;32mexec\x1b[0m", os.FileMode(syscall.S_IXUSR)}, +} + +func TestFileMode(t *testing.T) { + for _, test := range modeTests { + fi := &file{name: test.name, mode: test.mode} + no := &Node{FileInfo: fi, path: test.path} + if actual := ANSIColor(no, fi.name); actual != test.expected { + t.Errorf("\ngot:\n%+v\nexpected:\n%+v", actual, test.expected) + } + } +} diff --git a/vendor/github.com/a8m/tree/compileall.sh b/vendor/github.com/a8m/tree/compileall.sh new file mode 100755 index 000000000..fa94f6c4f --- /dev/null +++ b/vendor/github.com/a8m/tree/compileall.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +go tool dist list >/dev/null || { + echo 1>&2 "go tool dist list not supported - can't check compile" + exit 0 +} + +while read -r line; do + parts=(${line//\// }) + export GOOS=${parts[0]} + export GOARCH=${parts[1]} + echo Try GOOS=${GOOS} GOARCH=${GOARCH} + go install +done < <(go tool dist list) diff --git a/vendor/github.com/a8m/tree/csort_bsd.go b/vendor/github.com/a8m/tree/csort_bsd.go new file mode 100644 index 000000000..d2bbbcc3b --- /dev/null +++ b/vendor/github.com/a8m/tree/csort_bsd.go @@ -0,0 +1,13 @@ +//+build darwin freebsd netbsd + +package tree + +import ( + "os" + "syscall" +) + +func CTimeSort(f1, f2 os.FileInfo) bool { + s1, s2 := f1.Sys().(*syscall.Stat_t), f2.Sys().(*syscall.Stat_t) + return s1.Ctimespec.Sec < s2.Ctimespec.Sec +} diff --git a/vendor/github.com/a8m/tree/csort_generic.go b/vendor/github.com/a8m/tree/csort_generic.go new file mode 100644 index 000000000..38be9aadb --- /dev/null +++ b/vendor/github.com/a8m/tree/csort_generic.go @@ -0,0 +1,6 @@ +//+build !linux,!openbsd,!dragonfly,!android,!solaris,!darwin,!freebsd,!netbsd + +package tree + +// CtimeSort for unsupported OS - just compare ModTime +var CTimeSort = ModSort diff --git a/vendor/github.com/a8m/tree/csort_unix.go b/vendor/github.com/a8m/tree/csort_unix.go new file mode 100644 index 000000000..3d769f393 --- /dev/null +++ b/vendor/github.com/a8m/tree/csort_unix.go @@ -0,0 +1,13 @@ +//+build linux openbsd dragonfly android solaris + +package tree + +import ( + "os" + "syscall" +) + +func CTimeSort(f1, f2 os.FileInfo) bool { + s1, s2 := f1.Sys().(*syscall.Stat_t), f2.Sys().(*syscall.Stat_t) + return s1.Ctim.Sec < s2.Ctim.Sec +} diff --git a/vendor/github.com/a8m/tree/modes_bsd.go b/vendor/github.com/a8m/tree/modes_bsd.go new file mode 100644 index 000000000..c97206541 --- /dev/null +++ b/vendor/github.com/a8m/tree/modes_bsd.go @@ -0,0 +1,7 @@ +//+build dragonfly freebsd openbsd solaris windows + +package tree + +import "syscall" + +const modeExecute = syscall.S_IXUSR diff --git a/vendor/github.com/a8m/tree/modes_unix.go b/vendor/github.com/a8m/tree/modes_unix.go new file mode 100644 index 000000000..1394bf6b6 --- /dev/null +++ b/vendor/github.com/a8m/tree/modes_unix.go @@ -0,0 +1,7 @@ +//+build android darwin linux nacl netbsd + +package tree + +import "syscall" + +const modeExecute = syscall.S_IXUSR | syscall.S_IXGRP | syscall.S_IXOTH diff --git a/vendor/github.com/a8m/tree/modes_unsupported.go b/vendor/github.com/a8m/tree/modes_unsupported.go new file mode 100644 index 000000000..2fe96cb46 --- /dev/null +++ b/vendor/github.com/a8m/tree/modes_unsupported.go @@ -0,0 +1,5 @@ +//+build !dragonfly,!freebsd,!openbsd,!solaris,!windows,!android,!darwin,!linux,!nacl,!netbsd + +package tree + +const modeExecute = 0 diff --git a/vendor/github.com/a8m/tree/node.go b/vendor/github.com/a8m/tree/node.go new file mode 100644 index 000000000..31be6636f --- /dev/null +++ b/vendor/github.com/a8m/tree/node.go @@ -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 +} diff --git a/vendor/github.com/a8m/tree/node_test.go b/vendor/github.com/a8m/tree/node_test.go new file mode 100644 index 000000000..f3c7ebc8b --- /dev/null +++ b/vendor/github.com/a8m/tree/node_test.go @@ -0,0 +1,324 @@ +package tree + +import ( + "os" + "syscall" + "testing" + "time" +) + +// Mock file/FileInfo +type file struct { + name string + size int64 + files []*file + lastMod time.Time + stat_t interface{} + mode os.FileMode +} + +func (f file) Name() string { return f.name } +func (f file) Size() int64 { return f.size } +func (f file) Mode() (o os.FileMode) { + if f.mode != o { + return f.mode + } + if f.stat_t != nil { + stat := (f.stat_t).(*syscall.Stat_t) + o = os.FileMode(stat.Mode) + } + return +} +func (f file) ModTime() time.Time { return f.lastMod } +func (f file) IsDir() bool { return nil != f.files } +func (f file) Sys() interface{} { + if f.stat_t == nil { + return new(syscall.Stat_t) + } + return f.stat_t +} + +// Mock filesystem +type MockFs struct { + files map[string]*file +} + +func NewFs() *MockFs { + return &MockFs{make(map[string]*file)} +} + +func (fs *MockFs) clean() *MockFs { + fs.files = make(map[string]*file) + return fs +} + +func (fs *MockFs) addFile(path string, file *file) *MockFs { + fs.files[path] = file + if file.IsDir() { + for _, f := range file.files { + fs.addFile(path+"/"+f.name, f) + } + } + return fs +} + +func (fs *MockFs) Stat(path string) (os.FileInfo, error) { + return fs.files[path], nil +} +func (fs *MockFs) ReadDir(path string) ([]string, error) { + var names []string + for _, file := range fs.files[path].files { + names = append(names, file.Name()) + } + return names, nil +} + +// Mock output file +type Out struct { + str string +} + +func (o *Out) equal(s string) bool { + return o.str == s +} + +func (o *Out) Write(p []byte) (int, error) { + o.str += string(p) + return len(p), nil +} + +func (o *Out) clear() { + o.str = "" +} + +// FileSystem and Stdout mocks +var ( + fs = NewFs() + out = new(Out) +) + +type treeTest struct { + name string + opts *Options + expected string +} + +var listTests = []treeTest{ + {"basic", &Options{Fs: fs, OutFile: out}, `root +├── a +├── b +└── c + ├── d + └── e +`}, {"all", &Options{Fs: fs, OutFile: out, All: true, NoSort: true}, `root +├── a +├── b +└── c + ├── d + ├── e + └── .f +`}, {"dirs", &Options{Fs: fs, OutFile: out, DirsOnly: true}, `root +└── c +`}, {"fullPath", &Options{Fs: fs, OutFile: out, FullPath: true}, `root +├── root/a +├── root/b +└── root/c + ├── root/c/d + └── root/c/e +`}, {"deepLevel", &Options{Fs: fs, OutFile: out, DeepLevel: 1}, `root +├── a +├── b +└── c +`}, {"pattern", &Options{Fs: fs, OutFile: out, Pattern: "(a|e)"}, `root +├── a +└── c + └── e +`}, {"ipattern", &Options{Fs: fs, OutFile: out, IPattern: "(a|e)"}, `root +├── b +└── c + └── d +`}, {"ignore-case", &Options{Fs: fs, OutFile: out, Pattern: "(A)", IgnoreCase: true}, `root +├── a +└── c +`}} + +// Tests +func TestSimple(t *testing.T) { + root := &file{ + name: "root", + size: 200, + files: []*file{ + &file{name: "a", size: 50}, + &file{name: "b", size: 50}, + &file{ + name: "c", + size: 100, + files: []*file{ + &file{name: "d", size: 50}, + &file{name: "e", size: 50}, + &file{name: ".f", size: 0}, + }, + }}, + } + fs.clean().addFile(root.name, root) + for _, test := range listTests { + inf := New(root.name) + inf.Visit(test.opts) + inf.Print(test.opts) + if !out.equal(test.expected) { + t.Errorf("%s:\ngot:\n%+v\nexpected:\n%+v", test.name, out.str, test.expected) + } + out.clear() + } +} + +var sortTests = []treeTest{ + {"name-sort", &Options{Fs: fs, OutFile: out, NameSort: true}, `root +├── a +├── b +└── c + └── d +`}, {"dirs-first sort", &Options{Fs: fs, OutFile: out, DirSort: true}, `root +├── c +│ └── d +├── b +└── a +`}, {"reverse sort", &Options{Fs: fs, OutFile: out, ReverSort: true, DirSort: true}, `root +├── b +├── a +└── c + └── d +`}, {"no-sort", &Options{Fs: fs, OutFile: out, NoSort: true, DirSort: true}, `root +├── b +├── c +│ └── d +└── a +`}, {"size-sort", &Options{Fs: fs, OutFile: out, SizeSort: true}, `root +├── a +├── c +│ └── d +└── b +`}, {"last-mod-sort", &Options{Fs: fs, OutFile: out, ModSort: true}, `root +├── a +├── b +└── c + └── d +`}, {"c-time-sort", &Options{Fs: fs, OutFile: out, CTimeSort: true}, `root +├── b +├── c +│ └── d +└── a +`}} + +func TestSort(t *testing.T) { + + tFmt := "2006-Jan-02" + aTime, _ := time.Parse(tFmt, "2015-Aug-01") + bTime, _ := time.Parse(tFmt, "2015-Sep-01") + cTime, _ := time.Parse(tFmt, "2015-Oct-01") + root := &file{ + name: "root", + size: 200, + files: []*file{ + &file{name: "b", size: 11, lastMod: bTime}, + &file{name: "c", size: 10, files: []*file{ + &file{name: "d", size: 10, lastMod: cTime}, + }, lastMod: cTime}, + &file{name: "a", size: 9, lastMod: aTime}, + }, + } + fs.clean().addFile(root.name, root) + for _, test := range sortTests { + inf := New(root.name) + inf.Visit(test.opts) + inf.Print(test.opts) + if !out.equal(test.expected) { + t.Errorf("%s:\ngot:\n%+v\nexpected:\n%+v", test.name, out.str, test.expected) + } + out.clear() + } +} + +var graphicTests = []treeTest{ + {"no-indent", &Options{Fs: fs, OutFile: out, NoIndent: true}, `root +a +b +c +`}, {"quotes", &Options{Fs: fs, OutFile: out, Quotes: true}, `"root" +├── "a" +├── "b" +└── "c" +`}, {"byte-size", &Options{Fs: fs, OutFile: out, ByteSize: true}, `[ 12499] root +├── [ 1500] a +├── [ 9999] b +└── [ 1000] c +`}, {"unit-size", &Options{Fs: fs, OutFile: out, UnitSize: true}, `[ 12K] root +├── [1.5K] a +├── [9.8K] b +└── [1000] c +`}, {"show-gid", &Options{Fs: fs, OutFile: out, ShowGid: true}, `root +├── [1 ] a +├── [2 ] b +└── [1 ] c +`}, {"mode", &Options{Fs: fs, OutFile: out, FileMode: true}, `root +├── [-rw-r--r--] a +├── [-rwxr-xr-x] b +└── [-rw-rw-rw-] c +`}, {"lastMod", &Options{Fs: fs, OutFile: out, LastMod: true}, `root +├── [Feb 11 00:00] a +├── [Jan 28 00:00] b +└── [Jul 12 00:00] c +`}} + +func TestGraphics(t *testing.T) { + tFmt := "2006-Jan-02" + aTime, _ := time.Parse(tFmt, "2015-Feb-11") + bTime, _ := time.Parse(tFmt, "2006-Jan-28") + cTime, _ := time.Parse(tFmt, "2015-Jul-12") + root := &file{ + name: "root", + size: 11499, + files: []*file{ + &file{name: "a", size: 1500, lastMod: aTime, stat_t: &syscall.Stat_t{Gid: 1, Mode: 0644}}, + &file{name: "b", size: 9999, lastMod: bTime, stat_t: &syscall.Stat_t{Gid: 2, Mode: 0755}}, + &file{name: "c", size: 1000, lastMod: cTime, stat_t: &syscall.Stat_t{Gid: 1, Mode: 0666}}, + }, + stat_t: &syscall.Stat_t{Gid: 1}, + } + fs.clean().addFile(root.name, root) + for _, test := range graphicTests { + inf := New(root.name) + inf.Visit(test.opts) + inf.Print(test.opts) + if !out.equal(test.expected) { + t.Errorf("%s:\ngot:\n%+v\nexpected:\n%+v", test.name, out.str, test.expected) + } + out.clear() + } +} + +var symlinkTests = []treeTest{ + {"symlink", &Options{Fs: fs, OutFile: out}, `root +└── symlink -> root/symlink +`}, {"symlink-rec", &Options{Fs: fs, OutFile: out, FollowLink: true}, `root +└── symlink -> root/symlink [recursive, not followed] +`}} + +func TestSymlink(t *testing.T) { + root := &file{ + name: "root", + files: []*file{ + &file{name: "symlink", mode: os.ModeSymlink, files: make([]*file, 0)}, + }, + } + fs.clean().addFile(root.name, root) + for _, test := range symlinkTests { + inf := New(root.name) + inf.Visit(test.opts) + inf.Print(test.opts) + if !out.equal(test.expected) { + t.Errorf("%s:\ngot:\n%+v\nexpected:\n%+v", test.name, out.str, test.expected) + } + out.clear() + } +} diff --git a/vendor/github.com/a8m/tree/sort.go b/vendor/github.com/a8m/tree/sort.go new file mode 100644 index 000000000..55299935c --- /dev/null +++ b/vendor/github.com/a8m/tree/sort.go @@ -0,0 +1,100 @@ +package tree + +import "os" + +func (n Nodes) Len() int { return len(n) } +func (n Nodes) Swap(i, j int) { n[i], n[j] = n[j], n[i] } + +type ByFunc struct { + Nodes + Fn SortFunc +} + +func (b ByFunc) Less(i, j int) bool { + return b.Fn(b.Nodes[i].FileInfo, b.Nodes[j].FileInfo) +} + +type SortFunc func(f1, f2 os.FileInfo) bool + +func ModSort(f1, f2 os.FileInfo) bool { + return f1.ModTime().Before(f2.ModTime()) +} + +func DirSort(f1, f2 os.FileInfo) bool { + return f1.IsDir() && !f2.IsDir() +} + +func SizeSort(f1, f2 os.FileInfo) bool { + return f1.Size() < f2.Size() +} + +func NameSort(f1, f2 os.FileInfo) bool { + return f1.Name() < f2.Name() +} + +func VerSort(f1, f2 os.FileInfo) bool { + return NaturalLess(f1.Name(), f2.Name()) +} + +func isdigit(b byte) bool { return '0' <= b && b <= '9' } + +// NaturalLess compares two strings using natural ordering. This means that e.g. +// "abc2" < "abc12". +// +// Non-digit sequences and numbers are compared separately. The former are +// compared bytewise, while the latter are compared numerically (except that +// the number of leading zeros is used as a tie-breaker, so e.g. "2" < "02") +// +// Limitation: only ASCII digits (0-9) are considered. +// Code taken from: +// https://github.com/fvbommel/util/blob/master/sortorder/natsort.go +func NaturalLess(str1, str2 string) bool { + idx1, idx2 := 0, 0 + for idx1 < len(str1) && idx2 < len(str2) { + c1, c2 := str1[idx1], str2[idx2] + dig1, dig2 := isdigit(c1), isdigit(c2) + switch { + case dig1 != dig2: // Digits before other characters. + return dig1 // True if LHS is a digit, false if the RHS is one. + case !dig1: // && !dig2, because dig1 == dig2 + // UTF-8 compares bytewise-lexicographically, no need to decode + // codepoints. + if c1 != c2 { + return c1 < c2 + } + idx1++ + idx2++ + default: // Digits + // Eat zeros. + for ; idx1 < len(str1) && str1[idx1] == '0'; idx1++ { + } + for ; idx2 < len(str2) && str2[idx2] == '0'; idx2++ { + } + // Eat all digits. + nonZero1, nonZero2 := idx1, idx2 + for ; idx1 < len(str1) && isdigit(str1[idx1]); idx1++ { + } + for ; idx2 < len(str2) && isdigit(str2[idx2]); idx2++ { + } + // If lengths of numbers with non-zero prefix differ, the shorter + // one is less. + if len1, len2 := idx1-nonZero1, idx2-nonZero2; len1 != len2 { + return len1 < len2 + } + // If they're not equal, string comparison is correct. + if nr1, nr2 := str1[nonZero1:idx1], str2[nonZero2:idx2]; nr1 != nr2 { + return nr1 < nr2 + } + // Otherwise, the one with less zeros is less. + // Because everything up to the number is equal, comparing the index + // after the zeros is sufficient. + if nonZero1 != nonZero2 { + return nonZero1 < nonZero2 + } + } + // They're identical so far, so continue comparing. + } + // So far they are identical. At least one is ended. If the other continues, + // it sorts last. + return len(str1) < len(str2) +} diff --git a/vendor/github.com/a8m/tree/stat_unix.go b/vendor/github.com/a8m/tree/stat_unix.go new file mode 100644 index 000000000..5ed7b0a46 --- /dev/null +++ b/vendor/github.com/a8m/tree/stat_unix.go @@ -0,0 +1,20 @@ +//+build !plan9,!windows + +package tree + +import ( + "os" + "syscall" +) + +func getStat(fi os.FileInfo) (ok bool, inode, device, uid, gid uint64) { + sys := fi.Sys() + if sys == nil { + return false, 0, 0, 0, 0 + } + stat, ok := sys.(*syscall.Stat_t) + if !ok { + return false, 0, 0, 0, 0 + } + return true, uint64(stat.Ino), uint64(stat.Dev), uint64(stat.Uid), uint64(stat.Gid) +} diff --git a/vendor/github.com/a8m/tree/stat_unsupported.go b/vendor/github.com/a8m/tree/stat_unsupported.go new file mode 100644 index 000000000..b37407daf --- /dev/null +++ b/vendor/github.com/a8m/tree/stat_unsupported.go @@ -0,0 +1,9 @@ +//+build plan9 windows + +package tree + +import "os" + +func getStat(fi os.FileInfo) (ok bool, inode, device, uid, gid uint64) { + return false, 0, 0, 0, 0 +}