v1.16.11
ADDED: * stringsx: ** RunesInString ** RuneMapFromString ** SquashConsec ** SquashConsecRunes ** SquashConsecRunesAll ** SquashMap ** SquashWhitespace
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user