4
4
"errors"
5
5
"fmt"
6
6
"os"
7
+ "slices"
7
8
)
8
9
9
10
// WalkOpts is the struct that defines how a walk should be performed
@@ -18,8 +19,7 @@ type WalkOpts struct {
18
19
19
20
// FollowSymlinks defines whether symlinks should be dereferenced or not. If True,
20
21
// 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.
23
23
FollowSymlinks bool
24
24
25
25
// MinimumFileSize specifies the minimum size of a file for visitation.
@@ -40,14 +40,9 @@ type WalkOpts struct {
40
40
// VisitSymlinks specifies that we should visit symlinks during the walk.
41
41
VisitSymlinks bool
42
42
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
51
46
}
52
47
53
48
// DefaultWalkOpts returns the default WalkOpts struct used when
@@ -62,6 +57,7 @@ func DefaultWalkOpts() *WalkOpts {
62
57
VisitFiles : true ,
63
58
VisitDirs : true ,
64
59
VisitSymlinks : true ,
60
+ SortChildren : false ,
65
61
}
66
62
}
67
63
@@ -87,13 +83,17 @@ type Algorithm int
87
83
const (
88
84
// AlgorithmBasic is a walk algorithm. It iterates over filesystem objects in the
89
85
// 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.
92
89
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 .
96
93
AlgorithmDepthFirst
94
+ // AlgorithmPreOrderDepthFirst is a walk algorithm. It visits all of a node's children
95
+ // before recursing into the subdirectories.
96
+ AlgorithmPreOrderDepthFirst
97
97
)
98
98
99
99
// Walk is an object that handles walking through a directory tree
@@ -152,6 +152,12 @@ func WalkVisitSymlinks(value bool) WalkOptsFunc {
152
152
}
153
153
}
154
154
155
+ func WalkSortChildren (value bool ) WalkOptsFunc {
156
+ return func (config * WalkOpts ) {
157
+ config .SortChildren = value
158
+ }
159
+ }
160
+
155
161
// NewWalk returns a new Walk struct with default values applied
156
162
func NewWalk (root * Path , opts ... WalkOptsFunc ) (* Walk , error ) {
157
163
config := DefaultWalkOpts ()
@@ -242,6 +248,17 @@ func (w *Walk) iterateImmediateChildren(root *Path, algorithmFunction WalkFunc)
242
248
return err
243
249
}
244
250
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
+ }
245
262
var info os.FileInfo
246
263
for _ , child := range children {
247
264
if child .String () == root .String () {
@@ -321,30 +338,59 @@ func (w *Walk) walkBasic(walkFn WalkFunc, root *Path, currentDepth int) error {
321
338
return err
322
339
}
323
340
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
+ }
326
350
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
+ }
329
355
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
335
359
}
336
- return err
337
360
}
338
361
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 {
344
368
return err
345
369
}
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 {
348
386
return ErrInvalidAlgorithm
349
387
}
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
+
350
396
}
0 commit comments