44 "errors"
55 "fmt"
66 "os"
7+ "slices"
78)
89
910// WalkOpts is the struct that defines how a walk should be performed
@@ -18,8 +19,7 @@ type WalkOpts struct {
1819
1920 // FollowSymlinks defines whether symlinks should be dereferenced or not. If True,
2021 // the symlink itself will never be returned to WalkFunc, but rather whatever it
21- // points to. Warning!!! You are exposing yourself to substantial risk by setting this
22- // to True. Here be dragons!
22+ // points to.
2323 FollowSymlinks bool
2424
2525 // MinimumFileSize specifies the minimum size of a file for visitation.
@@ -40,14 +40,9 @@ type WalkOpts struct {
4040 // VisitSymlinks specifies that we should visit symlinks during the walk.
4141 VisitSymlinks bool
4242
43- // VisitFirst specifies that, in the algorithms where it is appropriate,
44- // a node's contents should be visited first, before recursing down. If false,
45- // a node's subdirectories will be recursed first before visiting any of its
46- // other children.
47- //
48- // This option is not appropriate in the Basic algorithm, where ordering is
49- // explicitly forbidden.
50- // VisitFirst bool
43+ // SortChildren causes all children of a path to be lexigraphically sorted before
44+ // being sent to the WalkFunc.
45+ SortChildren bool
5146}
5247
5348// DefaultWalkOpts returns the default WalkOpts struct used when
@@ -62,6 +57,7 @@ func DefaultWalkOpts() *WalkOpts {
6257 VisitFiles : true ,
6358 VisitDirs : true ,
6459 VisitSymlinks : true ,
60+ SortChildren : false ,
6561 }
6662}
6763
@@ -87,13 +83,17 @@ type Algorithm int
8783const (
8884 // AlgorithmBasic is a walk algorithm. It iterates over filesystem objects in the
8985 // order in which they are returned by the operating system. It guarantees no
90- // ordering of any kind. This is the most efficient algorithm and should be used
91- // in all cases where ordering does not matter.
86+ // ordering of any kind. It will recurse into subdirectories as soon as it encounters them,
87+ // and will continue iterating the remaining children after the recursion is complete.
88+ // It behaves as a quasi-DFS algorithm.
9289 AlgorithmBasic Algorithm = iota
93- // AlgorithmDepthFirst is a walk algorithm. It iterates over a filesystem tree
94- // by first recursing as far down as it can in one path. Each directory is visited
95- // only after all of its children directories have been recursed .
90+ // AlgorithmDepthFirst is a walk algorithm. More specifically, it is a post-order
91+ // depth first search whereby subdirectories are recursed into before
92+ // visiting the children of the current directory .
9693 AlgorithmDepthFirst
94+ // AlgorithmPreOrderDepthFirst is a walk algorithm. It visits all of a node's children
95+ // before recursing into the subdirectories.
96+ AlgorithmPreOrderDepthFirst
9797)
9898
9999// Walk is an object that handles walking through a directory tree
@@ -152,6 +152,12 @@ func WalkVisitSymlinks(value bool) WalkOptsFunc {
152152 }
153153}
154154
155+ func WalkSortChildren (value bool ) WalkOptsFunc {
156+ return func (config * WalkOpts ) {
157+ config .SortChildren = value
158+ }
159+ }
160+
155161// NewWalk returns a new Walk struct with default values applied
156162func NewWalk (root * Path , opts ... WalkOptsFunc ) (* Walk , error ) {
157163 config := DefaultWalkOpts ()
@@ -242,6 +248,17 @@ func (w *Walk) iterateImmediateChildren(root *Path, algorithmFunction WalkFunc)
242248 return err
243249 }
244250
251+ if w .Opts .SortChildren {
252+ slices .SortFunc [[]* Path , * Path ](children , func (a * Path , b * Path ) int {
253+ if a .String () < b .String () {
254+ return - 1
255+ }
256+ if a .String () == b .String () {
257+ return 0
258+ }
259+ return 1
260+ })
261+ }
245262 var info os.FileInfo
246263 for _ , child := range children {
247264 if child .String () == root .String () {
@@ -321,30 +338,59 @@ func (w *Walk) walkBasic(walkFn WalkFunc, root *Path, currentDepth int) error {
321338 return err
322339}
323340
324- // WalkFunc is the function provided to the Walk function for each directory.
325- type WalkFunc func (path * Path , info os.FileInfo , err error ) error
341+ func (w * Walk ) walkPreOrderDFS (walkFn WalkFunc , root * Path , currentDepth int ) error {
342+ if w .maxDepthReached (currentDepth ) {
343+ return nil
344+ }
345+ dirs := []* Path {}
346+ err := w .iterateImmediateChildren (root , func (child * Path , info os.FileInfo , encounteredErr error ) error {
347+ if IsDir (info .Mode ()) {
348+ dirs = append (dirs , child )
349+ }
326350
327- // Walk walks the directory using the algorithm specified in the configuration.
328- func (w * Walk ) Walk (walkFn WalkFunc ) error {
351+ passesQuery , err := w .passesQuerySpecification (info )
352+ if err != nil {
353+ return err
354+ }
329355
330- switch w .Opts .Algorithm {
331- case AlgorithmBasic :
332- if err := w .walkBasic (walkFn , w .root , 0 ); err != nil {
333- if errors .Is (err , ErrStopWalk ) {
334- return nil
356+ if passesQuery {
357+ if err := walkFn (child , info , encounteredErr ); err != nil {
358+ return err
335359 }
336- return err
337360 }
338361 return nil
339- case AlgorithmDepthFirst :
340- if err := w .walkDFS (walkFn , w .root , 0 ); err != nil {
341- if errors .Is (err , ErrStopWalk ) {
342- return nil
343- }
362+ })
363+ if err != nil {
364+ return err
365+ }
366+ for _ , dir := range dirs {
367+ if err := w .walkPreOrderDFS (walkFn , dir , currentDepth + 1 ); err != nil {
344368 return err
345369 }
346- return nil
347- default :
370+ }
371+ return nil
372+ }
373+
374+ // WalkFunc is the function provided to the Walk function for each directory.
375+ type WalkFunc func (path * Path , info os.FileInfo , err error ) error
376+
377+ // Walk walks the directory using the algorithm specified in the configuration.
378+ func (w * Walk ) Walk (walkFn WalkFunc ) error {
379+ funcs := map [Algorithm ]func (walkFn WalkFunc , root * Path , currentDepth int ) error {
380+ AlgorithmBasic : w .walkBasic ,
381+ AlgorithmDepthFirst : w .walkDFS ,
382+ AlgorithmPreOrderDepthFirst : w .walkPreOrderDFS ,
383+ }
384+ algoFunc , ok := funcs [w .Opts .Algorithm ]
385+ if ! ok {
348386 return ErrInvalidAlgorithm
349387 }
388+ if err := algoFunc (walkFn , w .root , 0 ); err != nil {
389+ if errors .Is (err , ErrStopWalk ) {
390+ return nil
391+ }
392+ return err
393+ }
394+ return nil
395+
350396}
0 commit comments