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