127 lines
2.6 KiB
Go
127 lines
2.6 KiB
Go
|
|
package glob
|
||
|
|
|
||
|
|
import (
|
||
|
|
"io/fs"
|
||
|
|
"path/filepath"
|
||
|
|
"slices"
|
||
|
|
)
|
||
|
|
|
||
|
|
type GlobFS struct {
|
||
|
|
base fs.FS
|
||
|
|
patterns []Pattern
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewGlobFS creates a new GlobFS that exposes only files matching any of the given glob patterns.
|
||
|
|
func NewGlobFS(base fs.FS, patterns ...string) (*GlobFS, error) {
|
||
|
|
fs := &GlobFS{base: base, patterns: []Pattern{}}
|
||
|
|
|
||
|
|
for _, value := range patterns {
|
||
|
|
pattern, err := New(value)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
fs.patterns = append(fs.patterns, *pattern)
|
||
|
|
}
|
||
|
|
|
||
|
|
return fs, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (g *GlobFS) match(name string, prefix bool) bool {
|
||
|
|
var f func(Pattern) bool
|
||
|
|
if prefix {
|
||
|
|
f = func(p Pattern) bool { return p.MatchPrefix(name) }
|
||
|
|
} else {
|
||
|
|
f = func(p Pattern) bool { return p.Match(name) }
|
||
|
|
}
|
||
|
|
return slices.ContainsFunc(g.patterns, f)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (g *GlobFS) contains(name string) (bool, error) {
|
||
|
|
stat, err := fs.Stat(g.base, name)
|
||
|
|
if err != nil {
|
||
|
|
return false, err
|
||
|
|
}
|
||
|
|
if stat.IsDir() {
|
||
|
|
contains := false
|
||
|
|
err := fs.WalkDir(g.base, name, func(path string, d fs.DirEntry, err error) error {
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
if d.IsDir() && !g.match(name+string(filepath.Separator), true) {
|
||
|
|
return fs.SkipDir
|
||
|
|
}
|
||
|
|
|
||
|
|
if g.match(path, false) {
|
||
|
|
contains = true
|
||
|
|
return fs.SkipAll
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
})
|
||
|
|
return contains, err
|
||
|
|
} else {
|
||
|
|
return g.match(name, false), nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (g *GlobFS) Open(name string) (fs.File, error) {
|
||
|
|
root := name == "."
|
||
|
|
|
||
|
|
// fast path some of the pattern matches
|
||
|
|
if root || g.match(name, false) {
|
||
|
|
return g.base.Open(name)
|
||
|
|
}
|
||
|
|
|
||
|
|
ok, err := g.contains(name)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
if ok {
|
||
|
|
return g.base.Open(name)
|
||
|
|
} else {
|
||
|
|
return nil, fs.ErrNotExist
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (g *GlobFS) ReadDir(name string) ([]fs.DirEntry, error) {
|
||
|
|
root := name == "."
|
||
|
|
path := name + string(filepath.Separator)
|
||
|
|
|
||
|
|
// fast path no pattern matches (prefix check)
|
||
|
|
// root dir ('.') must be handled to get initial entries
|
||
|
|
if !root && !g.match(path, true) {
|
||
|
|
return nil, fs.ErrNotExist
|
||
|
|
}
|
||
|
|
|
||
|
|
entries, err := fs.ReadDir(g.base, name)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
// if we do not have any child entries, we need to check if the directory
|
||
|
|
// itself matched some of the defined patterns, if so we should be able to
|
||
|
|
// read it, otherwise we can not read it.
|
||
|
|
if !root && len(entries) == 0 {
|
||
|
|
if !g.match(path, false) {
|
||
|
|
return nil, fs.ErrNotExist
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
children := []fs.DirEntry{}
|
||
|
|
for _, entry := range entries {
|
||
|
|
ok, err := g.contains(filepath.Join(name, entry.Name()))
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
if ok {
|
||
|
|
children = append(children, entry)
|
||
|
|
} else {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return children, nil
|
||
|
|
}
|