From 6898d45c5e69c324350630f5590022c33bf8936c Mon Sep 17 00:00:00 2001 From: LandonTClipp Date: Mon, 7 Sep 2020 12:29:25 -0500 Subject: [PATCH 1/5] Fixing a few things Fixes #17. Make algorithm options enumerated. --- path.go | 20 +++++++++++++++----- walk.go | 37 ++++++++++++++++++++----------------- walk_test.go | 16 +++++++++------- 3 files changed, 44 insertions(+), 29 deletions(-) diff --git a/path.go b/path.go index 5ae5b18..97d58f2 100644 --- a/path.go +++ b/path.go @@ -7,8 +7,8 @@ import ( "strings" "time" - "github.com/pkg/errors" "github.com/LandonTClipp/afero" + "github.com/pkg/errors" ) // Path is an object that represents a path @@ -200,12 +200,22 @@ func (p *Path) IsEmpty() (bool, error) { } // ReadDir reads the current path and returns a list of the corresponding -// Path objects. +// Path objects. This function differs from os.Readdir in that it does +// not call Stat() on the files. Instead, it calls Readdirnames which +// is less expensive and does not force the caller to make expensive +// Stat calls. func (p *Path) ReadDir() ([]*Path, error) { var paths []*Path - fileInfos, err := afero.ReadDir(p.Fs(), p.Path()) - for _, fileInfo := range fileInfos { - paths = append(paths, p.Join(fileInfo.Name())) + handle, err := p.Open() + if err != nil { + return paths, err + } + children, err := handle.Readdirnames(-1) + if err != nil { + return paths, err + } + for _, child := range children { + paths = append(paths, p.Join(child)) } return paths, err } diff --git a/walk.go b/walk.go index c46a10b..5d96ce7 100644 --- a/walk.go +++ b/walk.go @@ -12,18 +12,15 @@ type WalkOpts struct { // infinite depth. 0 means only the direct children of root will be returned, etc. Depth int - // WalkAlgorithm specifies the algoritm that the Walk() function should use to + // Algorithm specifies the algoritm that the Walk() function should use to // traverse the directory. - WalkAlgorithm string + Algorithm Algorithm // FollowSymlinks defines whether symlinks should be dereferenced or not. If True, // the symlink itself will never be returned to WalkFunc, but rather whatever it // points to. Warning!!! You are exposing yourself to substantial risk by setting this // to True. Here be dragons! FollowSymlinks bool - - // Size of the FIFO queue used when doing a breadth-first search - FIFOQueueSize int } // DefaultWalkOpts returns the default WalkOpts struct used when @@ -31,19 +28,25 @@ type WalkOpts struct { func DefaultWalkOpts() *WalkOpts { return &WalkOpts{ Depth: -1, - WalkAlgorithm: AlgorithmBasic(), + Algorithm: AlgorithmBasic, FollowSymlinks: false, - FIFOQueueSize: 100, } } -func AlgorithmDepthFirst() string { - return "depth-first" -} - -func AlgorithmBasic() string { - return "basic" -} +// Algorithm represents the walk algorithm that will be performed. +type Algorithm int + +const ( + // AlgorithmBasic is a walk algorithm. It iterates over filesystem objects in the + // order in which they are returned by the operating system. It guarantees no + // ordering of any kind. This is the most efficient algorithm and should be used + // in all cases where ordering does not matter. + AlgorithmBasic Algorithm = iota + // AlgorithmDepthFirst is a walk algorithm. It iterates over a filesystem tree + // by first recursing as far down as it can in one path. Each directory is visited + // only after all of its children directories have been recursed. + AlgorithmDepthFirst +) // Walk is an object that handles walking through a directory tree type Walk struct { @@ -194,8 +197,8 @@ type WalkFunc func(path *Path, info os.FileInfo, err error) error // Walk walks the directory using the algorithm specified in the configuration. func (w *Walk) Walk(walkFn WalkFunc) error { - switch w.Opts.WalkAlgorithm { - case AlgorithmBasic(): + switch w.Opts.Algorithm { + case AlgorithmBasic: if err := w.walkBasic(walkFn, w.root, 0); err != nil { if errors.Is(err, ErrStopWalk) { return nil @@ -203,7 +206,7 @@ func (w *Walk) Walk(walkFn WalkFunc) error { return err } return nil - case AlgorithmDepthFirst(): + case AlgorithmDepthFirst: if err := w.walkDFS(walkFn, w.root, 0); err != nil { if errors.Is(err, ErrStopWalk) { return nil diff --git a/walk_test.go b/walk_test.go index 330ff98..448c138 100644 --- a/walk_test.go +++ b/walk_test.go @@ -60,7 +60,7 @@ type WalkSuiteAll struct { suite.Suite walk *Walk root *Path - algorithm string + algorithm Algorithm Fs afero.Fs } @@ -71,7 +71,7 @@ func (w *WalkSuiteAll) SetupTest() { w.root = NewPathAfero("/", w.Fs) w.walk, err = NewWalk(w.root) require.NoError(w.T(), err) - w.walk.Opts.WalkAlgorithm = w.algorithm + w.walk.Opts.Algorithm = w.algorithm } func (w *WalkSuiteAll) TeardownTest() { @@ -144,9 +144,9 @@ func (w *WalkSuiteAll) TestWalkFuncErr() { } func TestWalkSuite(t *testing.T) { - for _, algorithm := range []string{ - AlgorithmBasic(), - AlgorithmDepthFirst(), + for _, algorithm := range []Algorithm{ + AlgorithmBasic, + AlgorithmDepthFirst, } { walkSuite := new(WalkSuiteAll) walkSuite.algorithm = algorithm @@ -159,7 +159,7 @@ func TestDefaultWalkOpts(t *testing.T) { name string want *WalkOpts }{ - {"assert defaults", &WalkOpts{-1, AlgorithmBasic(), false, 100}}, + {"assert defaults", &WalkOpts{-1, AlgorithmBasic, false}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -170,6 +170,8 @@ func TestDefaultWalkOpts(t *testing.T) { } } +var ConfusedWandering Algorithm = 0xBADC0DE + func TestWalk_Walk(t *testing.T) { type fields struct { Opts *WalkOpts @@ -187,7 +189,7 @@ func TestWalk_Walk(t *testing.T) { { name: "Bad algoritm", fields: fields{ - Opts: &WalkOpts{WalkAlgorithm: "confused wandering"}, + Opts: &WalkOpts{Algorithm: ConfusedWandering}, }, wantErr: true, }, From 4c64bf324873ce5ebb7ddf7912e2bec0c01a2f15 Mon Sep 17 00:00:00 2001 From: LandonTClipp Date: Mon, 7 Sep 2020 12:35:00 -0500 Subject: [PATCH 2/5] This fixes #12 --- path.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/path.go b/path.go index 97d58f2..4b23b8f 100644 --- a/path.go +++ b/path.go @@ -84,7 +84,8 @@ func doesNotImplementErr(interfaceName string, fs afero.Fs) error { // Create creates a file if possible, returning the file and an error, if any happens. func (p *Path) Create() (afero.File, error) { - return p.Fs().Create(p.Path()) + file, err := p.Fs().Create(p.Path()) + return File{file}, err } // Mkdir makes the current dir. If the parents don't exist, an error From 78ded2bdec6afb97d3bf4c40910901ee1a59ec98 Mon Sep 17 00:00:00 2001 From: LandonTClipp Date: Mon, 7 Sep 2020 12:35:35 -0500 Subject: [PATCH 3/5] Fix return value --- path.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/path.go b/path.go index 4b23b8f..b89888e 100644 --- a/path.go +++ b/path.go @@ -83,7 +83,7 @@ func doesNotImplementErr(interfaceName string, fs afero.Fs) error { // ******************************* // Create creates a file if possible, returning the file and an error, if any happens. -func (p *Path) Create() (afero.File, error) { +func (p *Path) Create() (File, error) { file, err := p.Fs().Create(p.Path()) return File{file}, err } From e66b98d238bb89a044a5645815a70a3ccee2d4e0 Mon Sep 17 00:00:00 2001 From: LandonTClipp Date: Mon, 7 Sep 2020 13:00:27 -0500 Subject: [PATCH 4/5] Adding tests --- path_test.go | 23 +++++++++++++++++++++++ walk_test.go | 38 -------------------------------------- 2 files changed, 23 insertions(+), 38 deletions(-) diff --git a/path_test.go b/path_test.go index 1d3a376..c18b02f 100644 --- a/path_test.go +++ b/path_test.go @@ -301,6 +301,29 @@ func (p *PathSuite) TestEquals() { p.True(hello1.Equals(hello2)) } +func (p *PathSuite) TestReadDir() { + require.NoError(p.T(), TwoFilesAtRootTwoInSubdir(p.tmpdir)) + paths, err := p.tmpdir.ReadDir() + p.NoError(err) + p.Equal(3, len(paths)) +} + +func (p *PathSuite) TestReadDirInvalidPath() { + paths, err := p.tmpdir.Join("i_dont_exist").ReadDir() + p.Error(err) + p.Equal(0, len(paths)) +} + +func (p *PathSuite) TestCreate() { + msg := "hello world" + file, err := p.tmpdir.Join("hello.txt").Create() + p.NoError(err) + defer file.Close() + n, err := file.WriteString(msg) + p.Equal(len(msg), n) + p.NoError(err) +} + func TestPathSuite(t *testing.T) { suite.Run(t, new(PathSuite)) } diff --git a/walk_test.go b/walk_test.go index 448c138..6a0cc63 100644 --- a/walk_test.go +++ b/walk_test.go @@ -11,44 +11,6 @@ import ( "github.com/stretchr/testify/suite" ) -// *********************** -// * FILESYSTEM FIXTURES * -// *********************** -// The following functions provide different "scenarios" -// that you might encounter in a filesystem tree. - -func HelloWorld(root *Path) error { - hello := root.Join("hello.txt") - return hello.WriteFile([]byte("hello world"), 0o644) -} - -func OneFile(root *Path, name string, content string) error { - file := root.Join(name) - return file.WriteFile([]byte(content), 0o644) -} - -func NFiles(root *Path, n int) error { - for i := 0; i < n; i++ { - if err := OneFile(root, fmt.Sprintf("file%d.txt", i), fmt.Sprintf("file%d contents", i)); err != nil { - return err - } - } - return nil -} - -// TwoFilesAtRootTwoInSubdir creates two files in the root dir, -// a directory, and creates two files inside that new directory. -func TwoFilesAtRootTwoInSubdir(root *Path) error { - if err := NFiles(root, 2); err != nil { - return err - } - subdir := root.Join("subdir") - if err := subdir.Mkdir(0o777); err != nil { - return err - } - return NFiles(subdir, 2) -} - // ********* // * TESTS * // ********* From 35efbc11c22bca230f3918432ec8a3c29f8118ee Mon Sep 17 00:00:00 2001 From: LandonTClipp Date: Mon, 7 Sep 2020 13:02:42 -0500 Subject: [PATCH 5/5] Adding scenarios as a separate file --- scenarios_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 scenarios_test.go diff --git a/scenarios_test.go b/scenarios_test.go new file mode 100644 index 0000000..5ce9781 --- /dev/null +++ b/scenarios_test.go @@ -0,0 +1,38 @@ +package pathlib + +import "fmt" + +// The following functions provide different "scenarios" +// that you might encounter in a filesystem tree. + +func HelloWorld(root *Path) error { + hello := root.Join("hello.txt") + return hello.WriteFile([]byte("hello world"), 0o644) +} + +func OneFile(root *Path, name string, content string) error { + file := root.Join(name) + return file.WriteFile([]byte(content), 0o644) +} + +func NFiles(root *Path, n int) error { + for i := 0; i < n; i++ { + if err := OneFile(root, fmt.Sprintf("file%d.txt", i), fmt.Sprintf("file%d contents", i)); err != nil { + return err + } + } + return nil +} + +// TwoFilesAtRootTwoInSubdir creates two files in the root dir, +// a directory, and creates two files inside that new directory. +func TwoFilesAtRootTwoInSubdir(root *Path) error { + if err := NFiles(root, 2); err != nil { + return err + } + subdir := root.Join("subdir") + if err := subdir.Mkdir(0o777); err != nil { + return err + } + return NFiles(subdir, 2) +}