aboutsummaryrefslogtreecommitdiffstats
path: root/pathutil.go
diff options
context:
space:
mode:
Diffstat (limited to 'pathutil.go')
-rw-r--r--pathutil.go169
1 files changed, 93 insertions, 76 deletions
diff --git a/pathutil.go b/pathutil.go
index 1653b3e..fe1bba2 100644
--- a/pathutil.go
+++ b/pathutil.go
@@ -19,7 +19,6 @@ import (
"errors"
"fmt"
"os"
- "os/exec"
"path/filepath"
"runtime"
"sort"
@@ -31,12 +30,32 @@ import (
)
type wildcardCacheT struct {
- mu sync.Mutex
- m map[string][]string
+ mu sync.Mutex
+ dirent map[string][]string
}
var wildcardCache = &wildcardCacheT{
- m: make(map[string][]string),
+ dirent: make(map[string][]string),
+}
+
+func (w *wildcardCacheT) dirs() int {
+ w.mu.Lock()
+ defer w.mu.Unlock()
+ return len(w.dirent)
+}
+
+func (w *wildcardCacheT) files() int {
+ w.mu.Lock()
+ defer w.mu.Unlock()
+ n := 0
+ for _, names := range w.dirent {
+ n += len(names)
+ }
+ return n
+}
+
+func hasWildcardMeta(pat string) bool {
+ return strings.IndexAny(pat, "*?[") >= 0
}
func wildcardUnescape(pat string) string {
@@ -45,99 +64,97 @@ func wildcardUnescape(pat string) string {
if pat[i] == '\\' && i+1 < len(pat) {
switch pat[i+1] {
case '*', '?', '[', '\\':
- writeByte(&buf, pat[i])
- default:
- i++
+ buf.WriteByte(pat[i])
}
+ continue
}
- writeByte(&buf, pat[i])
+ buf.WriteByte(pat[i])
}
return buf.String()
}
-func wildcardGlob(pat string) ([]string, error) {
- // TODO(ukai): use find cache for glob if exists.
- pat = wildcardUnescape(pat)
- pattern := filepath.Clean(pat)
- if pattern != pat {
- // For some reason, go's Glob normalizes
- // foo/../bar to bar.
- i := strings.IndexAny(pattern, "*?[")
- if i < 0 {
- // no wildcard. if any files matched with pattern,
- // return pat.
- _, err := os.Stat(pat)
- if err != nil {
- return nil, nil
- }
- return []string{pat}, nil
- }
- if strings.Contains(pattern[i+1:], "..") {
- // We ask shell to expand a glob to avoid this.
- cmdline := []string{"/bin/sh", "-c", "/bin/ls -d " + pat}
- cmd := exec.Cmd{
- Path: cmdline[0],
- Args: cmdline,
- }
- // Ignore errors.
- out, _ := cmd.Output()
- ws := newWordScanner(out)
- var files []string
- for ws.Scan() {
- files = append(files, string(ws.Bytes()))
- }
- return files, nil
- }
- // prefix + meta + suffix, and suffix doesn't have '..'
- prefix := pattern[:i]
- i = strings.IndexAny(pat, "*?[")
- if i < 0 {
- return nil, fmt.Errorf("wildcard metachar mismatch? pattern=%q pat=%q", pattern, pat)
- }
- oprefix := pat[:i]
- matched, err := filepath.Glob(pattern)
+func (w *wildcardCacheT) readdirnames(dir string) []string {
+ if dir == "" {
+ dir = "."
+ }
+ w.mu.Lock()
+ names, ok := w.dirent[dir]
+ w.mu.Unlock()
+ if ok {
+ return names
+ }
+ d, err := os.Open(dir)
+ if err != nil {
+ w.mu.Lock()
+ w.dirent[dir] = nil
+ w.mu.Unlock()
+ return nil
+ }
+ defer d.Close()
+ names, _ = d.Readdirnames(-1)
+ sort.Strings(names)
+ w.mu.Lock()
+ w.dirent[dir] = names
+ w.mu.Unlock()
+ return names
+}
+
+// glob searches for files matching pattern in the directory dir
+// and appends them to matches. ignore I/O errors.
+func (w *wildcardCacheT) glob(dir, pattern string, matches []string) ([]string, error) {
+ names := w.readdirnames(dir)
+ if dir != "" {
+ dir += string(filepath.Separator)
+ }
+ for _, n := range names {
+ matched, err := filepath.Match(pattern, n)
if err != nil {
return nil, err
}
- var files []string
- for _, m := range matched {
- file := oprefix + strings.TrimPrefix(m, prefix)
- _, err := os.Stat(file)
- if err != nil {
- continue
- }
- files = append(files, file)
+ if matched {
+ matches = append(matches, dir+n)
}
- return files, nil
}
- return filepath.Glob(pat)
+ return matches, nil
}
-func wildcard(w evalWriter, pat string) error {
- if UseWildcardCache {
- // TODO(ukai): make sure it didn't chdir?
- wildcardCache.mu.Lock()
- files, ok := wildcardCache.m[pat]
- wildcardCache.mu.Unlock()
- if ok {
- for _, file := range files {
- w.writeWordString(file)
- }
- return nil
+func (w *wildcardCacheT) Glob(pat string) ([]string, error) {
+ // TODO(ukai): use find cache for glob if exists
+ // or use wildcardCache for find cache.
+ pat = wildcardUnescape(pat)
+ dir, file := filepath.Split(pat)
+ switch dir {
+ case "", string(filepath.Separator):
+ // nothing
+ default:
+ dir = dir[0 : len(dir)-1] // chop off trailing separator
+ }
+ if !hasWildcardMeta(dir) {
+ return w.glob(dir, file, nil)
+ }
+
+ m, err := w.Glob(dir)
+ if err != nil {
+ return nil, err
+ }
+ var matches []string
+ for _, d := range m {
+ matches, err = w.glob(d, file, matches)
+ if err != nil {
+ return nil, err
}
}
- files, err := wildcardGlob(pat)
+ return matches, nil
+}
+
+func wildcard(w evalWriter, pat string) error {
+ files, err := wildcardCache.Glob(pat)
if err != nil {
return err
}
for _, file := range files {
w.writeWordString(file)
}
- if UseWildcardCache {
- wildcardCache.mu.Lock()
- wildcardCache.m[pat] = files
- wildcardCache.mu.Unlock()
- }
return nil
}