Skip to content

Commit 999b447

Browse files
committed
utils: add Walk function to walk over a filesystem
Adapted from the official filepath.Walk, this function allows to walk trough a whole filesystem and discover all its files
1 parent 3bf3fe5 commit 999b447

File tree

2 files changed

+264
-0
lines changed

2 files changed

+264
-0
lines changed

util/walk.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package util
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
7+
"github.com/go-git/go-billy/v5"
8+
)
9+
10+
// walk recursively descends path, calling walkFn
11+
// adapted from https://golang.org/src/path/filepath/path.go
12+
func walk(fs billy.Filesystem, path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
13+
if !info.IsDir() {
14+
return walkFn(path, info, nil)
15+
}
16+
17+
names, err := readdirnames(fs, path)
18+
err1 := walkFn(path, info, err)
19+
// If err != nil, walk can't walk into this directory.
20+
// err1 != nil means walkFn want walk to skip this directory or stop walking.
21+
// Therefore, if one of err and err1 isn't nil, walk will return.
22+
if err != nil || err1 != nil {
23+
// The caller's behavior is controlled by the return value, which is decided
24+
// by walkFn. walkFn may ignore err and return nil.
25+
// If walkFn returns SkipDir, it will be handled by the caller.
26+
// So walk should return whatever walkFn returns.
27+
return err1
28+
}
29+
30+
for _, name := range names {
31+
filename := filepath.Join(path, name)
32+
fileInfo, err := fs.Lstat(filename)
33+
if err != nil {
34+
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
35+
return err
36+
}
37+
} else {
38+
err = walk(fs, filename, fileInfo, walkFn)
39+
if err != nil {
40+
if !fileInfo.IsDir() || err != filepath.SkipDir {
41+
return err
42+
}
43+
}
44+
}
45+
}
46+
return nil
47+
}
48+
49+
// Walk walks the file tree rooted at root, calling fn for each file or directory in the tree, including root.
50+
//
51+
// All errors that arise visiting files and directories are filtered by fn: see the WalkFunc documentation for details.
52+
//
53+
// The files are walked in lexical order, which makes the output deterministic but requires Walk to read an entire directory into memory before proceeding to walk that directory.
54+
//
55+
// Walk does not follow symbolic links.
56+
//
57+
// adapted from https://github.com/golang/go/blob/3b770f2ccb1fa6fecc22ea822a19447b10b70c5c/src/path/filepath/path.go#L500
58+
func Walk(fs billy.Filesystem, root string, walkFn filepath.WalkFunc) error {
59+
info, err := fs.Lstat(root)
60+
61+
if err != nil {
62+
err = walkFn(root, nil, err)
63+
} else {
64+
err = walk(fs, root, info, walkFn)
65+
}
66+
if err == filepath.SkipDir {
67+
return nil
68+
}
69+
return err
70+
}

