-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #16 from LandonTClipp/walk
Adding walk functionality
- Loading branch information
Showing
11 changed files
with
822 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
quiet: False | ||
all: True | ||
inpackage: True | ||
testonly: True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,15 @@ | ||
package pathlib | ||
|
||
import "github.com/pkg/errors" | ||
import "fmt" | ||
|
||
var ( | ||
ErrDoesNotImplement = errors.Errorf("doesn't implement required interface") | ||
// ErrDoesNotImplement indicates that the afero filesystem doesn't | ||
// implement the required interface. | ||
ErrDoesNotImplement = fmt.Errorf("doesn't implement required interface") | ||
// ErrInfoIsNil indicates that a nil os.FileInfo object was provided | ||
ErrInfoIsNil = fmt.Errorf("provided os.Info object was nil") | ||
// ErrInvalidAlgorithm specifies that an unknown algorithm was given for Walk | ||
ErrInvalidAlgorithm = fmt.Errorf("invalid algorithm specified") | ||
// ErrStopWalk indicates to the Walk function that the walk should be aborted | ||
ErrStopWalk = fmt.Errorf("stop filesystem walk") | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
package pathlib | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"os" | ||
) | ||
|
||
// WalkOpts is the struct that defines how a walk should be performed | ||
type WalkOpts struct { | ||
// Depth defines how far down a directory we should recurse. A value of -1 means | ||
// 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 | ||
// traverse the directory. | ||
WalkAlgorithm string | ||
|
||
// 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 | ||
// walking a directory. | ||
func DefaultWalkOpts() *WalkOpts { | ||
return &WalkOpts{ | ||
Depth: -1, | ||
WalkAlgorithm: AlgorithmBasic(), | ||
FollowSymlinks: false, | ||
FIFOQueueSize: 100, | ||
} | ||
} | ||
|
||
func AlgorithmDepthFirst() string { | ||
return "depth-first" | ||
} | ||
|
||
func AlgorithmBasic() string { | ||
return "basic" | ||
} | ||
|
||
// Walk is an object that handles walking through a directory tree | ||
type Walk struct { | ||
Opts *WalkOpts | ||
root *Path | ||
} | ||
|
||
// NewWalk returns a new Walk struct with default values applied | ||
func NewWalk(root *Path) (*Walk, error) { | ||
return NewWalkWithOpts(root, DefaultWalkOpts()) | ||
} | ||
|
||
// NewWalkWithOpts returns a Walk object with the given WalkOpts applied | ||
func NewWalkWithOpts(root *Path, opts *WalkOpts) (*Walk, error) { | ||
if root == nil { | ||
return nil, fmt.Errorf("root path can't be nil") | ||
} | ||
if opts == nil { | ||
return nil, fmt.Errorf("opts can't be nil") | ||
} | ||
return &Walk{ | ||
Opts: opts, | ||
root: root, | ||
}, nil | ||
} | ||
|
||
func (w *Walk) maxDepthReached(currentDepth int) bool { | ||
if w.Opts.Depth >= 0 && currentDepth > w.Opts.Depth { | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
type dfsObjectInfo struct { | ||
path *Path | ||
info os.FileInfo | ||
err error | ||
} | ||
|
||
func (w *Walk) walkDFS(walkFn WalkFunc, root *Path, currentDepth int) error { | ||
if w.maxDepthReached(currentDepth) { | ||
return nil | ||
} | ||
|
||
var nonDirectories []*dfsObjectInfo | ||
|
||
if err := w.iterateImmediateChildren(root, func(child *Path, info os.FileInfo, encounteredErr error) error { | ||
// Since we are doing depth-first, we have to first recurse through all the directories, | ||
// and save all non-directory objects so we can defer handling at a later time. | ||
if IsDir(info) { | ||
if err := w.walkDFS(walkFn, child, currentDepth+1); err != nil { | ||
return err | ||
} | ||
if err := walkFn(child, info, encounteredErr); err != nil { | ||
return err | ||
} | ||
} else { | ||
nonDirectories = append(nonDirectories, &dfsObjectInfo{ | ||
path: child, | ||
info: info, | ||
err: encounteredErr, | ||
}) | ||
} | ||
return nil | ||
}); err != nil { | ||
return err | ||
} | ||
|
||
// Iterate over all non-directory objects | ||
for _, nonDir := range nonDirectories { | ||
if err := walkFn(nonDir.path, nonDir.info, nonDir.err); err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// iterateImmediateChildren is a function that handles discovering root's immediate children, | ||
// and will run the algorithm function for every child. The algorithm function is essentially | ||
// what differentiates how each walk behaves, and determines what actions to take given a | ||
// certain child. | ||
func (w *Walk) iterateImmediateChildren(root *Path, algorithmFunction WalkFunc) error { | ||
children, err := root.ReadDir() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var info os.FileInfo | ||
for _, child := range children { | ||
if child.Path() == root.Path() { | ||
continue | ||
} | ||
if w.Opts.FollowSymlinks { | ||
info, err = child.Stat() | ||
isSymlink, err := IsSymlink(info) | ||
if err != nil { | ||
return err | ||
} | ||
if isSymlink { | ||
child, err = child.ResolveAll() | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
} else { | ||
info, _, err = child.Lstat() | ||
} | ||
|
||
if info == nil { | ||
if err != nil { | ||
return err | ||
} | ||
return ErrInfoIsNil | ||
} | ||
|
||
if algoErr := algorithmFunction(child, info, err); algoErr != nil { | ||
return algoErr | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (w *Walk) walkBasic(walkFn WalkFunc, root *Path, currentDepth int) error { | ||
if w.maxDepthReached(currentDepth) { | ||
return nil | ||
} | ||
|
||
err := w.iterateImmediateChildren(root, func(child *Path, info os.FileInfo, encounteredErr error) error { | ||
if IsDir(info) { | ||
if err := w.walkBasic(walkFn, child, currentDepth+1); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
if err := walkFn(child, info, encounteredErr); err != nil { | ||
return err | ||
} | ||
return nil | ||
}) | ||
|
||
return err | ||
} | ||
|
||
// WalkFunc is the function provided to the Walk function for each directory. | ||
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(): | ||
if err := w.walkBasic(walkFn, w.root, 0); err != nil { | ||
if errors.Is(err, ErrStopWalk) { | ||
return nil | ||
} | ||
return err | ||
} | ||
return nil | ||
case AlgorithmDepthFirst(): | ||
if err := w.walkDFS(walkFn, w.root, 0); err != nil { | ||
if errors.Is(err, ErrStopWalk) { | ||
return nil | ||
} | ||
return err | ||
} | ||
return nil | ||
default: | ||
return ErrInvalidAlgorithm | ||
} | ||
} |
Oops, something went wrong.