package filesystem import ( "io" "io/fs" "os" "path/filepath" "time" copyDir "github.com/otiai10/copy" ) type fileStat struct { name string size int64 mode fs.FileMode modTime time.Time } func (fs fileStat) Name() string { return fs.name } func (fs fileStat) Size() int64 { return fs.size } func (fs fileStat) Mode() fs.FileMode { return fs.mode } func (fs fileStat) ModTime() time.Time { return fs.modTime } func (fs fileStat) IsDir() bool { return fs.mode.IsDir() } func (fs fileStat) Sys() any { return nil } type FileSystem struct { ReadFile func(string) ([]byte, error) ReadDir func(string) ([]fs.DirEntry, error) DeleteFile func(string) error FileExists func(string) (bool, error) Glob func(string) ([]string, error) FileExistsAt func(string) bool DirectoryExistsAt func(string) bool Dir func(string) string Stat func(string) (os.FileInfo, error) Getwd func() (string, error) Chdir func(string) error Abs func(string) (string, error) EvalSymlinks func(string) (string, error) CopyDir func(src, dst string) error } func DefaultFileSystem() *FileSystem { dfs := FileSystem{ ReadDir: os.ReadDir, DeleteFile: os.Remove, Glob: filepath.Glob, Getwd: os.Getwd, Chdir: os.Chdir, EvalSymlinks: filepath.EvalSymlinks, Dir: filepath.Dir, } dfs.Stat = dfs.stat dfs.ReadFile = dfs.readFile dfs.FileExistsAt = dfs.fileExistsAtDefault dfs.DirectoryExistsAt = dfs.directoryExistsDefault dfs.FileExists = dfs.fileExistsDefault dfs.Abs = dfs.absDefault dfs.CopyDir = dfs.copyDirDefault return &dfs } func FromFileSystem(params FileSystem) *FileSystem { dfs := DefaultFileSystem() if params.ReadFile != nil { dfs.ReadFile = params.ReadFile } if params.ReadDir != nil { dfs.ReadDir = params.ReadDir } if params.DeleteFile != nil { dfs.DeleteFile = params.DeleteFile } if params.FileExists != nil { dfs.FileExists = params.FileExists } if params.Glob != nil { dfs.Glob = params.Glob } if params.FileExistsAt != nil { dfs.FileExistsAt = params.FileExistsAt } if params.DirectoryExistsAt != nil { dfs.DirectoryExistsAt = params.DirectoryExistsAt } if params.Stat != nil { dfs.Stat = params.Stat } if params.Getwd != nil { dfs.Getwd = params.Getwd } if params.Chdir != nil { dfs.Chdir = params.Chdir } if params.Abs != nil { dfs.Abs = params.Abs } if params.EvalSymlinks != nil { dfs.EvalSymlinks = params.EvalSymlinks } if params.Dir != nil { dfs.Dir = params.Dir } if params.CopyDir != nil { dfs.CopyDir = params.CopyDir } return dfs } func (filesystem *FileSystem) stat(name string) (os.FileInfo, error) { if name == "-" { return fileStat{mode: 0}, nil } return os.Stat(name) } func (filesystem *FileSystem) readFile(name string) ([]byte, error) { if name == "-" { return io.ReadAll(os.Stdin) } return os.ReadFile(name) } func (filesystem *FileSystem) fileExistsAtDefault(path string) bool { path, err := filesystem.resolveSymlinks(path) if err != nil { return false } fileInfo, err := filesystem.Stat(path) return err == nil && fileInfo.Mode().IsRegular() } func (filesystem *FileSystem) fileExistsDefault(path string) (bool, error) { path, err := filesystem.resolveSymlinks(path) if err != nil { if os.IsNotExist(err) { return false, nil } return false, err } _, err = filesystem.Stat(path) if err != nil { if os.IsNotExist(err) { return false, nil } return false, err } return true, nil } func (filesystem *FileSystem) directoryExistsDefault(path string) bool { path, err := filesystem.resolveSymlinks(path) if err != nil { return false } fileInfo, err := filesystem.Stat(path) return err == nil && fileInfo.Mode().IsDir() } func (filesystem *FileSystem) resolveSymlinks(path string) (string, error) { if !filepath.IsAbs(path) && !filepath.IsLocal(path) { basePath, err := filesystem.Getwd() if err != nil { return "", err } basePath, err = filesystem.EvalSymlinks(basePath) if err != nil { return "", err } path, err := filesystem.EvalSymlinks(filepath.Join(basePath, path)) if err != nil { return "", err } return path, nil } return path, nil } func (filesystem *FileSystem) absDefault(path string) (string, error) { path, err := filesystem.resolveSymlinks(path) if err != nil { return "", err } return filepath.Abs(path) } // copyDirDefault recursively copies a directory tree, preserving permissions. func (filesystem *FileSystem) copyDirDefault(src string, dst string) error { return copyDir.Copy(src, dst, copyDir.Options{Sync: true}) }