Skip to content

Commit 02a3ced

Browse files
authoredFeb 11, 2022
Merge pull request #14 from arduino/readdir_filters
Added ReadDirRecursiveFiltered and some minor optimizations
2 parents 43bf0b4 + 4ad8389 commit 02a3ced

File tree

4 files changed

+407
-80
lines changed

4 files changed

+407
-80
lines changed
 

‎paths.go

-41
Original file line numberDiff line numberDiff line change
@@ -306,47 +306,6 @@ func (p *Path) IsDirCheck() (bool, error) {
306306
return false, err
307307
}
308308

309-
// ReadDir returns a PathList containing the content of the directory
310-
// pointed by the current Path
311-
func (p *Path) ReadDir() (PathList, error) {
312-
infos, err := ioutil.ReadDir(p.path)
313-
if err != nil {
314-
return nil, err
315-
}
316-
paths := PathList{}
317-
for _, info := range infos {
318-
path := p.Clone().Join(info.Name())
319-
paths.Add(path)
320-
}
321-
return paths, nil
322-
}
323-
324-
// ReadDirRecursive returns a PathList containing the content of the directory
325-
// and its subdirectories pointed by the current Path
326-
func (p *Path) ReadDirRecursive() (PathList, error) {
327-
infos, err := ioutil.ReadDir(p.path)
328-
if err != nil {
329-
return nil, err
330-
}
331-
paths := PathList{}
332-
for _, info := range infos {
333-
path := p.Clone().Join(info.Name())
334-
paths.Add(path)
335-
336-
if isDir, err := path.IsDirCheck(); err != nil {
337-
return nil, err
338-
} else if isDir {
339-
subPaths, err := path.ReadDirRecursive()
340-
if err != nil {
341-
return nil, err
342-
}
343-
paths.AddAll(subPaths)
344-
}
345-
346-
}
347-
return paths, nil
348-
}
349-
350309
// CopyTo copies the contents of the file named src to the file named
351310
// by dst. The file will be created if it does not already exist. If the
352311
// destination file exists, all it's contents will be replaced by the contents

‎paths_test.go

-39
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
package paths
3131

