250514.4 · bc5b513dc10438260287d7bf1b1a785d4b9720d1
fastwalk: add example tests
reading declarations
@@ -0,0 +1,178 @@+package fastwalk_test++import (+ "fmt"+ "io/fs"+ "os"+ "path/filepath"+ "strings"++ "github.com/charlievieth/fastwalk"+)++func CreateFiles(files map[string]string) (root string, cleanup func()) {+ tempdir, err := os.MkdirTemp("", "fastwalk-example-*")+ if err != nil {+ panic(err)+ }++ symlinks := map[string]string{}+ for path, contents := range files {+ file := filepath.Join(tempdir, "/src", path)+ if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil {+ panic(err)+ }+ if strings.HasPrefix(contents, "LINK:") {+ symlinks[file] = filepath.FromSlash(strings.TrimPrefix(contents, "LINK:"))+ continue+ }+ if err := os.WriteFile(file, []byte(contents), 0644); err != nil {+ panic(err)+ }+ }++ // Create symlinks after all other files. Otherwise, directory symlinks on+ // Windows are unusable (see https://golang.org/issue/39183).+ for file, dst := range symlinks {+ if err := os.Symlink(dst, file); err != nil {+ panic(err)+ }+ }++ return filepath.Join(tempdir, "src") + "/", func() { os.RemoveAll(tempdir) }+}++func PrettyPrintEntry(root, path string, de fs.DirEntry) {+ rel, err := filepath.Rel(root, path)+ if err != nil {+ panic(err)+ }+ rel = filepath.ToSlash(rel)+ if de.Type()&fs.ModeSymlink != 0 {+ dst, err := os.Readlink(path)+ if err != nil {+ panic(err)+ }+ fmt.Printf("%s: %s -> %s\n", de.Type(), rel, filepath.ToSlash(dst))+ } else {+ fmt.Printf("%s: %s\n", de.Type(), rel)+ }+}++// This example shows how the [fastwalk.Config] Follow field can be used to+// efficiently and safely follow symlinks. The below example contains a symlink+// loop ("bar/symloop"), which fastwalk detects and does not follow.+//+// NOTE: We still call the [fs.WalkDirFunc] on the symlink that creates a loop,+// but we do not follow/traverse it.+func ExampleWalk_follow() {+ // Setup+ root, cleanup := CreateFiles(map[string]string{+ "bar/bar.go": "one",+ "bar/symlink": "LINK:bar.go",+ "foo/foo.go": "two",+ "foo/symdir": "LINK:../bar/",+ "foo/broken": "LINK:nope.txt", // Broken symlink+ "foo/foo": "LINK:../foo/", // Symlink loop+ })+ defer cleanup()++ conf := fastwalk.Config{+ Follow: true,+ }+ err := fastwalk.Walk(&conf, root, func(path string, de fs.DirEntry, err error) error {+ if err != nil {+ return err+ }+ PrettyPrintEntry(root, path, de)+ return nil+ })+ if err != nil {+ panic(err)+ }+ // Unordered output:+ // d---------: .+ // d---------: bar+ // ----------: bar/bar.go+ // L---------: bar/symlink -> bar.go+ // d---------: foo+ // L---------: foo/broken -> nope.txt+ // ----------: foo/foo.go+ // L---------: foo/foo -> ../foo/+ // L---------: foo/symdir -> ../bar/+ // L---------: foo/symdir/symlink -> bar.go+ // ----------: foo/symdir/bar.go+}++func ExampleWalk() {+ root, cleanup := CreateFiles(map[string]string{+ "bar/b.txt": "",+ "foo/f.txt": "",+ // Since Config.Follow is set to false, the symbolic link "link" will+ // be visited, but we will not traverse into it (since our walk func+ // does not return [fastwalk.ErrTraverseLink]).+ "link": "LINK:bar",+ })+ defer cleanup()++ err := fastwalk.Walk(nil, root, func(path string, de fs.DirEntry, err error) error {+ if err != nil {+ return err+ }+ PrettyPrintEntry(root, path, de)+ return nil+ })+ if err != nil {+ panic(err)+ }+ // Unordered output:+ // d---------: .+ // d---------: bar+ // d---------: foo+ // L---------: link -> bar+ // ----------: bar/b.txt+ // ----------: foo/f.txt+}++func ExampleWalk_traverseLink() {+ root, cleanup := CreateFiles(map[string]string{+ "bar/b.txt": "",+ "foo/f.txt": "",+ // This link is followed since our walk func returns ErrTraverseLink+ // when a symlink that resolves to a directory is encountered.+ "link": "LINK:bar",+ })+ defer cleanup()++ err := fastwalk.Walk(nil, root, func(path string, de fs.DirEntry, err error) error {+ if err != nil {+ return err+ }+ PrettyPrintEntry(root, path, de)++ // It is strongly recommended to use fastwalk.Config.Follow instead+ // of manually handling link traversal since this will fail if the+ // link points to a file (and not a directory).+ if de.Type()&fs.ModeSymlink != 0 {+ fi, err := fastwalk.StatDirEntry(path, de)+ if err != nil {+ return err+ }+ if fi.IsDir() {+ return fastwalk.ErrTraverseLink+ }+ }+ return nil+ })+ if err != nil {+ panic(err)+ }+ // Unordered output:+ // d---------: .+ // d---------: bar+ // d---------: foo+ // L---------: link -> bar+ // ----------: bar/b.txt+ // ----------: foo/f.txt+ // ----------: link/b.txt+}