From eab06e2f6c5764b7e8bcb91683a124b13c741205 Mon Sep 17 00:00:00 2001 From: Matthew Rich Date: Thu, 19 Sep 2024 05:25:24 +0000 Subject: [PATCH] add pkg to handle walking a directory structure --- internal/fs/fs.go | 80 ++++++++++++++++++++++++++++++++++++++++++ internal/fs/fs_test.go | 46 ++++++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 internal/fs/fs.go create mode 100644 internal/fs/fs_test.go diff --git a/internal/fs/fs.go b/internal/fs/fs.go new file mode 100644 index 0000000..b34d020 --- /dev/null +++ b/internal/fs/fs.go @@ -0,0 +1,80 @@ +// Copyright 2024 Matthew Rich . All rights reserved. + +package fs + +import ( + "io/fs" + "log/slog" + "path/filepath" + "fmt" +) + +type DirEntry fs.DirEntry +type FS fs.FS +type Selector[Item comparable] func(r Item) bool + +type WalkFunc func(fsys FS, path string, file DirEntry) (err error) + +type WalkDir struct { + root *WorkingDir + subDirsStack []*WorkingDir `yaml:"-" json:"-"` + fn WalkFunc +} + +type WorkingDir struct { + path string + fsys FS +} + +func NewWalkDir(fsys FS, path string, fn WalkFunc) *WalkDir { + w := &WalkDir{ + subDirsStack: make([]*WorkingDir, 0, 100), + fn: fn, + } + + w.root = &WorkingDir{ path: path, fsys: fsys } + w.subDirsStack = append(w.subDirsStack, w.root) + return w +} + +func (w *WalkDir) WalkDirectory(fsys FS, path string, filter Selector[DirEntry]) (err error) { + var files []fs.DirEntry + + if files, err = fs.ReadDir(fsys, "."); err == nil { + for _,file := range files { + if filter != nil && filter(file) { + continue + } + slog.Info("fs.WalkDir.WalkDirectory()", "file", file, "path", path, "file", file.Name()) + entryPath := filepath.Join(path, file.Name()) + if err = w.fn(fsys, entryPath, file); err != nil { + slog.Info("fs.WalkDir.WalkDirectory() ERROR", "file", entryPath, "error", err) + return + } + if file.IsDir() { + var dir FS + if dir, err = fs.Sub(fsys, file.Name()); err != nil { + slog.Info("fs.WalkDir.WalkDirectory() SubDir ERROR", "dir", dir, "error", err) + return + } else { + w.subDirsStack = append(w.subDirsStack, &WorkingDir{ path: entryPath, fsys: dir }) + } + } + } + } else { + err = fmt.Errorf("fs.ReadDir failed on path %s: %w", path, err) + } + return +} + +func (w *WalkDir) Walk(filter Selector[DirEntry]) (err error) { + for len(w.subDirsStack) != 0 { + var dirPath *WorkingDir + dirPath, w.subDirsStack = w.subDirsStack[len(w.subDirsStack) - 1], w.subDirsStack[:len(w.subDirsStack) - 1] + if err = w.WalkDirectory(dirPath.fsys, dirPath.path, filter); err != nil { + return + } + } + return +} + diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go new file mode 100644 index 0000000..9876232 --- /dev/null +++ b/internal/fs/fs_test.go @@ -0,0 +1,46 @@ +// Copyright 2024 Matthew Rich . All rights reserved. + +package fs + +import ( + "github.com/stretchr/testify/assert" + "testing" + "os" + "log/slog" + "decl/tests/tempdir" +_ "path/filepath" +) + +var TempDir tempdir.Path = "testfswalkdir" + +func TestMain(m *testing.M) { + err := TempDir.Create() + if err != nil || TempDir == "" { + slog.Error("Failed creating temp dir", "error", err) + } + + rc := m.Run() + + TempDir.Remove() + os.Exit(rc) +} + +func TestNewWalkDir(t *testing.T) { + expected := []string { "bar", "foo.txt" } + assert.Nil(t, TempDir.CreateFile("foo.txt", "testdata")) + assert.Nil(t, TempDir.Mkdir("bar", 0700)) + + fileSystem := TempDir.DirFS() + i := 0 + d := NewWalkDir(fileSystem, string(TempDir), func(fsys FS, path string, entry DirEntry) (err error) { + slog.Info("TestWalkDir()", "path", path, "entry", entry) + assert.Equal(t, expected[i], entry.Name()) + i++ + return nil + }) + assert.NotNil(t, d) + assert.Nil(t, d.Walk(nil)) +} + +func TestWalk(t *testing.T) { +}