Skip to content

Commit afb58ef

Browse files
committed
✨ feat: fsutil - add new func: FindAllInParentDirs, FindOneInParentDirs, FindNameInParentDirs
1 parent a313253 commit afb58ef

File tree

2 files changed

+135
-11
lines changed

2 files changed

+135
-11
lines changed

fsutil/find.go

Lines changed: 102 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func FilePathInDirs(fPath string, dirs ...string) string {
2323
}
2424

2525
for _, dirPath := range dirs {
26-
fPath := JoinSubPaths(dirPath, fPath)
26+
fPath = JoinSubPaths(dirPath, fPath)
2727
if FileExists(fPath) {
2828
return fPath
2929
}
@@ -67,6 +67,103 @@ func MatchFirst(paths []string, matcher PathMatchFunc, defaultPath string) strin
6767
return defaultPath
6868
}
6969

70+
// FindParentOption options
71+
type FindParentOption struct {
72+
MaxLevel int // default: 10
73+
// NeedDir true: find dirs; false(default): find files
74+
NeedDir bool
75+
OnlyOne bool // only find one, default: true
76+
// Collector func
77+
Collector func(fullPath string)
78+
// MatchFunc custom matcher func. return false to stop find.
79+
MatchFunc func(currentDir string) bool
80+
}
81+
82+
// FindParentOptFn find parent option func
83+
type FindParentOptFn func(opt *FindParentOption)
84+
85+
// FindAllInParentDirs looks for all match file(default)/dir in the current directory and parent directories
86+
func FindAllInParentDirs(dirPath, name string, optFns ...FindParentOptFn) []string {
87+
var foundPaths []string
88+
optFns = append(optFns, func(opt *FindParentOption) {
89+
opt.OnlyOne = false
90+
})
91+
92+
FindNameInParentDirs(dirPath, name, func(fullPath string) {
93+
foundPaths = append(foundPaths, fullPath)
94+
}, optFns...)
95+
return foundPaths
96+
}
97+
98+
// FindOneInParentDirs looks for a file(default)/dir in the current directory and parent directories
99+
func FindOneInParentDirs(dirPath, name string, optFns ...FindParentOptFn) string {
100+
var foundPath string
101+
FindNameInParentDirs(dirPath, name, func(fullPath string) {
102+
foundPath = fullPath
103+
}, optFns...)
104+
return foundPath
105+
}
106+
107+
// FindNameInParentDirs looks for file(default)/dir in the current directory and parent directories
108+
func FindNameInParentDirs(dirPath, name string, collectFn func(fullPath string), optFns ...FindParentOptFn) {
109+
opts := &FindParentOption{
110+
MaxLevel: 10,
111+
OnlyOne: true,
112+
Collector: collectFn,
113+
}
114+
for _, fn := range optFns {
115+
fn(opts)
116+
}
117+
118+
FindInParentDirs(dirPath, func(currentDir string) bool {
119+
filePath := filepath.Join(currentDir, name)
120+
if fi, err := os.Stat(filePath); err == nil {
121+
found := false
122+
if fi.IsDir() {
123+
found = opts.NeedDir
124+
} else {
125+
found = !opts.NeedDir
126+
}
127+
128+
if found {
129+
opts.Collector(filePath)
130+
return !opts.OnlyOne
131+
}
132+
}
133+
return true
134+
}, opts.MaxLevel)
135+
}
136+
137+
// FindInParentDirs looks for file/dir in the current directory and parent directories
138+
// - MatchFunc custom matcher func. return false to stop find.
139+
func FindInParentDirs(dirPath string, matchFunc func(dir string) bool, maxLevel int) {
140+
currentLv := 1
141+
currentDir := ToAbsPath(dirPath)
142+
143+
for {
144+
// Check if the file exists in the current directory
145+
if !matchFunc(currentDir) {
146+
return
147+
}
148+
149+
// check find level
150+
if maxLevel > 0 && currentLv > maxLevel {
151+
break
152+
}
153+
154+
// Get parent directory
155+
parentDir := filepath.Dir(currentDir)
156+
if parentDir == currentDir {
157+
// Reached the root, file not found
158+
return
159+
}
160+
161+
// Move to parent directory
162+
currentLv++
163+
currentDir = parentDir
164+
}
165+
}
166+
70167
// SearchNameUp find file/dir name in dirPath or parent dirs,
71168
// return the name of directory path
72169
//
@@ -158,14 +255,10 @@ type (
158255
)
159256

