diff --git a/.github/tests/example/example1.txt b/.github/tests/example/example1.txt new file mode 100644 index 0000000..e69de29 diff --git a/.github/tests/john.txt b/.github/tests/john.txt new file mode 100644 index 0000000..e71ae8e --- /dev/null +++ b/.github/tests/john.txt @@ -0,0 +1 @@ +doe diff --git a/file.go b/file.go new file mode 100644 index 0000000..0ae8f22 --- /dev/null +++ b/file.go @@ -0,0 +1,110 @@ +package utils + +import ( + "io" + "net/http" + "os" + pathpkg "path" + "path/filepath" + "sort" +) + +// Walk walks the filesystem rooted at root, calling walkFn for each file or +// directory in the filesystem, including root. All errors that arise visiting files +// and directories are filtered by walkFn. The files are walked in lexical +// order. +func Walk(fs http.FileSystem, root string, walkFn filepath.WalkFunc) error { + info, err := stat(fs, root) + if err != nil { + return walkFn(root, nil, err) + } + return walk(fs, root, info, walkFn) +} + +// #nosec G304 +// ReadFile returns the raw content of a file +func ReadFile(path string, fs http.FileSystem) ([]byte, error) { + if fs != nil { + file, err := fs.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + return io.ReadAll(file) + } + return os.ReadFile(path) +} + +// readDirNames reads the directory named by dirname and returns +// a sorted list of directory entries. +func readDirNames(fs http.FileSystem, dirname string) ([]string, error) { + fis, err := readDir(fs, dirname) + if err != nil { + return nil, err + } + names := make([]string, len(fis)) + for i := range fis { + names[i] = fis[i].Name() + } + sort.Strings(names) + return names, nil +} + +// walk recursively descends path, calling walkFn. +func walk(fs http.FileSystem, path string, info os.FileInfo, walkFn filepath.WalkFunc) error { + err := walkFn(path, info, nil) + if err != nil { + if info.IsDir() && err == filepath.SkipDir { + return nil + } + return err + } + + if !info.IsDir() { + return nil + } + + names, err := readDirNames(fs, path) + if err != nil { + return walkFn(path, info, err) + } + + for _, name := range names { + filename := pathpkg.Join(path, name) + fileInfo, err := stat(fs, filename) + if err != nil { + if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir { + return err + } + } else { + err = walk(fs, filename, fileInfo, walkFn) + if err != nil { + if !fileInfo.IsDir() || err != filepath.SkipDir { + return err + } + } + } + } + return nil +} + +// readDir reads the contents of the directory associated with file and +// returns a slice of FileInfo values in directory order. +func readDir(fs http.FileSystem, name string) ([]os.FileInfo, error) { + f, err := fs.Open(name) + if err != nil { + return nil, err + } + defer f.Close() + return f.Readdir(0) +} + +// stat returns the FileInfo structure describing file. +func stat(fs http.FileSystem, name string) (os.FileInfo, error) { + f, err := fs.Open(name) + if err != nil { + return nil, err + } + defer f.Close() + return f.Stat() +} diff --git a/file_test.go b/file_test.go new file mode 100644 index 0000000..524616e --- /dev/null +++ b/file_test.go @@ -0,0 +1,65 @@ +package utils + +import ( + "io/fs" + "net/http" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_ReadFile(t *testing.T) { + t.Parallel() + + testFS := http.FS(os.DirFS(".github/tests")) + file, err := ReadFile("john.txt", testFS) + + require.Equal(t, string(file), "doe\n") + require.NoError(t, err) +} + +func Test_Walk(t *testing.T) { + t.Parallel() + + type file struct { + path string + name string + isDir bool + } + var files []file + + neededResults := []file{ + { + path: "example", + name: "example", + isDir: true, + }, + { + path: "example/example1.txt", + name: "example1.txt", + isDir: false, + }, + { + path: "john.txt", + name: "john.txt", + isDir: false, + }, + } + + testFS := http.FS(os.DirFS(".github/tests")) + err := Walk(testFS, ".", func(path string, info fs.FileInfo, err error) error { + if path != "." { + files = append(files, file{ + path: path, + name: info.Name(), + isDir: info.IsDir(), + }) + } + + return nil + }) + + require.NoError(t, err) + require.Equal(t, files, neededResults) +}