aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/chigopher/pathlib/walk.go
diff options
context:
space:
mode:
authorTaras Madan <tarasmadan@google.com>2024-05-08 14:17:02 +0200
committerTaras Madan <tarasmadan@google.com>2024-05-08 12:48:31 +0000
commit0ac372459159c5632ae7ce9778e9be771708a9b3 (patch)
tree9c6a979cada5c22b46dd3c5ad4b0d706f2252272 /vendor/github.com/chigopher/pathlib/walk.go
parent69141a688b25b2d33daf570507dbb13a3c80e5d9 (diff)
vendor: go mod vendor
We don't want to download mockery every time.
Diffstat (limited to 'vendor/github.com/chigopher/pathlib/walk.go')
-rw-r--r--vendor/github.com/chigopher/pathlib/walk.go405
1 files changed, 405 insertions, 0 deletions
diff --git a/vendor/github.com/chigopher/pathlib/walk.go b/vendor/github.com/chigopher/pathlib/walk.go
new file mode 100644
index 000000000..c50a9c938
--- /dev/null
+++ b/vendor/github.com/chigopher/pathlib/walk.go
@@ -0,0 +1,405 @@
+package pathlib
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "slices"
+)
+
+// 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
+
+ // Algorithm specifies the algoritm that the Walk() function should use to
+ // traverse the directory.
+ 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.
+ FollowSymlinks bool
+
+ // MinimumFileSize specifies the minimum size of a file for visitation.
+ // If negative, there is no minimum size.
+ MinimumFileSize int64
+
+ // MaximumFileSize specifies the maximum size of a file for visitation.
+ // If negative, there is no maximum size.
+ MaximumFileSize int64
+
+ // VisitFiles specifies that we should visit regular files during
+ // the walk.
+ VisitFiles bool
+
+ // VisitDirs specifies that we should visit directories during the walk.
+ VisitDirs bool
+
+ // VisitSymlinks specifies that we should visit symlinks during the walk.
+ VisitSymlinks bool
+
+ // SortChildren causes all children of a path to be lexigraphically sorted before
+ // being sent to the WalkFunc.
+ SortChildren bool
+}
+
+// DefaultWalkOpts returns the default WalkOpts struct used when
+// walking a directory.
+func DefaultWalkOpts() *WalkOpts {
+ return &WalkOpts{
+ Depth: -1,
+ Algorithm: AlgorithmBasic,
+ FollowSymlinks: false,
+ MinimumFileSize: -1,
+ MaximumFileSize: -1,
+ VisitFiles: true,
+ VisitDirs: true,
+ VisitSymlinks: true,
+ SortChildren: false,
+ }
+}
+
+// MeetsMinimumSize returns whether size is at least the minimum specified.
+func (w *WalkOpts) MeetsMinimumSize(size int64) bool {
+ if w.MinimumFileSize < 0 {
+ return true
+ }
+ return size >= w.MinimumFileSize
+}
+
+// MeetsMaximumSize returns whether size is less than or equal to the maximum specified.
+func (w *WalkOpts) MeetsMaximumSize(size int64) bool {
+ if w.MaximumFileSize < 0 {
+ return true
+ }
+ return size <= w.MaximumFileSize
+}
+
+// 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. It will recurse into subdirectories as soon as it encounters them,
+ // and will continue iterating the remaining children after the recursion is complete.
+ // It behaves as a quasi-DFS algorithm.
+ AlgorithmBasic Algorithm = iota
+ // AlgorithmDepthFirst is a walk algorithm. More specifically, it is a post-order
+ // depth first search whereby subdirectories are recursed into before
+ // visiting the children of the current directory.
+ // DEPRECATED: Use AlgorithmPostOrderDepthFirst
+ AlgorithmDepthFirst
+ // AlgorithmPostOrderDepthFirst is a walk algorithm that recurses into all of its children
+ // before visiting any of a node's elements.
+ AlgorithmPostOrderDepthFirst
+ // AlgorithmPreOrderDepthFirst is a walk algorithm. It visits all of a node's elements
+ // before recursing into its children.
+ AlgorithmPreOrderDepthFirst
+)
+
+// Walk is an object that handles walking through a directory tree
+type Walk struct {
+ Opts *WalkOpts
+ root *Path
+}
+
+type WalkOptsFunc func(config *WalkOpts)
+
+func WalkDepth(depth int) WalkOptsFunc {
+ return func(config *WalkOpts) {
+ config.Depth = depth
+ }
+}
+
+func WalkAlgorithm(algo Algorithm) WalkOptsFunc {
+ return func(config *WalkOpts) {
+ config.Algorithm = algo
+ }
+}
+
+func WalkFollowSymlinks(follow bool) WalkOptsFunc {
+ return func(config *WalkOpts) {
+ config.FollowSymlinks = follow
+ }
+}
+
+func WalkMinimumFileSize(size int64) WalkOptsFunc {
+ return func(config *WalkOpts) {
+ config.MinimumFileSize = size
+ }
+}
+
+func WalkMaximumFileSize(size int64) WalkOptsFunc {
+ return func(config *WalkOpts) {
+ config.MaximumFileSize = size
+ }
+}
+
+func WalkVisitFiles(value bool) WalkOptsFunc {
+ return func(config *WalkOpts) {
+ config.VisitFiles = value
+ }
+}
+
+func WalkVisitDirs(value bool) WalkOptsFunc {
+ return func(config *WalkOpts) {
+ config.VisitDirs = value
+ }
+}
+
+func WalkVisitSymlinks(value bool) WalkOptsFunc {
+ return func(config *WalkOpts) {
+ config.VisitSymlinks = value
+ }
+}
+
+func WalkSortChildren(value bool) WalkOptsFunc {
+ return func(config *WalkOpts) {
+ config.SortChildren = value
+ }
+}
+
+// NewWalk returns a new Walk struct with default values applied
+func NewWalk(root *Path, opts ...WalkOptsFunc) (*Walk, error) {
+ config := DefaultWalkOpts()
+ for _, opt := range opts {
+ opt(config)
+ }
+ return NewWalkWithOpts(root, config)
+}
+
+// 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 children []*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.Mode()) {
+ if err := w.walkDFS(walkFn, child, currentDepth+1); err != nil && !errors.Is(err, ErrWalkSkipSubtree) {
+ return err
+ }
+ }
+
+ children = append(children, &dfsObjectInfo{
+ path: child,
+ info: info,
+ err: encounteredErr,
+ })
+
+ return nil
+ }); err != nil {
+ return err
+ }
+
+ // Iterate over all children after all subdirs have been recursed
+ for _, child := range children {
+ passesQuery, err := w.passesQuerySpecification(child.info)
+ if err != nil {
+ return err
+ }
+
+ if passesQuery {
+ if err := walkFn(child.path, child.info, child.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
+ }
+
+ if w.Opts.SortChildren {
+ slices.SortFunc[[]*Path, *Path](children, func(a *Path, b *Path) int {
+ if a.String() < b.String() {
+ return -1
+ }
+ if a.String() == b.String() {
+ return 0
+ }
+ return 1
+ })
+ }
+ var info os.FileInfo
+ for _, child := range children {
+ if child.String() == root.String() {
+ continue
+ }
+ if w.Opts.FollowSymlinks {
+ info, err = child.Stat()
+ 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
+}
+
+// passesQuerySpecification returns whether or not the object described by
+// the os.FileInfo passes all of the query specifications listed in
+// the walk options.
+func (w *Walk) passesQuerySpecification(info os.FileInfo) (bool, error) {
+ if IsFile(info.Mode()) {
+ if !w.Opts.VisitFiles {
+ return false, nil
+ }
+
+ if !w.Opts.MeetsMinimumSize(info.Size()) ||
+ !w.Opts.MeetsMaximumSize(info.Size()) {
+ return false, nil
+ }
+ } else if IsDir(info.Mode()) && !w.Opts.VisitDirs {
+ return false, nil
+ } else if IsSymlink(info.Mode()) && !w.Opts.VisitSymlinks {
+ return false, nil
+ }
+
+ return true, 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.Mode()) {
+ // In the case the error is ErrWalkSkipSubtree, we ignore it as we've already
+ // exited from the recursive call. Any other error should be bubbled up.
+ if err := w.walkBasic(walkFn, child, currentDepth+1); err != nil && !errors.Is(err, ErrWalkSkipSubtree) {
+ return err
+ }
+ }
+
+ passesQuery, err := w.passesQuerySpecification(info)
+ if err != nil {
+ return err
+ }
+
+ if passesQuery {
+ if err := walkFn(child, info, encounteredErr); err != nil {
+ return err
+ }
+ }
+
+ return nil
+ })
+
+ return err
+}
+
+func (w *Walk) walkPreOrderDFS(walkFn WalkFunc, root *Path, currentDepth int) error {
+ if w.maxDepthReached(currentDepth) {
+ return nil
+ }
+ dirs := []*Path{}
+ err := w.iterateImmediateChildren(root, func(child *Path, info os.FileInfo, encounteredErr error) error {
+ if IsDir(info.Mode()) {
+ dirs = append(dirs, child)
+ }
+
+ passesQuery, err := w.passesQuerySpecification(info)
+ if err != nil {
+ return err
+ }
+
+ if passesQuery {
+ if err := walkFn(child, info, encounteredErr); err != nil {
+ return err
+ }
+ }
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+ for _, dir := range dirs {
+ if err := w.walkPreOrderDFS(walkFn, dir, currentDepth+1); err != nil && !errors.Is(err, ErrWalkSkipSubtree) {
+ return err
+ }
+ }
+ return nil
+}
+
+// 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. Your WalkFunc
+// may return any of the ErrWalk* errors to control various behavior of the walker. See the documentation
+// of each error for more details.
+func (w *Walk) Walk(walkFn WalkFunc) error {
+ funcs := map[Algorithm]func(walkFn WalkFunc, root *Path, currentDepth int) error{
+ AlgorithmBasic: w.walkBasic,
+ AlgorithmDepthFirst: w.walkDFS,
+ AlgorithmPostOrderDepthFirst: w.walkDFS,
+ AlgorithmPreOrderDepthFirst: w.walkPreOrderDFS,
+ }
+ algoFunc, ok := funcs[w.Opts.Algorithm]
+ if !ok {
+ return ErrInvalidAlgorithm
+ }
+ if err := algoFunc(walkFn, w.root, 0); err != nil {
+ if errors.Is(err, errWalkControl) {
+ return nil
+ }
+ return err
+ }
+ return nil
+
+}