Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions path.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -83,8 +83,9 @@ 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())
func (p *Path) Create() (File, error) {
file, err := p.Fs().Create(p.Path())
return File{file}, err
}

// Mkdir makes the current dir. If the parents don't exist, an error
Expand Down Expand Up @@ -200,12 +201,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
}
Expand Down
23 changes: 23 additions & 0 deletions path_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down
38 changes: 38 additions & 0 deletions scenarios_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
41 changes: 20 additions & 21 deletions walk.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ 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
Expand All @@ -37,26 +37,25 @@ type WalkOpts struct {
func DefaultWalkOpts() *WalkOpts {
return &WalkOpts{
Depth: -1,
WalkAlgorithm: AlgorithmBasic(),
Algorithm: AlgorithmBasic,
FollowSymlinks: false,
// VisitFirst: false,
}
}

// 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.
func AlgorithmDepthFirst() string {
return "depth-first"
}

// 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.
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 {
Expand Down Expand Up @@ -207,16 +206,16 @@ 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
}
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
Expand Down
54 changes: 9 additions & 45 deletions walk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
// *********
Expand All @@ -60,7 +22,7 @@ type WalkSuiteAll struct {
suite.Suite
walk *Walk
root *Path
algorithm string
algorithm Algorithm
Fs afero.Fs
}

Expand All @@ -71,7 +33,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() {
Expand Down Expand Up @@ -144,9 +106,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
Expand All @@ -159,7 +121,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) {
Expand All @@ -170,6 +132,8 @@ func TestDefaultWalkOpts(t *testing.T) {
}
}

var ConfusedWandering Algorithm = 0xBADC0DE

func TestWalk_Walk(t *testing.T) {
type fields struct {
Opts *WalkOpts
Expand All @@ -187,7 +151,7 @@ func TestWalk_Walk(t *testing.T) {
{
name: "Bad algoritm",
fields: fields{
Opts: &WalkOpts{WalkAlgorithm: "confused wandering"},
Opts: &WalkOpts{Algorithm: ConfusedWandering},
},
wantErr: true,
},
Expand Down