160257
// OnlyFindDir on find
161-
func OnlyFindDir(_ string, ent fs.DirEntry) bool {
162-
return ent.IsDir()
163-
}
258+
func OnlyFindDir(_ string, ent fs.DirEntry) bool { return ent.IsDir() }
164259

165260
// OnlyFindFile on find
166-
func OnlyFindFile(_ string, ent fs.DirEntry) bool {
167-
return !ent.IsDir()
168-
}
261+
func OnlyFindFile(_ string, ent fs.DirEntry) bool { return !ent.IsDir() }
169262

170263
// ExcludeNames on find
171264
func ExcludeNames(names ...string) FilterFunc {
@@ -182,9 +275,7 @@ func IncludeSuffix(ss ...string) FilterFunc {
182275
}
183276

184277
// ExcludeDotFile on find
185-
func ExcludeDotFile(_ string, ent fs.DirEntry) bool {
186-
return ent.Name()[0] != '.'
187-
}
278+
func ExcludeDotFile(_ string, ent fs.DirEntry) bool { return ent.Name()[0] != '.' }
188279

189280
// ExcludeSuffix on find
190281
func ExcludeSuffix(ss ...string) FilterFunc {
@@ -205,7 +296,7 @@ func ApplyFilters(fPath string, ent fs.DirEntry, filters []FilterFunc) bool {
205296

206297
// FindInDir code refer the go pkg: path/filepath.glob()
207298
//
208-
// - TIP: will be not found in sub-dir.
299+
// - TIP: default will be not found in sub-dir.
209300
//
210301
// filters: return false will skip the file.
211302
func FindInDir(dir string, handleFn HandleFunc, filters ...FilterFunc) (e error) {

fsutil/find_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package fsutil_test
22

33
import (
44
"io/fs"
5+
"runtime"
56
"strings"
67
"testing"
78

@@ -12,6 +13,38 @@ import (
1213
"github.com/gookit/goutil/x/fakeobj"
1314
)
1415

16+
func TestFindAllInParentDirs(t *testing.T) {
17+
// find all
18+
ss := fsutil.FindAllInParentDirs("testdata", "find.go", func(opt *fsutil.FindParentOption) {
19+
opt.MaxLevel = 3
20+
})
21+
assert.NotEmpty(t, ss)
22+
assert.Len(t, ss, 1)
23+
24+
ss = fsutil.FindAllInParentDirs("testdata", "README.md", func(opt *fsutil.FindParentOption) {
25+
opt.MaxLevel = 5
26+
})
27+
assert.NotEmpty(t, ss)
28+
assert.Gt(t, len(ss), 1)
29+
}
30+
31+
func TestFindOneInParentDirs(t *testing.T) {
32+
// find one
33+
s := fsutil.FindOneInParentDirs("testdata", "goutil.go")
34+
assert.NotEmpty(t, s)
35+
if runtime.GOOS == "windows" {
36+
assert.StrContains(t, s, "goutil\\goutil.go")
37+
} else {
38+
assert.Eq(t, s, "goutil/goutil.go")
39+
}
40+
41+
// find dir
42+
s = fsutil.FindOneInParentDirs("testdata", "errorx", func(opt *fsutil.FindParentOption) {
43+
opt.NeedDir = true
44+
})
45+
assert.NotEmpty(t, s)
46+
}
47+
1548
func TestFilePathInDirs(t *testing.T) {
1649
result := fsutil.FilePathInDirs("not_existing_file.txt")
1750
assert.Empty(t, result)

0 commit comments

Comments
 (0)