New Upstream Release - golang-github-a8m-tree
Ready changes
Summary
Merged new upstream version: 0.0~git20230208.36ae24d (was: 0.0~git20210414.ce3525c+ds).
Resulting package
Built on 2023-03-26T22:17 (took 3m45s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases golang-github-a8m-tree-dev
Lintian Result
Diff
diff --git a/.gitignore b/.gitignore
index 793bf2e..6714d1d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
draft
coverage
+*~
diff --git a/.travis.yml b/.travis.yml
index dbac1c6..d7475be 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,9 +5,9 @@ arch:
language: go
sudo: false
go:
- - 1.13.x
- - 1.14.x
- - 1.15.x
+ - 1.18.x
+ - 1.19.x
+ - 1.20.x
- tip
matrix:
allow_failures:
diff --git a/color.go b/color.go
index 65bdf27..2dffcce 100644
--- a/color.go
+++ b/color.go
@@ -22,6 +22,11 @@ const (
White
)
+// ANSIColorFormat
+func ANSIColorFormat(style string, s string) string {
+ return fmt.Sprintf("%s[%sm%s%s[%dm", Escape, style, s, Escape, Reset)
+}
+
// ANSIColor
func ANSIColor(node *Node, s string) string {
var style string
@@ -58,7 +63,7 @@ func ANSIColor(node *Node, s string) string {
default:
return s
}
- return fmt.Sprintf("%s[%sm%s%s[%dm", Escape, style, s, Escape, Reset)
+ return ANSIColorFormat(style, s)
}
// case-insensitive contains helper
diff --git a/csort_bsd.go b/csort_bsd.go
index f5895a6..21af4b1 100644
--- a/csort_bsd.go
+++ b/csort_bsd.go
@@ -8,6 +8,9 @@ import (
)
func CTimeSort(f1, f2 os.FileInfo) bool {
+ if f1 == nil || f2 == nil {
+ return f2 == nil
+ }
s1, ok1 := f1.Sys().(*syscall.Stat_t)
s2, ok2 := f2.Sys().(*syscall.Stat_t)
// If this type of node isn't an os node then revert to ModSort
diff --git a/csort_unix.go b/csort_unix.go
index 6acd092..e5777b8 100644
--- a/csort_unix.go
+++ b/csort_unix.go
@@ -1,4 +1,5 @@
-//+build linux openbsd dragonfly android solaris
+//go:build linux || openbsd || dragonfly || android || solaris
+// +build linux openbsd dragonfly android solaris
package tree
@@ -8,6 +9,9 @@ import (
)
func CTimeSort(f1, f2 os.FileInfo) bool {
+ if f1 == nil || f2 == nil {
+ return f2 == nil
+ }
s1, ok1 := f1.Sys().(*syscall.Stat_t)
s2, ok2 := f2.Sys().(*syscall.Stat_t)
// If this type of node isn't an os node then revert to ModSort
diff --git a/debian/changelog b/debian/changelog
index 033d018..cda57c5 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+golang-github-a8m-tree (0.0~git20230208.36ae24d-1) UNRELEASED; urgency=low
+
+ * New upstream snapshot.
+ * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk> Sun, 26 Mar 2023 22:14:34 -0000
+
golang-github-a8m-tree (0.0~git20201026.fce18e2-1) unstable; urgency=medium
[ Alexandre Viau ]
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..9cc896d
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module github.com/a8m/tree
+
+go 1.15
diff --git a/node.go b/node.go
index 4d15e56..fc36573 100644
--- a/node.go
+++ b/node.go
@@ -49,6 +49,8 @@ type Options struct {
DeepLevel int
Pattern string
IPattern string
+ MatchDirs bool
+ Prune bool
// File
ByteSize bool
UnitSize bool
@@ -71,6 +73,16 @@ type Options struct {
// Graphics
NoIndent bool
Colorize bool
+ // Color defaults to ANSIColor()
+ Color func(*Node, string) string
+}
+
+func (opts *Options) color(node *Node, s string) string {
+ f := opts.Color
+ if f == nil {
+ f = ANSIColor
+ }
+ return f(node, s)
}
// New get path and create new node(root).
@@ -103,6 +115,16 @@ func (node *Node) Visit(opts *Options) (dirs, files int) {
if opts.DeepLevel > 0 && opts.DeepLevel <= node.depth {
return
}
+ // MatchDirs option
+ var dirMatch bool
+ if node.depth != 0 && opts.MatchDirs {
+ // then disable prune and pattern for immediate children
+ if opts.Pattern != "" {
+ dirMatch = node.match(opts.Pattern, opts)
+ } else if opts.IPattern != "" && node.match(opts.IPattern, opts) {
+ return
+ }
+ }
names, err := opts.Fs.ReadDir(node.path)
if err != nil {
node.err = err
@@ -120,26 +142,26 @@ func (node *Node) Visit(opts *Options) (dirs, files int) {
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) {
+ if nnode.err == nil {
+ if nnode.IsDir() {
+ // "prune" option, hide empty directories
+ if opts.Prune && f == 0 {
continue
}
- }
- // IPattern matching
- if opts.IPattern != "" {
- re, err := regexp.Compile(rePrefix + opts.IPattern)
- if err == nil && re.MatchString(name) {
+ if opts.MatchDirs && opts.IPattern != "" && nnode.match(opts.IPattern, opts) {
+ continue
+ }
+ } else {
+ // "dirs only" option
+ if opts.DirsOnly {
+ continue
+ }
+ // Pattern matching
+ if !dirMatch && opts.Pattern != "" && !nnode.match(opts.Pattern, opts) {
+ continue
+ }
+ // IPattern matching
+ if opts.IPattern != "" && nnode.match(opts.IPattern, opts) {
continue
}
}
@@ -154,6 +176,19 @@ func (node *Node) Visit(opts *Options) (dirs, files int) {
return
}
+func (node *Node) match(pattern string, opt *Options) bool {
+ var prefix string
+ if opt.IgnoreCase {
+ prefix = "(?i)"
+ }
+ search := node.Name()
+ if strings.Contains(pattern, "*") {
+ search = node.path
+ }
+ re, err := regexp.Compile(prefix + pattern)
+ return err == nil && re.FindString(search) != ""
+}
+
func (node *Node) sort(opts *Options) {
var fn SortFunc
switch {
@@ -181,6 +216,11 @@ func (node *Node) sort(opts *Options) {
}
}
+// Path returns the Node's absolute path
+func (node *Node) Path() string {
+ return node.path
+}
+
// Print nodes based on the given configuration.
func (node *Node) Print(opts *Options) { node.print("", opts) }
@@ -214,7 +254,11 @@ func (node *Node) print(indent string, opts *Options) {
if msgs := strings.Split(err, ": "); len(msgs) > 1 {
err = msgs[1]
}
- fmt.Printf("%s [%s]\n", node.path, err)
+ name := node.path
+ if !opts.FullPath {
+ name = filepath.Base(name)
+ }
+ fmt.Fprintf(opts.OutFile, "%s [%s]\n", name, err)
return
}
if !node.IsDir() {
@@ -302,7 +346,7 @@ func (node *Node) print(indent string, opts *Options) {
}
// Colorize
if opts.Colorize {
- name = ANSIColor(node, name)
+ name = opts.color(node, name)
}
// IsSymlink
if node.Mode()&os.ModeSymlink == os.ModeSymlink {
@@ -316,7 +360,7 @@ func (node *Node) print(indent string, opts *Options) {
}
fi, err := opts.Fs.Stat(targetPath)
if opts.Colorize && fi != nil {
- vtarget = ANSIColor(&Node{FileInfo: fi, path: vtarget}, vtarget)
+ vtarget = opts.color(&Node{FileInfo: fi, path: vtarget}, vtarget)
}
name = fmt.Sprintf("%s -> %s", name, vtarget)
// Follow symbolic links like directories
diff --git a/node_test.go b/node_test.go
index 5a0a5f1..8ead508 100644
--- a/node_test.go
+++ b/node_test.go
@@ -1,6 +1,7 @@
package tree
import (
+ "errors"
"os"
"syscall"
"testing"
@@ -63,6 +64,9 @@ func (fs *MockFs) addFile(path string, file *file) *MockFs {
}
func (fs *MockFs) Stat(path string) (os.FileInfo, error) {
+ if path == "root/bad" {
+ return nil, errors.New("stat failed")
+ }
return fs.files[path], nil
}
func (fs *MockFs) ReadDir(path string) ([]string, error) {
@@ -109,47 +113,133 @@ var listTests = []treeTest{
{"basic", &Options{Fs: fs, OutFile: out}, `root
├── a
├── b
-└── c
- ├── d
- └── e
-`, 1, 4},
+├── c
+│ ├── d
+│ ├── e
+│ ├── g
+│ │ ├── h
+│ │ └── i
+│ └── k
+└── j
+`, 2, 8},
{"all", &Options{Fs: fs, OutFile: out, All: true, NoSort: true}, `root
├── a
├── b
-└── c
- ├── d
- ├── e
- └── .f
-`, 1, 5},
+├── c
+│ ├── d
+│ ├── e
+│ ├── .f
+│ ├── g
+│ │ ├── h
+│ │ └── i
+│ └── k
+└── j
+`, 2, 9},
{"dirs", &Options{Fs: fs, OutFile: out, DirsOnly: true}, `root
└── c
-`, 1, 0},
+ └── g
+`, 2, 0},
{"fullPath", &Options{Fs: fs, OutFile: out, FullPath: true}, `root
├── root/a
├── root/b
-└── root/c
- ├── root/c/d
- └── root/c/e
-`, 1, 4},
+├── root/c
+│ ├── root/c/d
+│ ├── root/c/e
+│ ├── root/c/g
+│ │ ├── root/c/g/h
+│ │ └── root/c/g/i
+│ └── root/c/k
+└── root/j
+`, 2, 8},
{"deepLevel", &Options{Fs: fs, OutFile: out, DeepLevel: 1}, `root
├── a
├── b
+├── c
+└── j
+`, 1, 3},
+ {"pattern (a|e|i)", &Options{Fs: fs, OutFile: out, Pattern: "(a|e|i)"}, `root
+├── a
└── c
-`, 1, 2},
- {"pattern", &Options{Fs: fs, OutFile: out, Pattern: "(a|e)"}, `root
+ ├── e
+ └── g
+ └── i
+`, 2, 3},
+ {"pattern (x) + 0 files", &Options{Fs: fs, OutFile: out, Pattern: "(x)"}, `root
+└── c
+ └── g
+`, 2, 0},
+ {"ipattern (a|e|i)", &Options{Fs: fs, OutFile: out, IPattern: "(a|e|i)"}, `root
+├── b
+├── c
+│ ├── d
+│ ├── g
+│ │ └── h
+│ └── k
+└── j
+`, 2, 5},
+ {"pattern (A) + ignore-case", &Options{Fs: fs, OutFile: out, Pattern: "(A)", IgnoreCase: true}, `root
├── a
└── c
- └── e
-`, 1, 2},
- {"ipattern", &Options{Fs: fs, OutFile: out, IPattern: "(a|e)"}, `root
+ └── g
+`, 2, 1},
+ {"pattern (A) + ignore-case + prune", &Options{Fs: fs, OutFile: out, Pattern: "(A)", Prune: true, IgnoreCase: true}, `root
+└── a
+`, 0, 1},
+ {"pattern (a) + prune", &Options{Fs: fs, OutFile: out, Pattern: "(a)", Prune: true}, `root
+└── a
+`, 0, 1},
+ {"pattern (c) + matchdirs", &Options{Fs: fs, OutFile: out, Pattern: "(c)", MatchDirs: true}, `root
+└── c
+ ├── d
+ ├── e
+ ├── g
+ └── k
+`, 2, 3},
+ {"pattern (c.*) + matchdirs", &Options{Fs: fs, OutFile: out, Pattern: "(c.*)", MatchDirs: true}, `root
+└── c
+ ├── d
+ ├── e
+ ├── g
+ │ ├── h
+ │ └── i
+ └── k
+`, 2, 5},
+ {"ipattern (c) + matchdirs", &Options{Fs: fs, OutFile: out, IPattern: "(c)", MatchDirs: true}, `root
+├── a
+├── b
+└── j
+`, 0, 3},
+ {"ipattern (g) + matchdirs", &Options{Fs: fs, OutFile: out, IPattern: "(g)", MatchDirs: true}, `root
+├── a
├── b
+├── c
+│ ├── d
+│ ├── e
+│ └── k
+└── j
+`, 1, 6},
+ {"ipattern (a|e|i|h) + matchdirs + prune", &Options{Fs: fs, OutFile: out, IPattern: "(a|e|i|h)", MatchDirs: true, Prune: true}, `root
+├── b
+├── c
+│ ├── d
+│ └── k
+└── j
+`, 1, 4},
+ {"pattern (d|e) + prune", &Options{Fs: fs, OutFile: out, Pattern: "(d|e)", Prune: true}, `root
└── c
- └── d
+ ├── d
+ └── e
`, 1, 2},
- {"ignore-case", &Options{Fs: fs, OutFile: out, Pattern: "(A)", IgnoreCase: true}, `root
-├── a
+ {"pattern (c.*) + matchdirs + prune ", &Options{Fs: fs, OutFile: out, Pattern: "(c.*)", Prune: true, MatchDirs: true}, `root
└── c
-`, 1, 1}}
+ ├── d
+ ├── e
+ ├── g
+ │ ├── h
+ │ └── i
+ └── k
+`, 2, 5},
+}
func TestSimple(t *testing.T) {
root := &file{
@@ -165,8 +255,18 @@ func TestSimple(t *testing.T) {
{name: "d", size: 50},
{name: "e", size: 50},
{name: ".f", size: 0},
+ {
+ name: "g",
+ size: 100,
+ files: []*file{
+ {name: "h", size: 50},
+ {name: "i", size: 50},
+ },
+ },
+ {name: "k", size: 50},
},
},
+ {name: "j", size: 50},
},
}
fs.clean().addFile(root.name, root)
@@ -177,7 +277,7 @@ func TestSimple(t *testing.T) {
t.Errorf("wrong dir count for test %q:\ngot:\n%d\nexpected:\n%d", test.name, d, test.dirs)
}
if f != test.files {
- t.Errorf("wrong dir count for test %q:\ngot:\n%d\nexpected:\n%d", test.name, d, test.files)
+ t.Errorf("wrong file count for test %q:\ngot:\n%d\nexpected:\n%d", test.name, f, test.files)
}
inf.Print(test.opts)
if !out.equal(test.expected) {
@@ -396,3 +496,158 @@ func TestCount(t *testing.T) {
t.Errorf("TestCount - expect (dir, file) count to be equal to (7, 8)\n%s", out.str)
}
}
+
+var errorTests = []treeTest{
+ {"basic", &Options{Fs: fs, OutFile: out}, `root
+├── a
+├── b
+├── j
+└── bad [stat failed]
+`, 0, 3},
+ {"all", &Options{Fs: fs, OutFile: out, All: true, NoSort: true}, `root
+├── a
+├── b
+├── j
+└── bad [stat failed]
+`, 0, 3},
+ {"dirs", &Options{Fs: fs, OutFile: out, DirsOnly: true}, `root
+└── bad [stat failed]
+`, 0, 0},
+ {"fullPath", &Options{Fs: fs, OutFile: out, FullPath: true}, `root
+├── root/a
+├── root/b
+├── root/j
+└── root/bad [stat failed]
+`, 0, 3},
+ {"deepLevel", &Options{Fs: fs, OutFile: out, DeepLevel: 1}, `root
+├── a
+├── b
+├── j
+└── bad [stat failed]
+`, 0, 3},
+ {"pattern (a|e|i)", &Options{Fs: fs, OutFile: out, Pattern: "(a|e|i)"}, `root
+├── a
+└── bad [stat failed]
+`, 0, 1},
+ {"pattern (x) + 0 files", &Options{Fs: fs, OutFile: out, Pattern: "(x)"}, `root
+└── bad [stat failed]
+`, 0, 0},
+ {"ipattern (a|e|i)", &Options{Fs: fs, OutFile: out, IPattern: "(a|e|i)"}, `root
+├── b
+├── j
+└── bad [stat failed]
+`, 0, 2},
+ {"pattern (A) + ignore-case", &Options{Fs: fs, OutFile: out, Pattern: "(A)", IgnoreCase: true}, `root
+├── a
+└── bad [stat failed]
+`, 0, 1},
+ {"pattern (A) + ignore-case + prune", &Options{Fs: fs, OutFile: out, Pattern: "(A)", Prune: true, IgnoreCase: true}, `root
+├── a
+└── bad [stat failed]
+`, 0, 1},
+ {"pattern (a) + prune", &Options{Fs: fs, OutFile: out, Pattern: "(a)", Prune: true}, `root
+├── a
+└── bad [stat failed]
+`, 0, 1},
+ {"pattern (c) + matchdirs", &Options{Fs: fs, OutFile: out, Pattern: "(c)", MatchDirs: true}, `root
+└── bad [stat failed]
+`, 0, 0},
+ {"pattern (c.*) + matchdirs", &Options{Fs: fs, OutFile: out, Pattern: "(c.*)", MatchDirs: true}, `root
+└── bad [stat failed]
+`, 0, 0},
+ {"ipattern (c) + matchdirs", &Options{Fs: fs, OutFile: out, IPattern: "(c)", MatchDirs: true}, `root
+├── a
+├── b
+├── j
+└── bad [stat failed]
+`, 0, 3},
+ {"ipattern (g) + matchdirs", &Options{Fs: fs, OutFile: out, IPattern: "(g)", MatchDirs: true}, `root
+├── a
+├── b
+├── j
+└── bad [stat failed]
+`, 0, 3},
+ {"ipattern (a|e|i|h) + matchdirs + prune", &Options{Fs: fs, OutFile: out, IPattern: "(a|e|i|h)", MatchDirs: true, Prune: true}, `root
+├── b
+├── j
+└── bad [stat failed]
+`, 0, 2},
+ {"pattern (d|e) + prune", &Options{Fs: fs, OutFile: out, Pattern: "(d|e)", Prune: true}, `root
+└── bad [stat failed]
+`, 0, 0},
+ {"pattern (c.*) + matchdirs + prune ", &Options{Fs: fs, OutFile: out, Pattern: "(c.*)", Prune: true, MatchDirs: true}, `root
+└── bad [stat failed]
+`, 0, 0},
+
+ {"name-sort", &Options{Fs: fs, OutFile: out, NameSort: true}, `root
+├── a
+├── b
+├── j
+└── bad [stat failed]
+`, 0, 3},
+ {"dirs-first sort", &Options{Fs: fs, OutFile: out, DirSort: true}, `root
+├── a
+├── b
+├── j
+└── bad [stat failed]
+`, 0, 3},
+ {"reverse sort", &Options{Fs: fs, OutFile: out, ReverSort: true, NameSort: true}, `root
+├── bad [stat failed]
+├── j
+├── b
+└── a
+`, 0, 3},
+ {"no-sort", &Options{Fs: fs, OutFile: out, NoSort: true, DirSort: true}, `root
+├── a
+├── b
+├── j
+└── bad [stat failed]
+`, 0, 3},
+ {"size-sort", &Options{Fs: fs, OutFile: out, SizeSort: true}, `root
+├── a
+├── b
+├── j
+└── bad [stat failed]
+`, 0, 3},
+ {"last-mod-sort", &Options{Fs: fs, OutFile: out, ModSort: true}, `root
+├── a
+├── b
+├── j
+└── bad [stat failed]
+`, 0, 3},
+ {"c-time-sort", &Options{Fs: fs, OutFile: out, CTimeSort: true}, `root
+├── a
+├── b
+├── j
+└── bad [stat failed]
+`, 0, 3},
+}
+
+func TestError(t *testing.T) {
+ root := &file{
+ name: "root",
+ size: 200,
+ files: []*file{
+ {name: "a", size: 50},
+ {name: "b", size: 50},
+ {name: "j", size: 50},
+ {name: "bad", size: 50}, // stat fails on this file
+ },
+ }
+ fs.clean().addFile(root.name, root)
+ for _, test := range errorTests {
+ inf := New(root.name)
+ d, f := inf.Visit(test.opts)
+ if d != test.dirs {
+ t.Errorf("wrong dir count for test %q:\ngot:\n%d\nexpected:\n%d", test.name, d, test.dirs)
+ }
+ if f != test.files {
+ t.Errorf("wrong file count for test %q:\ngot:\n%d\nexpected:\n%d", test.name, f, test.files)
+ }
+ 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/sort.go b/sort.go
index 5529993..0ee8a07 100644
--- a/sort.go
+++ b/sort.go
@@ -17,22 +17,38 @@ func (b ByFunc) Less(i, j int) bool {
type SortFunc func(f1, f2 os.FileInfo) bool
func ModSort(f1, f2 os.FileInfo) bool {
+ // This ensures any nil os.FileInfos sort at the end
+ if f1 == nil || f2 == nil {
+ return f2 == nil
+ }
return f1.ModTime().Before(f2.ModTime())
}
func DirSort(f1, f2 os.FileInfo) bool {
+ if f1 == nil || f2 == nil {
+ return f2 == nil
+ }
return f1.IsDir() && !f2.IsDir()
}
func SizeSort(f1, f2 os.FileInfo) bool {
+ if f1 == nil || f2 == nil {
+ return f2 == nil
+ }
return f1.Size() < f2.Size()
}
func NameSort(f1, f2 os.FileInfo) bool {
+ if f1 == nil || f2 == nil {
+ return f2 == nil
+ }
return f1.Name() < f2.Name()
}
func VerSort(f1, f2 os.FileInfo) bool {
+ if f1 == nil || f2 == nil {
+ return f2 == nil
+ }
return NaturalLess(f1.Name(), f2.Name())
}
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in second set of .debs but not in first
-rw-r--r-- root/root /usr/share/gocode/src/github.com/a8m/tree/go.mod
No differences were encountered in the control files