package file import ( "errors" "io/fs" "os" "path/filepath" "strings" "github.com/diskfs/go-diskfs/filesystem" ) type WalkDiskDirFunc func(fsys filesystem.FileSystem, path string, d os.FileInfo, err error) error // WalkDiskDir walks the file tree within the go-diskfs filesystem at root, calling fn for each file or directory in the tree, including root. // This is meant to mimic the behavior of fs.WalkDir in the standard library. func WalkDiskDir(fsys filesystem.FileSystem, root string, fn WalkDiskDirFunc) error { infos, err := readDiskDir(fsys, root) if err != nil { return err } if len(infos) == 0 { return nil } for _, info := range infos { p := filepath.Join(root, info.Name()) err = walkDiskDir(fsys, p, info, fn) if err != nil { if errors.Is(err, fs.SkipDir) { continue } if errors.Is(err, fs.SkipAll) { return nil } return err } } return err } func walkDiskDir(fsys filesystem.FileSystem, name string, d os.FileInfo, walkDirFn WalkDiskDirFunc) error { if err := walkDirFn(fsys, name, d, nil); err != nil { if errors.Is(err, fs.SkipDir) && (d == nil || d.IsDir()) { return nil } return err } isDir := d != nil && d.IsDir() if d == nil { _, err := readDiskDir(fsys, name) if err != nil { return nil } isDir = true } if !isDir { return nil } dirs, err := readDiskDir(fsys, name) if err != nil { err = walkDirFn(fsys, name, d, err) if err != nil { if errors.Is(err, fs.SkipDir) { return nil } return err } } for _, d1 := range dirs { name1 := filepath.Join(name, d1.Name()) if err := walkDiskDir(fsys, name1, d1, walkDirFn); err != nil { if errors.Is(err, fs.SkipDir) { break } if errors.Is(err, fs.SkipAll) { return err } return err } } return nil } // readDiskDir reads the directory entries at the given path from a go-diskfs filesystem. // go-diskfs returns fs.DirEntry values; these are resolved to os.FileInfo so callers have // access to the full file metadata (mode, size, modification time). func readDiskDir(fsys filesystem.FileSystem, p string) ([]os.FileInfo, error) { entries, err := fsys.ReadDir(ToFSPath(p)) if err != nil { return nil, err } infos := make([]os.FileInfo, 0, len(entries)) for _, entry := range entries { info, err := entry.Info() if err != nil { return nil, err } infos = append(infos, info) } return infos, nil } // ToFSPath converts an absolute ("/"-rooted) path into an io/fs-valid path as required by // go-diskfs, where the root is "." and other paths carry no leading slash (see io/fs.ValidPath). func ToFSPath(p string) string { p = strings.TrimPrefix(p, "/") if p == "" { return "." } return p }