package fsx import ( "go/types" "io/fs" "path/filepath" ) // TODO: FilterFsPaths /* FsPaths returns all paths in [io/fs.FS] `fsys`. If `fsys` is nil, both `fsPaths` and `err` will be nil. `t` should be an OR'd [io/fs.FileMode] containing all filetypes that should be included. Use [io/fs.ModeType] to match any non-regular-file object, or 0 to explicitly match regular files. If `t` is non-zero but you wish to ALSO include regular files, then specify `inclFiles` as true. (`inclFiles` is ignored if `t` == 0, as this inherently matches regular files only/explicitly.) See [HasType] for more information on this filtering. `maxDepth` can be used to limit recursion depth. - If `maxDepth` == 0, only direct [io/fs.DirEntry] descendants of `fsys` will be included in `fsPaths`. - If `maxDepth` < 0, ALL [io/fs.DirEntry] descendants of `fsys` will be included in `fsPaths`. - If `maxDepth` > 0, then only children at a depth of this level or lower ("higher" in a hierarchy) will be included in `fsPaths`. */ func FsPaths(fsys fs.FS, t fs.FileMode, inclFiles bool, maxDepth int) (fsPaths map[string]fs.DirEntry, err error) { var idx int var entries []fs.DirEntry var t fs.FileMode = types.Type() if fsys == nil { return } if fsPaths, err = getEntries(fsys, t, inclFiles, maxDepth, "."); err != nil { return } return } /* HasType is just a convenience wrapper for: hasType = m & t != 0 The type(s) will be explicitly extracted from `m` and `t` first. Note that if `t` is an OR'd set of filetypes, then HasType will return true if ANY of the types in `t` match `m`'s type (see [r00t2.io/goutils/bitmask] for more information on bitmasking). If you wish to check if `m` is A SINGLE type, either ensure that `t` is not OR'd (i.e. is a single fs.FileMode type) or use [IsType] instead. */ func HasType(m, t fs.FileMode) (hasType bool) { return m.Type()&t.Type() != 0 } /* IsType is just a convenience wrapper for: isType = m & t == t The type(s) will be explicitly extracted from `m` and `t` first. Note that if `t` is an OR'd set of filetypes, then IsType will *never* return true as its type is single-valued/not bitmasked (see [r00t2.io/goutils/bitmask] for more information on bitmasking). If you wish to check if `m` is ONE OF multiple types in `t`, use [HasType] instead. */ func IsType(m, t fs.FileMode) (isType bool) { return m&t == t } /* getEntries is used to recursively fetch entries from `fsys`. It mostly serves as the recursion for [FsPaths], and the arguments retain their exact meaning except that it requires `relPath` and `maxDepth` (if > 0) will be decremanted in each recursion. */ func getEntries(fsys fs.FS, t fs.FileMode, inclFiles bool, maxDepth int, relPath string) (fsPaths map[string]fs.DirEntry, err error) { var idx int var p string var k string var v fs.DirEntry var e []fs.DirEntry var subs map[string]fs.DirEntry = make(map[string]fs.DirEntry) if e, err = fs.ReadDir(fsys, relPath); err != nil { return } fsPaths = make(map[string]fs.DirEntry) for idx = range e { p = filepath.Join(relPath, e[idx].Name()) if e[idx].IsDir() { if subs, err = getEntries(fsys, t, inclFiles, maxDepth, p); err != nil { return } for k, v = range subs { fsPaths[k] = v } } if t != 0 { if HasType(t, e[idx].Type()) { fsPaths[p] = e[idx] } } else if inclFiles && e[idx].Type() == 0 { fsPaths[p] = e[idx] } } return }