source: tol/tolsh/tolsh.vfs/lib/tcllib1.10/fileutil/traverse.tcl @ 8597867d

1941-docker-image-to-testdevel
Last change on this file since 8597867d was 8597867d, checked in by Jorge Pérez <josp.jorge@…>, 16 years ago

closes #976, se incluye tcllib1.10, base64 debe eliminarse del raiz de lib ya que esta incluido en tcllib

git-svn-id: ​https://www.tol-project.org/svn/tolp/trunk@2556 be4846ae-f051-483a-831f-b64e7d08527f

  • Property mode set to 100644
File size: 11.8 KB
RevLine 
[8597867d]1# traverse.tcl --
2#
3#       Directory traversal.
4#
5# Copyright (c) 2006-2007 by Andreas Kupries <andreas_kupries@users.sourceforge.net>
6#
7# See the file "license.terms" for information on usage and redistribution
8# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
9#
10# RCS: @(#) $Id: traverse.tcl,v 1.4 2007/08/08 19:42:43 andreas_kupries Exp $
11
12package require Tcl 8.3
13package require snit    ; # OO core
14package require control ; # Helpers for control structures
15
16snit::type ::fileutil::traverse {
17
18    # Incremental directory traversal.
19
20    # API
21    # create  %AUTO% basedirectory options... -> object
22    # next    filevar                         -> boolean
23    # foreach filevar script
24    # files                                   -> list (path ...)
25
26    # Options
27    # -prefilter command-prefix
28    # -filter    command-prefix
29    # -errorcmd  command-prefix
30
31    # Use cases
32    #
33    # (a) Basic incremental
34    # - Create and configure a traversal object.
35    # - Execute 'next' to retrieve one path at a time,
36    #   until the command returns False, signaling that
37    #   the iterator has exhausted the supply of paths.
38    #   (The path is stored in the named variable).
39    #
40    # The execution of 'next' can be done in a loop, or via event
41    # processing.
42
43    # (b) Basic loop
44    # - Create and configure a traversal object.
45    # - Run a script for each path, using 'foreach'.
46    #   This is a convenient standard wrapper around 'next'.
47    #
48    # The loop properly handles all possible Tcl result codes.
49
50    # (c) Non-incremental, non-looping.
51    # - Create and configure a traversal object.
52    # - Retrieve a list of all paths via 'files'.
53
54    # The -prefilter callback is executed for directories. Its result
55    # determines if the traverser recurses into the directory or not.
56    # The default is to always recurse into all directories. The call-
57    # back is invoked with a single argument, the path of the
58    # directory.
59    #
60    # The -filter callback is executed for all paths. Its result
61    # determines if the current path is a valid result, and returned
62    # by 'next'. The default is to accept all paths as valid. The
63    # callback is invoked with a single argument, the path to check.
64
65    # The -errorcmd callback is executed for all paths the traverser
66    # has trouble with. Like being unable to cd into them, get their
67    # status, etc. The default is to ignore any such problems. The
68    # callback is invoked with a two arguments, the path for which the
69    # error occured, and the error message. Errors thrown by the
70    # filter callbacks are handled through this callback too. Errors
71    # thrown by the error callback itself are not caught and ignored,
72    # but allowed to pass to the caller, usually of 'next'.
73
74    # Note: Low-level functionality, version and platform dependent is
75    # implemented in procedures, and conditioally defined for optimal
76    # use of features, etc. ...
77
78    # Note: Traversal is done in depth-first pre-order.
79
80    # Note: The options are handled only during
81    # construction. Afterward they are read-only and attempts to
82    # modify them will cause the system to throw errors.
83
84    # ### ### ### ######### ######### #########
85    ## Implementation
86
87    option -filter    -default {} -readonly 1
88    option -prefilter -default {} -readonly 1
89    option -errorcmd  -default {} -readonly 1
90
91    constructor {basedir args} {
92        set _base $basedir
93        $self configurelist $args
94        return
95    }
96
97    method files {} {
98        set files {}
99        $self foreach f {lappend files $f}
100        return $files
101    }
102
103    method foreach {fvar body} {
104        upvar 1 $fvar currentfile
105
106        # (Re-)initialize the traversal state on every call.
107        $self Init
108
109        while {[$self next currentfile]} {
110            set code [catch {uplevel 1 $body} result]
111
112            # decide what to do upon the return code:
113            #
114            #               0 - the body executed successfully
115            #               1 - the body raised an error
116            #               2 - the body invoked [return]
117            #               3 - the body invoked [break]
118            #               4 - the body invoked [continue]
119            # everything else - return and pass on the results
120            #
121            switch -exact -- $code {
122                0 {}
123                1 {
124                    return -errorinfo [::control::ErrorInfoAsCaller uplevel foreach]  \
125                            -errorcode $::errorCode -code error $result
126                }
127                3 {
128                    # FRINK: nocheck
129                    return
130                }
131                4 {}
132                default {
133                    return -code $code $result
134                }
135            }
136        }
137        return
138    }
139
140    method next {fvar} {
141        upvar 1 $fvar currentfile
142
143        # Initialize on first call.
144        if {!$_init} {
145            $self Init
146        }
147
148        # We (still) have valid paths in the result stack, return the
149        # next one.
150
151        if {[llength $_results]} {
152            set top      [lindex   $_results end]
153            set _results [lreplace $_results end end]
154            set currentfile $top
155            return 1
156        }
157
158        # Take the next directory waiting in the processing stack and
159        # fill the result stack with all valid files and sub-
160        # directories contained in it. Extend the processing queue
161        # with all sub-directories not yet seen already (!circular
162        # symlinks) and accepted by the prefilter. We stop iterating
163        # when we either have no directories to process anymore, or
164        # the result stack contains at least one path we can return.
165
166        while {[llength $_pending]} {
167            set top      [lindex   $_pending end]
168            set _pending [lreplace $_pending end end]
169
170            # Directory accessible? Skip if not.
171            if {![ACCESS $top]} {
172                Error $top "Inacessible directory"
173                continue
174            }
175
176            # Expand the result stack with all files in the directory,
177            # modulo filtering.
178
179            foreach f [GLOBF $top] {
180                if {![Valid $f]} continue
181                lappend _results $f
182            }
183
184            # Expand the result stack with all sub-directories in the
185            # directory, modulo filtering. Further expand the
186            # processing stack with the same directories, if not seen
187            # yet and modulo pre-filtering.
188
189            foreach f [GLOBD $top] {
190                if {
191                    [string equal [file tail $f]  "."] ||
192                    [string equal [file tail $f] ".."]
193                } continue
194
195                if {[Valid $f]} {
196                    lappend _results $f
197                }
198
199                set norm [fileutil::fullnormalize $f]
200                if {[info exists _known($norm)]} continue
201                set _known($norm) .
202
203                if {[Recurse $f]} {
204                    lappend _pending $f
205                }
206            }
207
208            # Stop expanding if we have paths to return.
209
210            if {[llength $_results]} {
211                set top    [lindex   $_results end]
212                set _results [lreplace $_results end end]
213                set currentfile $top
214                return 1
215            }
216        }
217
218        # Allow re-initialization with next call.
219
220        set _init 0
221        return 0
222    }
223
224    # ### ### ### ######### ######### #########
225    ## Traversal state
226
227    # * Initialization flag. Checked in 'next', reset by next when no
228    #   more files are available. Set in 'Init'.
229    # * Base directory (or file) to start the traversal from.
230    # * Stack of prefiltered unknown directories waiting for
231    #   processing, i.e. expansion (TOP at end).
232    # * Stack of valid paths waiting to be returned as results.
233    # * Set of directories already visited (normalized paths), for
234    #   detection of circular symbolic links.
235
236    variable _init         0  ; # Initialization flag.
237    variable _base         {} ; # Base directory.
238    variable _pending      {} ; # Processing stack.
239    variable _results      {} ; # Result stack.
240    variable _known -array {} ; # Seen paths.
241
242    # ### ### ### ######### ######### #########
243    ## Internal helpers.
244
245    method Init {} {
246        # Path ok as result?
247        if {[Valid $_base]} {
248            lappend _results $_base
249        }
250
251        # Expansion allowed by prefilter?
252        if {[file isdirectory $_base] && [Recurse $_base]} {
253            lappend _pending $_base
254        }
255
256        array unset _known *
257
258        # System is set up now.
259        set _init 1
260        return
261    }
262
263    proc Valid {path} {
264        upvar 1 options options
265        if {![llength $options(-filter)]} {return 1}
266        set code [catch {uplevel \#0 [linsert $options(-filter) end $path]} valid]
267        if {!$code} {return $valid}
268        Error $path $valid
269        return 0
270    }
271
272    proc Recurse {path} {
273        upvar 1 options options
274        if {![llength $options(-prefilter)]} {return 1}
275        set code [catch {uplevel \#0 [linsert $options(-prefilter) end $path]} valid]
276        if {!$code} {return $valid}
277        Error $path $valid
278        return 0
279    }
280
281    proc Error {path msg} {
282        upvar 1 options options
283        if {![llength $options(-errorcmd)]} return
284        uplevel \#0 [linsert $options(-errorcmd) end $path $msg]
285        return
286    }
287
288    ##
289    # ### ### ### ######### ######### #########
290}
291
292# ### ### ### ######### ######### #########
293##
294
295# The next three helper commands for the traverser depend strongly on
296# the version of Tcl, and partially on the platform.
297
298# 1. In Tcl 8.3 using -types f will return only true files, but not
299#    links to files. This changed in 8.4+ where links to files are
300#    returned as well. So for 8.3 we have to handle the links
301#    separately (-types l) and also filter on our own.
302#    Note that Windows file links are hard links which are reported by
303#    -types f, but not -types l, so we can optimize that for the two
304#    platforms.
305#
306# 2. In Tcl 8.3 we also have a crashing bug in glob (SIGABRT, "stat on
307#    a known file") when trying to perform 'glob -types {hidden f}' on
308#    a directory without e'x'ecute permissions. We code around by
309#    testing if we can cd into the directory (stat might return enough
310#    information too (mode), but possibly also not portable).
311#
312#    For Tcl 8.2 and 8.4+ glob simply delivers an empty result
313#    (-nocomplain), without crashing. For them this command is defined
314#    so that the bytecode compiler removes it from the bytecode.
315#
316#    This bug made the ACCESS helper necessary.
317#    We code around the problem by testing if we can cd into the
318#    directory (stat might return enough information too (mode), but
319#    possibly also not portable).
320
321if {[package vsatisfies [package present Tcl] 8.4]} {
322    # Tcl 8.4+.
323    # (Ad 1) We have -directory, and -types,
324    # (Ad 2) Links are returned for -types f/d if they refer to files/dirs.
325    # (Ad 3) No bug to code around
326
327    proc ::fileutil::traverse::ACCESS {args} {return 1}
328
329    proc ::fileutil::traverse::GLOBF {current} {
330        concat \
331            [glob -nocomplain -directory $current -types f          -- *] \
332            [glob -nocomplain -directory $current -types {hidden f} -- *]
333    }
334
335    proc ::fileutil::traverse::GLOBD {current} {
336        concat \
337            [glob -nocomplain -directory $current -types d          -- *] \
338            [glob -nocomplain -directory $current -types {hidden d} -- *]
339    }
340
341} else {
342    # 8.3.
343    # (Ad 1) We have -directory, and -types,
344    # (Ad 2) Links are NOT returned for -types f/d, collect separately.
345    #        No symbolic file links on Windows.
346    # (Ad 3) Bug to code around.
347
348    proc ::fileutil::traverse::ACCESS {current} {
349        if {[catch {
350            set h [pwd] ; cd $current ; cd $h
351        }]} {return 0}
352        return 1
353    }
354
355    if {[string equal $::tcl_platform(platform) windows]} {
356        proc ::fileutil::traverse::GLOBF {current} {
357            concat \
358                [glob -nocomplain -directory $current -types f          -- *] \
359                [glob -nocomplain -directory $current -types {hidden f} -- *]]
360        }
361    } else {
362        proc ::fileutil::traverse::GLOBF {current} {
363            set l [concat \
364                       [glob -nocomplain -directory $current -types f          -- *] \
365                       [glob -nocomplain -directory $current -types {hidden f} -- *]]
366
367            foreach x [concat \
368                           [glob -nocomplain -directory $current -types l          -- *] \
369                           [glob -nocomplain -directory $current -types {hidden l} -- *]] {
370                if {![file isfile $x]} continue
371                lappend l $x
372            }
373
374            return $l
375        }
376    }
377
378    proc ::fileutil::traverse::GLOBD {current} {
379        set l [concat \
380                   [glob -nocomplain -directory $current -types d          -- *] \
381                   [glob -nocomplain -directory $current -types {hidden d} -- *]]
382
383        foreach x [concat \
384                       [glob -nocomplain -directory $current -types l          -- *] \
385                       [glob -nocomplain -directory $current -types {hidden l} -- *]] {
386            if {![file isdirectory $x]} continue
387            lappend l $x
388        }
389
390        return $l
391    }
392}
393
394# ### ### ### ######### ######### #########
395## Ready
396
397package provide fileutil::traverse 0.3
Note: See TracBrowser for help on using the repository browser.