FIXED:
* tplx/sprigx would throw errors on dnsx.IsPtr, because it returns a
  bool and net.IP instead of a boolen and optional error. Fixed by
  adding dnsx.IsPtrChk and using that instead.
This commit is contained in:
brent saner
2026-08-08 00:13:34 -04:00
parent cbfaaddf34
commit 4e7d474fb3
12 changed files with 202 additions and 78 deletions
+4
View File
@@ -0,0 +1,4 @@
/*
Package fsx provides some enhancements to [io/fs].
*/
package fsx
+102
View File
@@ -0,0 +1,102 @@
package fsx
import (
"go/types"
"io/fs"
"path/filepath"
)
/*
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 e []fs.DirEntry
var subs map[string]fs.DirEntry = make(map[string]fs.DirEntry)
if e, err = fs.ReadDir(fsys, relPath); err != nil {
return
}
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
}
}
}
return
}
+94
View File
@@ -0,0 +1,94 @@
package main
import (
"embed"
"os"
"slices"
"github.com/deckarep/golang-set/v2"
"r00t2.io/sysutils/paths"
)
// NewEmbedFsMember returns an embedFsMember from embedFs.
func NewEmbedFsMember(embedFs *embed.FS) (m *embedFsMember) {
m = &embedFsMember{
FS: embedFs,
}
return
}
/*
NewMergedFS returns a MergedFS.
The last filesystem in a MergedFS is always embedTplFS.
If fsPaths is empty, mfs will only contain embedTplFS.
Nonexisting paths in fsPaths will be skipped (and added to the noExist slice).
Order will be retained but duplicates will be skipped and added to the dupes slice.
noExist and dupes will be nil if no non-existing or duplicate paths are encountereed.
*/
func NewMergedFS(fsPaths ...string) (mfs *MergedFS, noExist, dupes []string, err error) {
var m MergedFS
var exists bool
var fsPath string
var fsMem mergedFsMember
m = MergedFS{
fses: make([]mergedFsMember, 0, len(fsPaths)+1),
overridePaths: make([]string, 0, len(fsPaths)+1),
uniq: mapset.NewSet[string](),
}
for _, fsPath = range fsPaths {
if exists, err = paths.RealPathExists(&fsPath); err != nil {
return
}
if !exists {
noExist = append(noExist, fsPath)
continue
}
if !m.uniq.Add(fsPath) {
dupes = append(dupes, fsPath)
continue
}
if fsMem, err = NewRealFsMember(fsPath); err != nil {
return
}
m.fses = append(m.fses, fsMem)
m.overridePaths = append(m.overridePaths, fsPath)
}
// embedded fs last
m.fses = append(m.fses, NewEmbedFsMember(&embedTplFS))
m.overridePaths = append(m.overridePaths, "")
m.fses = slices.Clip(m.fses)
m.overridePaths = slices.Clip(m.overridePaths)
mfs = &m
return
}
// NewRealFsMember returns a realFsMember (which properly conforms to mergedFsMember) from a real filesystem path.
func NewRealFsMember(fsPath string) (fsm *realFsMember, err error) {
var fsp string
var m realFsMember
if err = paths.RealPath(&fsp); err != nil {
return
}
if m.Root, err = os.OpenRoot(fsPath); err != nil {
return
}
fsm = &m
return
}
@@ -0,0 +1,18 @@
package main
import (
"io/fs"
)
// Close is a no-op, but used to conform to mergedFsMember.
func (e *embedFsMember) Close() (err error) { return }
// Stat wraps fs.Stat on the embeded filesystem.
func (e *embedFsMember) Stat(fnm string) (stat fs.FileInfo, err error) {
if stat, err = fs.Stat(e.FS, fnm); err != nil {
return
}
return
}
@@ -0,0 +1,360 @@
package main
import (
"io"
"io/fs"
"os"
"path/filepath"
"slices"
"r00t2.io/sysutils/paths"
)
/*
Append adds fsPaths as fs.FS to the MergedFS (before the embed.FS).
If fsPaths is empty, Append is a NO-OP.
noExist and dupes have the same meaning as in NewMergedFS.
*/
func (m *MergedFS) Append(fsPaths ...string) (noExist, dupes []string, err error) {
var exists bool
var fsPath string
var newPaths []string
var fsMem *realFsMember
var newFs []mergedFsMember
m.mut.Lock()
defer m.mut.Unlock()
newPaths = make([]string, 0, len(fsPaths))
newFs = make([]mergedFsMember, 0, len(fsPaths))
for _, fsPath = range fsPaths {
if exists, err = paths.RealPathExists(&fsPath); err != nil {
return
}
if !exists {
noExist = append(noExist, fsPath)
continue
}
if fsMem, err = NewRealFsMember(fsPath); err != nil {
return
}
if !m.uniq.Add(fsPath) {
dupes = append(dupes, fsPath)
continue
}
newFs = append(newFs, fsMem)
newPaths = append(newPaths, fsPath)
}
if len(newFs) == 0 {
return
}
m.overridePaths = append(m.overridePaths, newPaths...)
m.fses = append(m.fses[:len(m.fses)-1], append(newFs, m.fses[len(m.fses)-1])...)
return
}
// Close closes all realFsMember members. This MergedFS will not be able to be used after this is called.
func (m *MergedFS) Close() (err error) {
var fsMem mergedFsMember
m.mut.Lock()
defer m.mut.Unlock()
for _, fsMem = range m.fses {
if err = fsMem.(io.Closer).Close(); err != nil {
return
}
}
return
}
/*
Insert adds fsPaths as fs.FS to a MergedFS starting at index idx.
If fsPaths is empty, Insert is a NO-OP.
If idx is 0 or negative, they will be prepended.
If idx is larger than the last index of real directories (excluding the last embed.FS),
it will be reduced to the highest valid index.
noExist and dupes have the same meaning as in NewMergedFS.
*/
func (m *MergedFS) Insert(idx int, fsPaths ...string) (noExist, dupes []string, err error) {
var exists bool
var fsPath string
var newPaths []string
var fsMem *realFsMember
var newFs []mergedFsMember
if len(fsPaths) == 0 {
return
}
m.mut.Lock()
defer m.mut.Unlock()
newPaths = make([]string, 0, len(fsPaths))
newFs = make([]mergedFsMember, 0, len(fsPaths))
for _, fsPath = range fsPaths {
if exists, err = paths.RealPathExists(&fsPath); err != nil {
return
}
if !exists {
noExist = append(noExist, fsPath)
continue
}
if fsMem, err = NewRealFsMember(fsPath); err != nil {
return
}
if !m.uniq.Add(fsPath) {
dupes = append(dupes, fsPath)
continue
}
newPaths = append(newPaths, fsPath)
newFs = append(newFs, fsMem)
}
if len(newFs) == 0 {
return
}
if idx < 0 {
m.fses = append(newFs, m.fses...)
m.overridePaths = append(newPaths, m.overridePaths...)
return
} else if idx >= len(m.fses)-1 {
idx = len(m.fses) - 2
}
m.fses = append(m.fses[:idx], append(newFs, m.fses[idx:]...)...)
m.overridePaths = append(m.overridePaths[:idx], append(newPaths, m.overridePaths[idx:]...)...)
return
}
// Open opens filename fnm and returns it as file handler fh.
func (m *MergedFS) Open(fnm string) (fh fs.File, err error) {
var fsref fs.FS
m.mut.RLock()
defer m.mut.RUnlock()
// Iterate until a match is found.
for _, fsref = range m.fses {
if fh, err = fsref.Open(fnm); err == nil {
return
}
}
// No match found.
err = &fs.PathError{
Op: "Open",
Path: fnm,
Err: fs.ErrNotExist,
}
return
}
/*
Paths returns a slice of real filesystem paths that are used by this MergedFS.
If sorted is true, they will be returned as a sorted slice -
otherwise they will be returned in the order they are checked.
*/
func (m *MergedFS) Paths(sorted bool) (fsPaths []string) {
m.mut.RLock()
defer m.mut.RUnlock()
if sorted {
fsPaths = m.uniq.ToSlice()
slices.Sort(fsPaths)
} else {
fsPaths = make([]string, len(m.overridePaths)-1)
copy(fsPaths, m.overridePaths[:len(m.overridePaths)-1])
}
return
}
/*
ReadDir returns a listing of path matcher fpath.
The result is a combination of all filesystems; the first encountered matching
name is added.
*/
func (m *MergedFS) ReadDir(fpath string) (entries []fs.DirEntry, err error) {
var idx int
var e fs.DirEntry
var tmpEntries []fs.DirEntry
var seen map[string]bool = make(map[string]bool)
m.mut.RLock()
defer m.mut.RUnlock()
// merge real and embedded, preferring first-seen real paths
for idx, _ = range m.fses {
if m.overridePaths[idx] == "" {
if tmpEntries, err = m.fses[idx].ReadDir(filepath.FromSlash(fpath)); err != nil {
continue
}
} else {
if tmpEntries, err = os.ReadDir(filepath.Join(m.overridePaths[idx], filepath.FromSlash(fpath))); err != nil {
continue
}
}
for _, e = range tmpEntries {
if seen[e.Name()] {
continue
}
seen[e.Name()] = true
entries = append(entries, e)
}
}
return
}
/*
ReadFile returns the bytes for the first found fpath.
It wraps MergedFS.Open.
*/
func (m *MergedFS) ReadFile(fpath string) (b []byte, err error) {
var fh fs.File
if fh, err = m.Open(fpath); nil != err {
return
}
defer func() {
if fh != nil {
if err = fh.Close(); err != nil {
return
}
}
}()
if b, err = io.ReadAll(fh); err != nil {
return
}
return
}
/*
Stat returns the fs.FileInfo for the first found fpath.
It wraps MergedFS.Open.
*/
func (m *MergedFS) Stat(fpath string) (stat fs.FileInfo, err error) {
var fh fs.File
if fh, err = m.Open(fpath); nil != err {
return
}
defer func() {
if fh != nil {
if err = fh.Close(); err != nil {
return
}
}
}()
if stat, err = fh.Stat(); err != nil {
return
}
return
}
/*
RemoveFsByIdx removes the filesystem found at index idx.
An attempt to remove the final embed.FS filesystem will result in an ErrFsIdxOOB.
If idx is larger than the last index of "real" filesystems, an ErrFsIdxOOB will be returned.
*/
func (m *MergedFS) RemoveFsByIdx(idx uint) (err error) {
var fsPath string
m.mut.Lock()
defer m.mut.Unlock()
if idx+1 == uint(len(m.fses)) {
return
}
if idx > uint(len(m.overridePaths)-1) {
err = ErrFsIdxOOB
return
}
fsPath = m.overridePaths[idx]
if m.uniq.Contains(fsPath) {
m.uniq.Remove(fsPath)
}
m.fses = append(m.fses[:idx], m.fses[idx+1:]...)
m.overridePaths = append(m.overridePaths[:idx], m.overridePaths[idx+1:]...)
return
}
/*
RemoveFsByPath removes the fileststem with path dirPath.
If dirpath is empty, this function NOOPs.
If dirpath does not exist in this MergedFS, an ErrFsPathNoExists will be returned.
*/
func (m *MergedFS) RemoveFsByPath(dirPath string) (err error) {
var idx int
var fsp string
var p string = dirPath
if p == "" {
return
}
if err = paths.RealPath(&p); err != nil {
return
}
m.mut.Lock()
defer m.mut.Unlock()
if !m.uniq.Contains(p) {
err = ErrFsPathNoExists
return
}
m.uniq.Remove(p)
for idx, fsp = range m.overridePaths {
if fsp == p {
m.fses = append(m.fses[:idx], m.fses[idx+1:]...)
m.overridePaths = append(m.overridePaths[:idx], m.overridePaths[idx+1:]...)
return
}
}
err = ErrFsIdxOOB
return
}
@@ -0,0 +1,52 @@
package main
import (
"io/fs"
"os"
"path/filepath"
"strings"
"r00t2.io/sysutils/paths"
)
// Open opens file fnm.
func (r *realFsMember) Open(fnm string) (fh fs.File, err error) {
if fh, err = r.Root.Open(fnm); err != nil {
return
}
return
}
// ReadDir returns directory entries at fpath joined with the realFsMember's root.
func (r *realFsMember) ReadDir(fpath string) (entries []fs.DirEntry, err error) {
var rel string
var fsp string = fpath
if !filepath.IsAbs(fsp) {
fsp = filepath.Join(
r.Root.Name(),
filepath.FromSlash(fsp),
)
}
if err = paths.RealPath(&fsp); err != nil {
return
}
if rel, err = filepath.Rel(r.Root.Name(), fsp); err != nil {
// Not relative to this root, so skip.
err = nil
return
}
if strings.HasPrefix(rel, "..") {
// Absolute but not relative to this root, so skip.
return
}
if entries, err = os.ReadDir(fsp); err != nil {
return
}
return
}
+23
View File
@@ -0,0 +1,23 @@
package main
type (
mergedFsMember interface {
fs.ReadDirFS
fs.ReadFileFS
fs.StatFS
io.Closer
}
realFsMember struct {
*os.Root
}
embedFsMember struct {
*embed.FS
}
MergedFS struct {
// always appended with embedTplFs
fses []mergedFsMember
overridePaths []string
uniq mapset.Set[string]
mut sync.RWMutex
}
)