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 }