sdk/glob/fs.go

127 lines
2.6 KiB
Go
Raw Permalink Normal View History

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
}