diff --git a/go.mod b/go.mod index f788a0e..5f045b8 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module code.geekeey.de/actions/sdk +module git.geekeey.de/actions/sdk go 1.22.5 diff --git a/iox/globfs.go b/iox/globfs.go deleted file mode 100644 index 91240cb..0000000 --- a/iox/globfs.go +++ /dev/null @@ -1,99 +0,0 @@ -package iox - -import ( - "io/fs" - "os" - "path" -) - -type GlobFS struct { - base fs.FS - patterns []string -} - -// NewGlobFS creates a new GlobFS that exposes only files matching any of the given glob patterns. -func NewGlobFS(base fs.FS, patterns ...string) *GlobFS { - return &GlobFS{base: base, patterns: patterns} -} - -// match reports whether the given path matches any of the configured patterns. -func (g *GlobFS) match(name string) bool { - for _, pat := range g.patterns { - if matched, _ := path.Match(pat, name); matched { - return true - } - } - return false -} - -func (g *GlobFS) contains(dir string) bool { - queue := []string{dir} - visited := make(map[string]struct{}) - for len(queue) > 0 { - current := queue[0] - queue = queue[1:] // dequeue - - // Prevent visiting same dir multiple times - if _, seen := visited[current]; seen { - continue - } - visited[current] = struct{}{} - - entries, err := fs.ReadDir(g.base, current) - if err != nil { - continue - } - for _, entry := range entries { - rel := path.Join(current, entry.Name()) - if g.match(rel) { - return true - } - if entry.IsDir() { - queue = append(queue, rel) - } - } - } - return false -} - -func (g *GlobFS) Open(name string) (fs.File, error) { - if g.match(name) { - return g.base.Open(name) - } - - fi, err := fs.Stat(g.base, name) - if err != nil || !fi.IsDir() { - return nil, fs.ErrNotExist - } - if g.contains(name) { - return g.base.Open(name) - } - return nil, fs.ErrNotExist -} - -func (g *GlobFS) ReadDir(name string) ([]fs.DirEntry, error) { - if g.match(name) { - return fs.ReadDir(g.base, name) - } - - entries, err := fs.ReadDir(g.base, name) - if err != nil { - return nil, err - } - var children []fs.DirEntry - for _, entry := range entries { - rel := path.Join(name, entry.Name()) - if g.match(rel) { - children = append(children, entry) - } - if entry.IsDir() && g.contains(rel) { - children = append(children, entry) - } - } - - if len(children) == 0 { - return nil, os.ErrNotExist - } - - return children, nil -} diff --git a/iox/globfs_test.go b/iox/globfs_test.go deleted file mode 100644 index e548a2a..0000000 --- a/iox/globfs_test.go +++ /dev/null @@ -1,234 +0,0 @@ -package iox - -import ( - "io/fs" - "reflect" - "sort" - "testing" - "testing/fstest" -) - -func setupFS() fs.ReadDirFS { - // Create an in-memory FS with a mix of files and directories - return fstest.MapFS{ - "main.go": &fstest.MapFile{Data: []byte("package main")}, - "main_test.go": &fstest.MapFile{Data: []byte("package main_test")}, - "README.md": &fstest.MapFile{Data: []byte("# readme")}, - "LICENSE": &fstest.MapFile{Data: []byte("MIT")}, - "docs/guide.md": &fstest.MapFile{Data: []byte("Docs")}, - "docs/other.txt": &fstest.MapFile{Data: []byte("Other")}, - "docs/hidden/.keep": &fstest.MapFile{Data: []byte("")}, - "assets/img.png": &fstest.MapFile{Data: []byte("PNG")}, - "assets/style.css": &fstest.MapFile{Data: []byte("CSS")}, - ".gitignore": &fstest.MapFile{Data: []byte("*.log")}, - ".hiddenfile": &fstest.MapFile{Data: []byte("")}, - "emptydir/": &fstest.MapFile{Mode: fs.ModeDir}, - } -} - -// helper to get base names for easier comparison -func basenames(entries []fs.DirEntry) []string { - names := []string{} - for _, e := range entries { - names = append(names, e.Name()) - } - sort.Strings(names) - return names -} - -func TestGlobFSMultiplePatterns(t *testing.T) { - memfs := setupFS() - gfs := NewGlobFS(memfs, "*.go", "*.md", "assets/*", "docs/guide.md", ".gitignore") - - tests := []struct { - path string - want []string - wantErr bool - }{ - {path: ".", want: []string{"README.md", "assets", "docs", "main.go", "main_test.go", ".gitignore"}}, - {path: "assets", want: []string{"img.png", "style.css"}}, - {path: "docs", want: []string{"guide.md"}}, - {path: "docs/hidden", want: []string{}, wantErr: true}, - {path: "emptydir", want: []string{}, wantErr: true}, - } - - for _, tc := range tests { - tc := tc // capture range variable - t.Run(tc.path, func(t *testing.T) { - entries, err := fs.ReadDir(gfs, tc.path) - if tc.wantErr && err == nil { - t.Errorf("expected error, got nil") - return - } - if !tc.wantErr && err != nil { - t.Errorf("unexpected error: %v", err) - return - } - got := basenames(entries) - sort.Strings(tc.want) - if !reflect.DeepEqual(got, tc.want) { - t.Errorf("got %v; want %v", got, tc.want) - } - }) - } -} - -func TestGlobFSOpen(t *testing.T) { - memfs := setupFS() - gfs := NewGlobFS(memfs, "*.go", "*.md", "assets/*", "docs/guide.md", ".gitignore") - - type test struct { - path string - wantErr bool - } - tests := []test{ - {path: "main.go"}, - {path: "README.md"}, - {path: "LICENSE", wantErr: true}, - {path: "assets/img.png"}, - {path: "assets/style.css"}, - {path: "assets/nonexistent.png", wantErr: true}, - {path: "docs/guide.md"}, - {path: "docs/other.txt", wantErr: true}, - {path: ".gitignore"}, - {path: ".hiddenfile", wantErr: true}, - {path: "docs/hidden/.keep", wantErr: true}, - {path: "emptydir", wantErr: true}, - {path: "docs"}, // allowed because it contains matching file(s) - {path: "assets"}, // allowed because it contains matching file(s) - } - for _, tc := range tests { - tc := tc - t.Run(tc.path, func(t *testing.T) { - f, err := gfs.Open(tc.path) - if tc.wantErr && err == nil { - t.Errorf("expected error, got file") - if f != nil { - f.Close() - } - } else if !tc.wantErr && err != nil { - t.Errorf("unexpected error: %v", err) - } else if !tc.wantErr && err == nil { - info, _ := f.Stat() - if info.IsDir() { - _, derr := fs.ReadDir(gfs, tc.path) - if derr != nil && !tc.wantErr { - t.Errorf("unexpected error: %v", derr) - } - } - f.Close() - } - }) - } -} - -func TestGlobFSReadFile(t *testing.T) { - memfs := setupFS() - gfs := NewGlobFS(memfs, "*.go", "*.md", "assets/*", ".gitignore") - - tests := []struct { - name string - want []byte - wantErr bool - }{ - {name: "main.go", want: []byte("package main")}, - {name: "main_test.go", want: []byte("package main_test")}, - {name: "README.md", want: []byte("# readme")}, - {name: "assets/img.png", want: []byte("PNG")}, - {name: "assets/style.css", want: []byte("CSS")}, - {name: ".gitignore", want: []byte("*.log")}, - {name: "LICENSE", wantErr: true}, // not allowed by filter - {name: "docs/guide.md", wantErr: true}, // not allowed by filter - {name: "docs/hidden/.keep", wantErr: true}, // not allowed by filter - {name: "doesnotexist.txt", wantErr: true}, // does not exist - } - - for _, tc := range tests { - tc := tc - t.Run(tc.name, func(t *testing.T) { - got, err := fs.ReadFile(gfs, tc.name) - if tc.wantErr { - if err == nil { - t.Errorf("expected error, got nil (got=%q)", got) - } - } else { - if err != nil { - t.Errorf("unexpected error: %v", err) - } - if string(got) != string(tc.want) { - t.Errorf("got %q; want %q", got, tc.want) - } - } - }) - } -} - -func TestGlobFSRelativePaths(t *testing.T) { - memfs := setupFS() - gfs := NewGlobFS(memfs, "docs/*.md") - entries, err := fs.ReadDir(gfs, "docs") - if err != nil { - t.Fatal(err) - } - got := basenames(entries) - want := []string{"guide.md"} - if !reflect.DeepEqual(got, want) { - t.Errorf("docs/*.md: got %v, want %v", got, want) - } -} - -func TestGlobFSNoMatchesOpen(t *testing.T) { - gfs := NewGlobFS(setupFS(), "*.xyz") - _, err := gfs.Open("main.go") - if err == nil { - t.Fatal("expected error when opening file with no matches") - } -} - -func TestGlobFSNoMatchesStat(t *testing.T) { - gfs := NewGlobFS(setupFS(), "*.xyz") - _, err := fs.Stat(gfs, "main.go") - if err == nil { - t.Fatal("expected error with no matches: stat") - } -} - -func TestGlobFSNoMatchesReadDir(t *testing.T) { - gfs := NewGlobFS(setupFS(), "*.xyz") - _, err := fs.ReadDir(gfs, "main.go") - if err == nil { - t.Fatal("expected error with no matches: readdir") - } -} - -func TestGlobFSNoMatchesReadFile(t *testing.T) { - gfs := NewGlobFS(setupFS(), "*.xyz") - _, err := fs.ReadFile(gfs, "main.go") - if err == nil { - t.Fatal("expected error with no matches: readfile") - } -} - -func TestGlobFS_IntegrationWithStdlib(t *testing.T) { - memfs := setupFS() - gfs := NewGlobFS(memfs, "*.go", "docs/guide.md") - // Use fs.WalkDir with our filtered FS - var walked []string - err := fs.WalkDir(gfs, ".", func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - walked = append(walked, path) - return nil - }) - if err != nil { - t.Fatal(err) - } - // Only files and dirs matching or containing matches should appear - for _, p := range walked { - if p == "." || p == "main.go" || p == "main_test.go" || p == "docs" || p == "docs/guide.md" { - continue - } - t.Errorf("WalkDir: unexpected path %q", p) - } -}