util/walk_test.go

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
package util_test
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io/fs"
7+
"path/filepath"
8+
"reflect"
9+
"testing"
10+
11+
"github.com/go-git/go-billy/v5"
12+
"github.com/go-git/go-billy/v5/memfs"
13+
"github.com/go-git/go-billy/v5/util"
14+
15+
. "gopkg.in/check.v1"
16+
)
17+
18+
type WalkSuite struct{}
19+
20+
func TestWalk(t *testing.T) { TestingT(t) }
21+
22+
var _ = Suite(&WalkSuite{})
23+
24+
func (s *WalkSuite) TestWalkCanSkipTopDirectory(c *C) {
25+
filesystem := memfs.New()
26+
c.Assert(util.Walk(filesystem, "/root/that/does/not/exist", func(path string, info fs.FileInfo, err error) error { return filepath.SkipDir }), IsNil)
27+
}
28+
29+
func (s *WalkSuite) TestWalkReturnsAnErrorWhenRootDoesNotExist(c *C) {
30+
filesystem := memfs.New()
31+
c.Assert(util.Walk(filesystem, "/root/that/does/not/exist", func(path string, info fs.FileInfo, err error) error { return err }), NotNil)
32+
}
33+
34+
func (s *WalkSuite) TestWalkOnPlainFile(c *C) {
35+
filesystem := memfs.New()
36+
createFile(c, filesystem, "./README.md")
37+
discoveredPaths := []string{}
38+
c.Assert(util.Walk(filesystem, "./README.md", func(path string, info fs.FileInfo, err error) error {
39+
discoveredPaths = append(discoveredPaths, path)
40+
return nil
41+
}), IsNil)
42+
c.Assert(discoveredPaths, DeepEquals, []string{"./README.md"})
43+
}
44+
45+
func (s *WalkSuite) TestWalkOnExistingFolder(c *C) {
46+
filesystem := memfs.New()
47+
createFile(c, filesystem, "path/to/some/subfolder/that/contain/file")
48+
createFile(c, filesystem, "path/to/some/file")
49+
discoveredPaths := []string{}
50+
c.Assert(util.Walk(filesystem, "path", func(path string, info fs.FileInfo, err error) error {
51+
discoveredPaths = append(discoveredPaths, path)
52+
return nil
53+
}), IsNil)
54+
c.Assert(discoveredPaths, Contains, "path")
55+
c.Assert(discoveredPaths, Contains, "path/to")
56+
c.Assert(discoveredPaths, Contains, "path/to/some")
57+
c.Assert(discoveredPaths, Contains, "path/to/some/file")
58+
c.Assert(discoveredPaths, Contains, "path/to/some/subfolder")
59+
c.Assert(discoveredPaths, Contains, "path/to/some/subfolder/that")
60+
c.Assert(discoveredPaths, Contains, "path/to/some/subfolder/that/contain")
61+
c.Assert(discoveredPaths, Contains, "path/to/some/subfolder/that/contain/file")
62+
}
63+
64+
func (s *WalkSuite) TestWalkCanSkipFolder(c *C) {
65+
filesystem := memfs.New()
66+
createFile(c, filesystem, "path/to/some/subfolder/that/contain/file")
67+
createFile(c, filesystem, "path/to/some/file")
68+
discoveredPaths := []string{}
69+
c.Assert(util.Walk(filesystem, "path", func(path string, info fs.FileInfo, err error) error {
70+
discoveredPaths = append(discoveredPaths, path)
71+
if path == "path/to/some/subfolder" {
72+
return filepath.SkipDir
73+
}
74+
return nil
75+
}), IsNil)
76+
c.Assert(discoveredPaths, Contains, "path")
77+
c.Assert(discoveredPaths, Contains, "path/to")
78+
c.Assert(discoveredPaths, Contains, "path/to/some")
79+
c.Assert(discoveredPaths, Contains, "path/to/some/file")
80+
c.Assert(discoveredPaths, Contains, "path/to/some/subfolder")
81+
c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that")
82+
c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that/contain")
83+
c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that/contain/file")
84+
}
85+
86+
func (s *WalkSuite) TestWalkStopsOnError(c *C) {
87+
filesystem := memfs.New()
88+
createFile(c, filesystem, "path/to/some/subfolder/that/contain/file")
89+
createFile(c, filesystem, "path/to/some/file")
90+
discoveredPaths := []string{}
91+
c.Assert(util.Walk(filesystem, "path", func(path string, info fs.FileInfo, err error) error {
92+
discoveredPaths = append(discoveredPaths, path)
93+
if path == "path/to/some/subfolder" {
94+
return errors.New("uncaught error")
95+
}
96+
return nil
97+
}), NotNil)
98+
c.Assert(discoveredPaths, Contains, "path")
99+
c.Assert(discoveredPaths, Contains, "path/to")
100+
c.Assert(discoveredPaths, Contains, "path/to/some")
101+
c.Assert(discoveredPaths, Contains, "path/to/some/file")
102+
c.Assert(discoveredPaths, Contains, "path/to/some/subfolder")
103+
c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that")
104+
c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that/contain")
105+
c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that/contain/file")
106+
}
107+
108+
func (s *WalkSuite) TestWalkForwardsStatErrors(c *C) {
109+
memFilesystem := memfs.New()
110+
filesystem := &fnFs{
111+
Filesystem: memFilesystem,
112+
lstat: func(path string) (fs.FileInfo, error) {
113+
if path == "path/to/some/subfolder" {
114+
return nil, errors.New("uncaught error")
115+
}
116+
return memFilesystem.Lstat(path)
117+
},
118+
}
119+
120+
createFile(c, filesystem, "path/to/some/subfolder/that/contain/file")
121+
createFile(c, filesystem, "path/to/some/file")
122+
discoveredPaths := []string{}
123+
c.Assert(util.Walk(filesystem, "path", func(path string, info fs.FileInfo, err error) error {
124+
discoveredPaths = append(discoveredPaths, path)
125+
if path == "path/to/some/subfolder" {
126+
c.Assert(err, NotNil)
127+
}
128+
return err
129+
}), NotNil)
130+
c.Assert(discoveredPaths, Contains, "path")
131+
c.Assert(discoveredPaths, Contains, "path/to")
132+
c.Assert(discoveredPaths, Contains, "path/to/some")
133+
c.Assert(discoveredPaths, Contains, "path/to/some/file")
134+
c.Assert(discoveredPaths, Contains, "path/to/some/subfolder")
135+
c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that")
136+
c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that/contain")
137+
c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that/contain/file")
138+
}
139+
140+
func createFile(c *C, filesystem billy.Filesystem, path string) {
141+
fd, err := filesystem.Create(path)
142+
c.Assert(err, IsNil)
143+
if err != nil {
144+
fd.Close()
145+
}
146+
}
147+
148+
type fnFs struct {
149+
billy.Filesystem
150+
lstat func(path string) (fs.FileInfo, error)
151+
}
152+
153+
func (f *fnFs) Lstat(path string) (fs.FileInfo, error) {
154+
if f.lstat != nil {
155+
return f.lstat(path)
156+
}
157+
return nil, errors.New("not implemented")
158+
}
159+
160+
type containsChecker struct {
161+
*CheckerInfo
162+
}
163+
164+
func (checker *containsChecker) Check(params []interface{}, names []string) (result bool, err string) {
165+
defer func() {
166+
if v := recover(); v != nil {
167+
result = false
168+
err = fmt.Sprint(v)
169+
}
170+
}()
171+
172+
value := reflect.ValueOf(params[0])
173+
result = false
174+
err = fmt.Sprintf("%v does not contain %v", params[0], params[1])
175+
switch value.Kind() {
176+
case reflect.Array, reflect.Slice:
177+
for i := 0; i < value.Len(); i++ {
178+
r := reflect.DeepEqual(value.Index(i).Interface(), params[1])
179+
if r {
180+
result = true
181+
err = ""
182+
}
183+
}
184+
default:
185+
return false, "obtained value type is not iterable"
186+
}
187+
return
188+
}
189+
190+
var Contains Checker = &containsChecker{
191+
&CheckerInfo{Name: "Contains", Params: []string{"obtained", "expected"}},
192+
}
193+
194+
var NotContain Checker = Not(Contains)

0 commit comments

Comments
 (0)