208 lines
6.1 KiB
Go
208 lines
6.1 KiB
Go
package iovfs
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"strings"
|
|
"time"
|
|
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/stringutil"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs/internal"
|
|
)
|
|
|
|
type RealpathFS interface {
|
|
fs.FS
|
|
Realpath(path string) (string, error)
|
|
}
|
|
|
|
type WritableFS interface {
|
|
fs.FS
|
|
WriteFile(path string, data []byte, perm fs.FileMode) error
|
|
MkdirAll(path string, perm fs.FileMode) error
|
|
// Removes `path` and all its contents. Will return the first error it encounters.
|
|
Remove(path string) error
|
|
Chtimes(path string, aTime time.Time, mTime time.Time) error
|
|
}
|
|
|
|
type FsWithSys interface {
|
|
vfs.FS
|
|
FSys() fs.FS
|
|
}
|
|
|
|
// From creates a new FS from an [fs.FS].
|
|
//
|
|
// For paths like `c:/foo/bar`, fsys will be used as though it's rooted at `/` and the path is `/c:/foo/bar`.
|
|
//
|
|
// If the provided [fs.FS] implements [RealpathFS], it will be used to implement the Realpath method.
|
|
// If the provided [fs.FS] implements [WritableFS], it will be used to implement the WriteFile method.
|
|
//
|
|
// From does not actually handle case-insensitivity; ensure the passed in [fs.FS]
|
|
// respects case-insensitive file names if needed. Consider using [vfstest.FromMap] for testing.
|
|
func From(fsys fs.FS, useCaseSensitiveFileNames bool) FsWithSys {
|
|
var realpath func(path string) (string, error)
|
|
if fsys, ok := fsys.(RealpathFS); ok {
|
|
realpath = func(path string) (string, error) {
|
|
rest, hadSlash := strings.CutPrefix(path, "/")
|
|
rp, err := fsys.Realpath(rest)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if hadSlash {
|
|
return "/" + rp, nil
|
|
}
|
|
return rp, nil
|
|
}
|
|
} else {
|
|
realpath = func(path string) (string, error) {
|
|
return path, nil
|
|
}
|
|
}
|
|
|
|
var writeFile func(path string, content string, writeByteOrderMark bool) error
|
|
var mkdirAll func(path string) error
|
|
var remove func(path string) error
|
|
var chtimes func(path string, aTime time.Time, mTime time.Time) error
|
|
if fsys, ok := fsys.(WritableFS); ok {
|
|
writeFile = func(path string, content string, writeByteOrderMark bool) error {
|
|
rest, _ := strings.CutPrefix(path, "/")
|
|
if writeByteOrderMark {
|
|
// Strada uses \uFEFF because NodeJS requires it, but substitutes it with the correct BOM based on the
|
|
// output encoding. \uFEFF is actually the BOM for big-endian UTF-16. For UTF-8 the actual BOM is
|
|
// \xEF\xBB\xBF.
|
|
content = stringutil.AddUTF8ByteOrderMark(content)
|
|
}
|
|
return fsys.WriteFile(rest, []byte(content), 0o666)
|
|
}
|
|
mkdirAll = func(path string) error {
|
|
rest, _ := strings.CutPrefix(path, "/")
|
|
return fsys.MkdirAll(rest, 0o777)
|
|
}
|
|
remove = func(path string) error {
|
|
rest, _ := strings.CutPrefix(path, "/")
|
|
return fsys.Remove(rest)
|
|
}
|
|
chtimes = func(path string, aTime time.Time, mTime time.Time) error {
|
|
rest, _ := strings.CutPrefix(path, "/")
|
|
return fsys.Chtimes(rest, aTime, mTime)
|
|
}
|
|
} else {
|
|
writeFile = func(string, string, bool) error {
|
|
panic("writeFile not supported")
|
|
}
|
|
mkdirAll = func(string) error {
|
|
panic("mkdirAll not supported")
|
|
}
|
|
remove = func(string) error {
|
|
panic("remove not supported")
|
|
}
|
|
chtimes = func(string, time.Time, time.Time) error {
|
|
panic("chtimes not supported")
|
|
}
|
|
}
|
|
|
|
return &ioFS{
|
|
common: internal.Common{
|
|
RootFor: func(root string) fs.FS {
|
|
if root == "/" {
|
|
return fsys
|
|
}
|
|
|
|
p := tspath.RemoveTrailingDirectorySeparator(root)
|
|
sub, err := fs.Sub(fsys, p)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("vfs: failed to create sub file system for %q: %v", p, err))
|
|
}
|
|
return sub
|
|
},
|
|
},
|
|
useCaseSensitiveFileNames: useCaseSensitiveFileNames,
|
|
realpath: realpath,
|
|
writeFile: writeFile,
|
|
mkdirAll: mkdirAll,
|
|
remove: remove,
|
|
chtimes: chtimes,
|
|
fsys: fsys,
|
|
}
|
|
}
|
|
|
|
type ioFS struct {
|
|
common internal.Common
|
|
|
|
useCaseSensitiveFileNames bool
|
|
realpath func(path string) (string, error)
|
|
writeFile func(path string, content string, writeByteOrderMark bool) error
|
|
mkdirAll func(path string) error
|
|
remove func(path string) error
|
|
chtimes func(path string, aTime time.Time, mTime time.Time) error
|
|
fsys fs.FS
|
|
}
|
|
|
|
var _ FsWithSys = (*ioFS)(nil)
|
|
|
|
func (vfs *ioFS) UseCaseSensitiveFileNames() bool {
|
|
return vfs.useCaseSensitiveFileNames
|
|
}
|
|
|
|
func (vfs *ioFS) DirectoryExists(path string) bool {
|
|
return vfs.common.DirectoryExists(path)
|
|
}
|
|
|
|
func (vfs *ioFS) FileExists(path string) bool {
|
|
return vfs.common.FileExists(path)
|
|
}
|
|
|
|
func (vfs *ioFS) GetAccessibleEntries(path string) vfs.Entries {
|
|
return vfs.common.GetAccessibleEntries(path)
|
|
}
|
|
|
|
func (vfs *ioFS) Stat(path string) vfs.FileInfo {
|
|
_ = internal.RootLength(path) // Assert path is rooted
|
|
return vfs.common.Stat(path)
|
|
}
|
|
|
|
func (vfs *ioFS) ReadFile(path string) (contents string, ok bool) {
|
|
return vfs.common.ReadFile(path)
|
|
}
|
|
|
|
func (vfs *ioFS) WalkDir(root string, walkFn vfs.WalkDirFunc) error {
|
|
return vfs.common.WalkDir(root, walkFn)
|
|
}
|
|
|
|
func (vfs *ioFS) Remove(path string) error {
|
|
_ = internal.RootLength(path) // Assert path is rooted
|
|
return vfs.remove(path)
|
|
}
|
|
|
|
func (vfs *ioFS) Chtimes(path string, aTime time.Time, mTime time.Time) error {
|
|
_ = internal.RootLength(path) // Assert path is rooted
|
|
return vfs.chtimes(path, aTime, mTime)
|
|
}
|
|
|
|
func (vfs *ioFS) Realpath(path string) string {
|
|
root, rest := internal.SplitPath(path)
|
|
// splitPath normalizes the path into parts (e.g. "c:/foo/bar" -> "c:/", "foo/bar")
|
|
// Put them back together to call realpath.
|
|
realpath, err := vfs.realpath(root + rest)
|
|
if err != nil {
|
|
return path
|
|
}
|
|
return realpath
|
|
}
|
|
|
|
func (vfs *ioFS) WriteFile(path string, content string, writeByteOrderMark bool) error {
|
|
_ = internal.RootLength(path) // Assert path is rooted
|
|
if err := vfs.writeFile(path, content, writeByteOrderMark); err == nil {
|
|
return nil
|
|
}
|
|
if err := vfs.mkdirAll(tspath.GetDirectoryPath(tspath.NormalizePath(path))); err != nil {
|
|
return err
|
|
}
|
|
return vfs.writeFile(path, content, writeByteOrderMark)
|
|
}
|
|
|
|
func (vfs *ioFS) FSys() fs.FS {
|
|
return vfs.fsys
|
|
}
|