c9f3e7a639
ADDED: * stringsx: ** RunesInString ** RuneMapFromString ** SquashConsec ** SquashConsecRunes ** SquashConsecRunesAll ** SquashMap ** SquashWhitespace
361 lines
6.9 KiB
Go
361 lines
6.9 KiB
Go
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
|
|
}
|