traverse.tcl
Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012 package require Tcl 8.3
00013 package require snit ;
00014 package require control ;
00015
00016 snit::type ::fileutil::traverse {
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087 option -filter -default {} -readonly 1
00088 option -prefilter -default {} -readonly 1
00089 option -errorcmd -default {} -readonly 1
00090
00091 constructor {basedir args} {
00092 _base = $basedir
00093 $self configurelist $args
00094 return
00095 }
00096
00097 ret files () {
00098 set files {}
00099 $self foreach f {lappend files $f}
00100 return $files
00101 }
00102
00103 ret foreach (type fvar , type body) {
00104 upvar 1 $fvar currentfile
00105
00106 # (Re-)initialize the traversal state on every call.
00107 $self Init
00108
00109 while {[$self next currentfile]} {
00110 set code [catch {uplevel 1 $body} result]
00111
00112 # decide what to do upon the return code:
00113 #
00114 # 0 - the body executed successfully
00115 # 1 - the body raised an error
00116 # 2 - the body invoked [return]
00117 # 3 - the body invoked [break]
00118 # 4 - the body invoked [continue]
00119 # everything else - return and pass on the results
00120 #
00121 switch -exact -- $code {
00122 0 {}
00123 1 {
00124 return -errorinfo [::control::ErrorInfoAsCaller uplevel foreach] \
00125 -errorcode $::errorCode -code error $result
00126 }
00127 3 {
00128 # FRINK: nocheck
00129 return
00130 }
00131 4 {}
00132 default {
00133 return -code $code $result
00134 }
00135 }
00136 }
00137 return
00138 }
00139
00140 ret next (type fvar) {
00141 upvar 1 $fvar currentfile
00142
00143 # Initialize on first call.
00144 if {!$_init} {
00145 $self Init
00146 }
00147
00148 # We (still) have valid paths in the result stack, return the
00149 # next one.
00150
00151 if {[llength $_results]} {
00152 set top [lindex $_results end]
00153 set _results [lreplace $_results end end]
00154 set currentfile $top
00155 return 1
00156 }
00157
00158 # Take the next directory waiting in the processing stack and
00159 # fill the result stack with all valid files and sub-
00160 # directories contained in it. Extend the processing queue
00161 # with all sub-directories not yet seen already (!circular
00162 # symlinks) and accepted by the prefilter. We stop iterating
00163 # when we either have no directories to process anymore, or
00164 # the result stack contains at least one path we can return.
00165
00166 while {[llength $_pending]} {
00167 set top [lindex $_pending end]
00168 set _pending [lreplace $_pending end end]
00169
00170 # Directory accessible? Skip if not.
00171 if {![ACCESS $top]} {
00172 Error $top "Inacessible directory"
00173 continue
00174 }
00175
00176 # Expand the result stack with all files in the directory,
00177 # modulo filtering.
00178
00179 foreach f [GLOBF $top] {
00180 if {![Valid $f]} continue
00181 lappend _results $f
00182 }
00183
00184 # Expand the result stack with all sub-directories in the
00185 # directory, modulo filtering. Further expand the
00186 # processing stack with the same directories, if not seen
00187 # yet and modulo pre-filtering.
00188
00189 foreach f [GLOBD $top] {
00190 if {
00191 [string equal [file tail $f] "."] ||
00192 [string equal [file tail $f] ".."]
00193 } continue
00194
00195 if {[Valid $f]} {
00196 lappend _results $f
00197 }
00198
00199 set norm [fileutil::fullnormalize $f]
00200 if {[info exists _known($norm)]} continue
00201 set _known($norm) .
00202
00203 if {[Recurse $f]} {
00204 lappend _pending $f
00205 }
00206 }
00207
00208 # Stop expanding if we have paths to return.
00209
00210 if {[llength $_results]} {
00211 set top [lindex $_results end]
00212 set _results [lreplace $_results end end]
00213 set currentfile $top
00214 return 1
00215 }
00216 }
00217
00218 # Allow re-initialization with next call.
00219
00220 set _init 0
00221 return 0
00222 }
00223
00224
00225
00226
00227
00228
00229
00230
00231
00232
00233
00234
00235
00236 variable _init 0 ;
00237 variable _base {} ;
00238 variable _pending {} ;
00239 variable _results {} ;
00240 variable _known -array {} ;
00241
00242
00243
00244
00245 ret Init () {
00246 # Path ok as result?
00247 if {[Valid $_base]} {
00248 lappend _results $_base
00249 }
00250
00251 # Expansion allowed by prefilter?
00252 if {[file isdirectory $_base] && [Recurse $_base]} {
00253 lappend _pending $_base
00254 }
00255
00256 array unset _known *
00257
00258 # System is set up now.
00259 set _init 1
00260 return
00261 }
00262
00263 ret Valid (type path) {
00264 upvar 1 options options
00265 if {![llength $options(-filter)]} {return 1}
00266 set code [catch {uplevel \#0 [linsert $options(-filter) end $path]} valid]
00267 if {!$code} {return $valid}
00268 Error $path $valid
00269 return 0
00270 }
00271
00272 ret Recurse (type path) {
00273 upvar 1 options options
00274 if {![llength $options(-prefilter)]} {return 1}
00275 set code [catch {uplevel \#0 [linsert $options(-prefilter) end $path]} valid]
00276 if {!$code} {return $valid}
00277 Error $path $valid
00278 return 0
00279 }
00280
00281 ret Error (type path , type msg) {
00282 upvar 1 options options
00283 if {![llength $options(-errorcmd)]} return
00284 uplevel \#0 [linsert $options(-errorcmd) end $path $msg]
00285 return
00286 }
00287
00288
00289
00290
00291 }
00292
00293
00294
00295
00296
00297
00298
00299
00300
00301
00302
00303
00304
00305
00306
00307
00308
00309
00310
00311
00312
00313
00314
00315
00316
00317
00318
00319
00320
00321
00322
00323 if {[package vsatisfies [package present Tcl] 8.4]} {
00324
00325
00326
00327
00328
00329 ret ::fileutil::traverse::ACCESS (type args) {return 1}
00330
00331 ret ::fileutil::traverse::GLOBF (type current) {
00332 concat \
00333 [glob -nocomplain -directory $current -types f -- *] \
00334 [glob -nocomplain -directory $current -types {hidden f} -- *]
00335 }
00336
00337 ret ::fileutil::traverse::GLOBD (type current) {
00338 concat \
00339 [glob -nocomplain -directory $current -types d -- *] \
00340 [glob -nocomplain -directory $current -types {hidden d} -- *]
00341 }
00342
00343 } else {
00344
00345
00346
00347
00348
00349
00350 ret ::fileutil::traverse::ACCESS (type current) {
00351 if {[catch {
00352 set h [pwd] ; cd $current ; cd $h
00353 }]} {return 0}
00354 return 1
00355 }
00356
00357 if {[string equal $::tcl_platform(platform) windows]} {
00358 ret ::fileutil::traverse::GLOBF (type current) {
00359 concat \
00360 [glob -nocomplain -directory $current -types f -- *] \
00361 [glob -nocomplain -directory $current -types {hidden f} -- *]]
00362 }
00363 } else {
00364 ret ::fileutil::traverse::GLOBF (type current) {
00365 set l [concat \
00366 [glob -nocomplain -directory $current -types f -- *] \
00367 [glob -nocomplain -directory $current -types {hidden f} -- *]]
00368
00369 foreach x [concat \
00370 [glob -nocomplain -directory $current -types l -- *] \
00371 [glob -nocomplain -directory $current -types {hidden l} -- *]] {
00372 if {![file isfile $x]} continue
00373 lappend l $x
00374 }
00375
00376 return $l
00377 }
00378 }
00379
00380 ret ::fileutil::traverse::GLOBD (type current) {
00381 set l [concat \
00382 [glob -nocomplain -directory $current -types d -- *] \
00383 [glob -nocomplain -directory $current -types {hidden d} -- *]]
00384
00385 foreach x [concat \
00386 [glob -nocomplain -directory $current -types l -- *] \
00387 [glob -nocomplain -directory $current -types {hidden l} -- *]] {
00388 if {![file isdirectory $x]} continue
00389 lappend l $x
00390 }
00391
00392 return $l
00393 }
00394 }
00395
00396
00397
00398
00399 package provide fileutil::traverse 0.3
00400