3232
import (
33-
"os"
3433
"path/filepath"
3534
"runtime"
3635
"strings"
@@ -255,44 +254,6 @@ func TestParents(t *testing.T) {
255254
pathEqualsTo(t, ".", parents2[4])
256255
}
257256

258-
func TestReadDirRecursive(t *testing.T) {
259-
testPath := New("_testdata")
260-
261-
list, err := testPath.ReadDirRecursive()
262-
require.NoError(t, err)
263-
require.Len(t, list, 16)
264-
265-
pathEqualsTo(t, "_testdata/anotherFile", list[0])
266-
pathEqualsTo(t, "_testdata/file", list[1])
267-
pathEqualsTo(t, "_testdata/folder", list[2])
268-
pathEqualsTo(t, "_testdata/folder/.hidden", list[3])
269-
pathEqualsTo(t, "_testdata/folder/file2", list[4])
270-
pathEqualsTo(t, "_testdata/folder/file3", list[5])
271-
pathEqualsTo(t, "_testdata/folder/subfolder", list[6])
272-
pathEqualsTo(t, "_testdata/folder/subfolder/file4", list[7])
273-
pathEqualsTo(t, "_testdata/symlinktofolder", list[8])
274-
pathEqualsTo(t, "_testdata/symlinktofolder/.hidden", list[9])
275-
pathEqualsTo(t, "_testdata/symlinktofolder/file2", list[10])
276-
pathEqualsTo(t, "_testdata/symlinktofolder/file3", list[11])
277-
pathEqualsTo(t, "_testdata/symlinktofolder/subfolder", list[12])
278-
pathEqualsTo(t, "_testdata/symlinktofolder/subfolder/file4", list[13])
279-
pathEqualsTo(t, "_testdata/test.txt", list[14])
280-
pathEqualsTo(t, "_testdata/test.txt.gz", list[15])
281-
282-
// Test symlink loop
283-
tmp, err := MkTempDir("", "")
284-
require.NoError(t, err)
285-
defer tmp.RemoveAll()
286-
287-
folder := tmp.Join("folder")
288-
err = os.Symlink(tmp.String(), folder.String())
289-
require.NoError(t, err)
290-
291-
l, err := tmp.ReadDirRecursive()
292-
require.Error(t, err)
293-
require.Nil(t, l)
294-
}
295-
296257
func TestFilterDirs(t *testing.T) {
297258
testPath := New("_testdata")
298259

‎readdir.go

+228
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/*
2+
* This file is part of PathsHelper library.
3+
*
4+
* Copyright 2018-2022 Arduino AG (http://www.arduino.cc/)
5+
*
6+
* PathsHelper library is free software; you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation; either version 2 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with this program; if not, write to the Free Software
18+
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19+
*
20+
* As a special exception, you may use this file as part of a free software
21+
* library without restriction. Specifically, if other files instantiate
22+
* templates or use macros or inline functions from this file, or you compile
23+
* this file and link it with other files to produce an executable, this
24+
* file does not by itself cause the resulting executable to be covered by
25+
* the GNU General Public License. This exception does not however
26+
* invalidate any other reasons why the executable file might be covered by
27+
* the GNU General Public License.
28+
*/
29+
30+
package paths
31+
32+
import (
33+
"io/ioutil"
34+
"strings"
35+
)
36+
37+
// ReadDirFilter is a filter for Path.ReadDir and Path.ReadDirRecursive methods.
38+
// The filter should return true to accept a file or false to reject it.
39+
type ReadDirFilter func(file *Path) bool
40+
41+
// ReadDir returns a PathList containing the content of the directory
42+
// pointed by the current Path. The resulting list is filtered by the given filters chained.
43+
func (p *Path) ReadDir(filters ...ReadDirFilter) (PathList, error) {
44+
infos, err := ioutil.ReadDir(p.path)
45+
if err != nil {
46+
return nil, err
47+
}
48+
49+
accept := func(p *Path) bool {
50+
for _, filter := range filters {
51+
if !filter(p) {
52+
return false
53+
}
54+
}
55+
return true
56+
}
57+
58+
paths := PathList{}
59+
for _, info := range infos {
60+
path := p.Join(info.Name())
61+
if !accept(path) {
62+
continue
63+
}
64+
paths.Add(path)
65+
}
66+
return paths, nil
67+
}
68+
69+
// ReadDirRecursive returns a PathList containing the content of the directory
70+
// and its subdirectories pointed by the current Path
71+
func (p *Path) ReadDirRecursive() (PathList, error) {
72+
infos, err := ioutil.ReadDir(p.path)
73+
if err != nil {
74+
return nil, err
75+
}
76+
paths := PathList{}
77+
for _, info := range infos {
78+
path := p.Join(info.Name())
79+
paths.Add(path)
80+
81+
if isDir, err := path.IsDirCheck(); err != nil {
82+
return nil, err
83+
} else if isDir {
84+
subPaths, err := path.ReadDirRecursive()
85+
if err != nil {
86+
return nil, err
87+
}
88+
paths.AddAll(subPaths)
89+
}
90+
91+
}
92+
return paths, nil
93+
}
94+
95+
// ReadDirRecursiveFiltered returns a PathList containing the content of the directory
96+
// and its subdirectories pointed by the current Path, filtered by the given skipFilter
97+
// and filters:
98+
// - `recursionFilter` is a filter that is checked to determine if the subdirectory must
99+
// by visited recursively (if the filter rejects the entry, the entry is not visited
100+
// but can still be added to the result)
101+
// - `filters` are the filters that are checked to determine if the entry should be
102+
// added to the resulting PathList
103+
func (p *Path) ReadDirRecursiveFiltered(recursionFilter ReadDirFilter, filters ...ReadDirFilter) (PathList, error) {
104+
infos, err := ioutil.ReadDir(p.path)
105+
if err != nil {
106+
return nil, err
107+
}
108+
109+
accept := func(p *Path) bool {
110+
for _, filter := range filters {
111+
if !filter(p) {
112+
return false
113+
}
114+
}
115+
return true
116+
}
117+
118+
paths := PathList{}
119+
for _, info := range infos {
120+
path := p.Join(info.Name())
121+
122+
if accept(path) {
123+
paths.Add(path)
124+
}
125+
126+
if recursionFilter == nil || recursionFilter(path) {
127+
if isDir, err := path.IsDirCheck(); err != nil {
128+
return nil, err
129+
} else if isDir {
130+
subPaths, err := path.ReadDirRecursiveFiltered(recursionFilter, filters...)
131+
if err != nil {
132+
return nil, err
133+
}
134+
paths.AddAll(subPaths)
135+
}
136+
}
137+
}
138+
return paths, nil
139+
}
140+
141+
// FilterDirectories is a ReadDirFilter that accepts only directories
142+
func FilterDirectories() ReadDirFilter {
143+
return func(path *Path) bool {
144+
return path.IsDir()
145+
}
146+
}
147+
148+
// FilterOutDirectories is a ReadDirFilter that rejects all directories
149+
func FilterOutDirectories() ReadDirFilter {
150+
return func(path *Path) bool {
151+
return !path.IsDir()
152+
}
153+
}
154+
155+
// FilterSuffixes creates a ReadDirFilter that accepts only the given
156+
// filename suffixes
157+
func FilterSuffixes(allowedSuffixes ...string) ReadDirFilter {
158+
return func(file *Path) bool {
159+
for _, suffix := range allowedSuffixes {
160+
if strings.HasSuffix(file.String(), suffix) {
161+
return true
162+
}
163+
}
164+
return false
165+
}
166+
}
167+
168+
// FilterOutSuffixes creates a ReadDirFilter that rejects all the given
169+
// filename suffixes
170+
func FilterOutSuffixes(rejectedSuffixes ...string) ReadDirFilter {
171+
return func(file *Path) bool {
172+
for _, suffix := range rejectedSuffixes {
173+
if strings.HasSuffix(file.String(), suffix) {
174+
return false
175+
}
176+
}
177+
return true
178+
}
179+
}
180+
181+
// FilterPrefixes creates a ReadDirFilter that accepts only the given
182+
// filename prefixes
183+
func FilterPrefixes(allowedPrefixes ...string) ReadDirFilter {
184+
return func(file *Path) bool {
185+
name := file.Base()
186+
for _, prefix := range allowedPrefixes {
187+
if strings.HasPrefix(name, prefix) {
188+
return true
189+
}
190+
}
191+
return false
192+
}
193+
}
194+
195+
// FilterOutPrefixes creates a ReadDirFilter that rejects all the given
196+
// filename prefixes
197+
func FilterOutPrefixes(rejectedPrefixes ...string) ReadDirFilter {
198+
return func(file *Path) bool {
199+
name := file.Base()
200+
for _, prefix := range rejectedPrefixes {
201+
if strings.HasPrefix(name, prefix) {
202+
return false
203+
}
204+
}
205+
return true
206+
}
207+
}
208+
209+
// OrFilter creates a ReadDirFilter that accepts all items that are accepted by x or by y
210+
func OrFilter(x, y ReadDirFilter) ReadDirFilter {
211+
return func(path *Path) bool {
212+
return x(path) || y(path)
213+
}
214+
}
215+
216+
// AndFilter creates a ReadDirFilter that accepts all items that are accepted by both x and y
217+
func AndFilter(x, y ReadDirFilter) ReadDirFilter {
218+
return func(path *Path) bool {
219+
return x(path) && y(path)
220+
}
221+
}
222+
223+
// NotFilter creates a ReadDifFilter that accepts all items rejected by x and viceversa
224+
func NotFilter(x ReadDirFilter) ReadDirFilter {
225+
return func(path *Path) bool {
226+
return !x(path)
227+
}
228+
}

‎readdir_test.go

+179
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*
2+
* This file is part of PathsHelper library.
3+
*
4+
* Copyright 2018-2022 Arduino AG (http://www.arduino.cc/)
5+
*
6+
* PathsHelper library is free software; you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation; either version 2 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with this program; if not, write to the Free Software
18+
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19+
*
20+
* As a special exception, you may use this file as part of a free software
21+
* library without restriction. Specifically, if other files instantiate
22+
* templates or use macros or inline functions from this file, or you compile
23+
* this file and link it with other files to produce an executable, this
24+
* file does not by itself cause the resulting executable to be covered by
25+
* the GNU General Public License. This exception does not however
26+
* invalidate any other reasons why the executable file might be covered by
27+
* the GNU General Public License.
28+
*/
29+
30+
package paths
31+
32+
import (
33+
"fmt"
34+
"os"
35+
"testing"
36+
37+
"github.com/stretchr/testify/require"
38+
)
39+
40+
func TestReadDirRecursive(t *testing.T) {
41+
testPath := New("_testdata")
42+
43+
list, err := testPath.ReadDirRecursive()
44+
require.NoError(t, err)
45+
require.Len(t, list, 16)
46+
47+
pathEqualsTo(t, "_testdata/anotherFile", list[0])
48+
pathEqualsTo(t, "_testdata/file", list[1])
49+
pathEqualsTo(t, "_testdata/folder", list[2])
50+
pathEqualsTo(t, "_testdata/folder/.hidden", list[3])
51+
pathEqualsTo(t, "_testdata/folder/file2", list[4])
52+
pathEqualsTo(t, "_testdata/folder/file3", list[5])
53+
pathEqualsTo(t, "_testdata/folder/subfolder", list[6])
54+
pathEqualsTo(t, "_testdata/folder/subfolder/file4", list[7])
55+
pathEqualsTo(t, "_testdata/symlinktofolder", list[8])
56+
pathEqualsTo(t, "_testdata/symlinktofolder/.hidden", list[9])
57+
pathEqualsTo(t, "_testdata/symlinktofolder/file2", list[10])
58+
pathEqualsTo(t, "_testdata/symlinktofolder/file3", list[11])
59+
pathEqualsTo(t, "_testdata/symlinktofolder/subfolder", list[12])
60+
pathEqualsTo(t, "_testdata/symlinktofolder/subfolder/file4", list[13])
61+
pathEqualsTo(t, "_testdata/test.txt", list[14])
62+
pathEqualsTo(t, "_testdata/test.txt.gz", list[15])
63+
}
64+
65+
func TestReadDirRecursiveSymLinkLoop(t *testing.T) {
66+
// Test symlink loop
67+
tmp, err := MkTempDir("", "")
68+
require.NoError(t, err)
69+
defer tmp.RemoveAll()
70+
71+
folder := tmp.Join("folder")
72+
err = os.Symlink(tmp.String(), folder.String())
73+
require.NoError(t, err)
74+
75+
l, err := tmp.ReadDirRecursive()
76+
require.Error(t, err)
77+
fmt.Println(err)
78+
require.Nil(t, l)
79+
80+
l, err = tmp.ReadDirRecursiveFiltered(nil)
81+
require.Error(t, err)
82+
fmt.Println(err)
83+
require.Nil(t, l)
84+
}
85+
86+
func TestReadDirFiltered(t *testing.T) {
87+
folderPath := New("_testdata/folder")
88+
list, err := folderPath.ReadDir()
89+
require.NoError(t, err)
90+
require.Len(t, list, 4)
91+
pathEqualsTo(t, "_testdata/folder/.hidden", list[0])
92+
pathEqualsTo(t, "_testdata/folder/file2", list[1])
93+
pathEqualsTo(t, "_testdata/folder/file3", list[2])
94+
pathEqualsTo(t, "_testdata/folder/subfolder", list[3])
95+
96+
list, err = folderPath.ReadDir(FilterDirectories())
97+
require.NoError(t, err)
98+
require.Len(t, list, 1)
99+
pathEqualsTo(t, "_testdata/folder/subfolder", list[0])
100+
101+
list, err = folderPath.ReadDir(FilterOutPrefixes("file"))
102+
require.NoError(t, err)
103+
require.Len(t, list, 2)
104+
pathEqualsTo(t, "_testdata/folder/.hidden", list[0])
105+
pathEqualsTo(t, "_testdata/folder/subfolder", list[1])
106+
}
107+
108+
func TestReadDirRecursiveFiltered(t *testing.T) {
109+
testdata := New("_testdata")
110+
l, err := testdata.ReadDirRecursiveFiltered(nil)
111+
require.NoError(t, err)
112+
l.Sort()
113+
pathEqualsTo(t, "_testdata/anotherFile", l[0])
114+
pathEqualsTo(t, "_testdata/file", l[1])
115+
pathEqualsTo(t, "_testdata/folder", l[2])
116+
pathEqualsTo(t, "_testdata/folder/.hidden", l[3])
117+
pathEqualsTo(t, "_testdata/folder/file2", l[4])
118+
pathEqualsTo(t, "_testdata/folder/file3", l[5])
119+
pathEqualsTo(t, "_testdata/folder/subfolder", l[6])
120+
pathEqualsTo(t, "_testdata/folder/subfolder/file4", l[7])
121+
pathEqualsTo(t, "_testdata/symlinktofolder", l[8])
122+
pathEqualsTo(t, "_testdata/symlinktofolder/.hidden", l[9])
123+
pathEqualsTo(t, "_testdata/symlinktofolder/file2", l[10])
124+
pathEqualsTo(t, "_testdata/symlinktofolder/file3", l[11])
125+
pathEqualsTo(t, "_testdata/symlinktofolder/subfolder", l[12])
126+
pathEqualsTo(t, "_testdata/symlinktofolder/subfolder/file4", l[13])
127+
pathEqualsTo(t, "_testdata/test.txt", l[14])
128+
pathEqualsTo(t, "_testdata/test.txt.gz", l[15])
129+
130+
l, err = testdata.ReadDirRecursiveFiltered(FilterOutDirectories())
131+
require.NoError(t, err)
132+
l.Sort()
133+
pathEqualsTo(t, "_testdata/anotherFile", l[0])
134+
pathEqualsTo(t, "_testdata/file", l[1])
135+
pathEqualsTo(t, "_testdata/folder", l[2]) // <- this is listed but not traversed
136+
pathEqualsTo(t, "_testdata/symlinktofolder", l[3]) // <- this is listed but not traversed
137+
pathEqualsTo(t, "_testdata/test.txt", l[4])
138+
pathEqualsTo(t, "_testdata/test.txt.gz", l[5])
139+
140+
l, err = testdata.ReadDirRecursiveFiltered(nil, FilterOutDirectories())
141+
require.NoError(t, err)
142+
l.Sort()
143+
pathEqualsTo(t, "_testdata/anotherFile", l[0])
144+
pathEqualsTo(t, "_testdata/file", l[1])
145+
pathEqualsTo(t, "_testdata/folder/.hidden", l[2])
146+
pathEqualsTo(t, "_testdata/folder/file2", l[3])
147+
pathEqualsTo(t, "_testdata/folder/file3", l[4])
148+
pathEqualsTo(t, "_testdata/folder/subfolder/file4", l[5])
149+
pathEqualsTo(t, "_testdata/symlinktofolder/.hidden", l[6])
150+
pathEqualsTo(t, "_testdata/symlinktofolder/file2", l[7])
151+
pathEqualsTo(t, "_testdata/symlinktofolder/file3", l[8])
152+
pathEqualsTo(t, "_testdata/symlinktofolder/subfolder/file4", l[9])
153+
pathEqualsTo(t, "_testdata/test.txt", l[10])
154+
pathEqualsTo(t, "_testdata/test.txt.gz", l[11])
155+
156+
l, err = testdata.ReadDirRecursiveFiltered(FilterOutDirectories(), FilterOutDirectories())
157+
require.NoError(t, err)
158+
l.Sort()
159+
pathEqualsTo(t, "_testdata/anotherFile", l[0])
160+
pathEqualsTo(t, "_testdata/file", l[1])
161+
pathEqualsTo(t, "_testdata/test.txt", l[2])
162+
pathEqualsTo(t, "_testdata/test.txt.gz", l[3])
163+
164+
l, err = testdata.ReadDirRecursiveFiltered(FilterOutPrefixes("sub"), FilterOutSuffixes("3"))
165+
require.NoError(t, err)
166+
l.Sort()
167+
pathEqualsTo(t, "_testdata/anotherFile", l[0])
168+
pathEqualsTo(t, "_testdata/file", l[1])
169+
pathEqualsTo(t, "_testdata/folder", l[2])
170+
pathEqualsTo(t, "_testdata/folder/.hidden", l[3])
171+
pathEqualsTo(t, "_testdata/folder/file2", l[4])
172+
pathEqualsTo(t, "_testdata/folder/subfolder", l[5]) // <- subfolder skipped by Prefix("sub")
173+
pathEqualsTo(t, "_testdata/symlinktofolder", l[6])
174+
pathEqualsTo(t, "_testdata/symlinktofolder/.hidden", l[7])
175+
pathEqualsTo(t, "_testdata/symlinktofolder/file2", l[8])
176+
pathEqualsTo(t, "_testdata/symlinktofolder/subfolder", l[9]) // <- subfolder skipped by Prefix("sub")
177+
pathEqualsTo(t, "_testdata/test.txt", l[10])
178+
pathEqualsTo(t, "_testdata/test.txt.gz", l[11])
179+
}

0 commit comments

Comments
 (0)
Please sign in to comment.