-
Notifications
You must be signed in to change notification settings - Fork 2
/
glob.go
170 lines (133 loc) · 4.02 KB
/
glob.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package glob
import (
"errors"
"os"
"path/filepath"
"strings"
)
/*
* glob - an expanded version
*
* This implementation of globbing will still take advantage of the Glob
* function in path/filepath, however this extends the pattern to include '**'
*
*/
/*
Algorithm details
segments = glob pattern split by os.path separator
define Entry:
path, index into glob
Base Case:
add Entry{root, 0}
while num entries > 0
given an entry (path, idx)
given glob segment (gb) at idx
if gb == **
move cur entry idx + 1
for each dir inside path
add new Entry{dir, idx}
else
add gb to path
check for any results from normal globbing
if none
remove entry
else
if idx + 1 is out of bounds
add result to final list
else
add an entry{result, idx + 1}
keep current entry if it's idx is in bounds
*/
type matchEntry struct {
path string
idx int
}
func Glob(root string, pattern string) (matches []string, e error) {
if strings.Index(pattern, "**") < 0 {
return filepath.Glob(filepath.Join(root, pattern))
}
segments := strings.Split(pattern, string(os.PathSeparator))
workingEntries := []matchEntry{
matchEntry{path: root, idx: 0},
}
for len(workingEntries) > 0 {
var temp []matchEntry
for _, entry := range workingEntries {
workingPath := entry.path
idx := entry.idx
segment := segments[entry.idx]
if segment == "**" {
// add all subdirectories and move yourself one step further
// into pattern
entry.idx++
subDirectories, err := getAllSubDirectories(entry.path)
if err != nil {
return nil, err
}
for _, name := range subDirectories {
path := filepath.Join(workingPath, name)
newEntry := matchEntry{
path: path,
idx: idx,
}
temp = append(temp, newEntry)
}
} else {
// look at all results
// if we're at the end of the pattern, we found a match
// else add it to a working entry
path := filepath.Join(workingPath, segment)
results, err := filepath.Glob(path)
if err != nil {
return nil, err
}
for _, result := range results {
if idx + 1 < len(segments) {
newEntry := matchEntry{
path: result,
idx: idx + 1,
}
temp = append(temp, newEntry)
} else {
matches = append(matches, result)
}
}
// delete ourself regardless
entry.idx = len(segments)
}
// check whether current entry is still valid
if entry.idx < len(segments) {
temp = append(temp, entry)
}
}
workingEntries = temp
}
return
}
func isDir(path string) (val bool, err error) {
fi, err := os.Stat(path)
if err != nil {
return false, err
}
return fi.IsDir(), nil
}
func getAllSubDirectories(path string) (dirs []string, err error) {
if dir, err := isDir(path); err != nil || !dir {
return nil, errors.New("Not a directory " + path)
}
d, err := os.Open(path)
if err != nil {
return nil, err
}
files, err := d.Readdirnames(-1)
if err != nil {
return nil, err
}
for _, file := range files {
path := filepath.Join(path, file)
if dir, err := isDir(path); err == nil && dir {
dirs = append(dirs, file)
}
}
return
}