pkgIndex.tcl
# 04.07.2014 package ifneeded Globx 0.1 [list source [file join $dir globx.tcl]] package ifneeded globx 0.1 [list source [file join $dir globx.tcl]]globx.tcl
#****h* Library/globx.tcl
#
# NAME
#
# globx.tcl - list files or folders of a folder tree
# v0.10, 07.07.2014
#
# AUTHOR
#
# M.Hoffmann, © 2004-2014
#
# PORTABILITY
#
# Tcl. Tested by the author on Windows only.
#
# USAGE
#
# -- package require globx
# To get all matching files in the given folder and subfolders:
# -- set files [globx startDir patterns]
# or
# proc callback fileName {...}
# -- set numFiles [globx startDir patterns callback]
# To get all subfolders of given folder (including the folder itself):
# -- set dirs [globx2 startDir]
# or
# proc callback dirName {...}
# -- set numDirs [globx2 startDir callback]
#
# NOTES
#
# -- works non-recursiv
# -- if using a callback, the order in which the cb is called is undetermined
# -- proc looks in *all* subfolders - only files are matched against pattern
# -- hidden files or folders are included
# -- use * instead of *.* to get the same results as with Windows commands (dir /s)
#
# TODO
#
# -- Namespace
# -- maybe using 8.6`s lmap, and coroutine to avoid blocking a gui while reading large dirs
#
# HISTORY
#
# v0.01 06.02.2004 - first documented, usable version
# v0.02 21.10.2004 - wiki fix (suppress . and ..)
# v0.03 09.12.2004 - new globx2 to only list folders
# v0.04 21.07.2006 - multiple patterns made possible
# v0.05 14.06.2009 - globx2 returns the given folder itself, too
# v0.06 26.08.2011 - catch{} to prevent some dubios runtime errors
# v0.10 07.07.2014 - optimizations - approx. 10% better performance.
# - bugfix: globx2 with callback did not return given folder.
# - removed update; a GUI program could use the callback method
# and, if required, call update from within the cb. Or switch
# back to the old behaviour and call 'globxSetUpdate update' once.
# - configurable update command (see above).
# - additional package name all lowercase.
# - callback could break the loop by returning a break.
#
# SOURCE
package require Tcl 8.5
package provide Globx 0.1
package provide globx 0.1
proc globxUpdate {} {
}
# Sets a command, which is then called repeatedly during processing via globxUpdate.
# By default, globxUpdate does nothing. To achieve same behaviour as with versions
# prior to 0.1, call 'globxSetUpdate update' once before anything else.
#
proc globxSetUpdate {script} {
proc globxUpdate {} $script
}
# Returns all files wich match given search-mask(s) in the given directory and below,
# hidden or not hidden. ALL subdirectories (*) are visited, hidden or not, without
# a recursive proc call.
#
proc globx {startDir {search *} {cb ""}} {
set dirStack [list [file normalize $startDir]]
set files [list]
set fc 0
while {[llength $dirStack]} {
set newStack [list]
foreach dir $dirStack {
set filesHere [list]
set dirsHere [list]
# temporary var's only because eventually using CallBack
catch {
lappend filesHere {*}[glob -noc -typ f -dir $dir -- {*}$search]
lappend filesHere {*}[glob -noc -typ {f hidden} -dir $dir -- {*}$search]
}
if {[string equal $cb ""]} {
lappend files {*}$filesHere; # cumulation
} else {
# call back early, not at the end
foreach f $filesHere {
incr fc
set rc [catch {uplevel [list $cb $f]} errMsg opts]
if {$rc == 3} {
return $fc; # TCL_BREAK means: stop here, but don't propagate
} elseif {$rc != 0} {
# propagate the error
return -options $opts $errMsg
}
}
}
catch {
lappend dirsHere {*}[glob -noc -typ d -dir $dir -- *]
lappend dirsHere {*}[glob -noc -typ {d hidden} -dir $dir -- *]
}
# note: lmap possible in 8.6
# start Wikipatch v0.02 ---
foreach newDir $dirsHere {
set theDir [file tail $newDir]
if {[string equal $theDir "." ] || [string equal $theDir ".."]} {
# Don't push this, otherwise entering an endless
# loop (on UNIX, at least)
} else {
lappend newStack $newDir
}
}
# end Wikipatch ---
}
set dirStack $newStack
globxUpdate
}
if {[string equal $cb ""]} {
return [lsort $files]
} else {
return $fc
}
}
# Returns the name of the given dir and all of it's subdirectories (direct or
# indirect). No wildcard selection possible - proc is for reading the
# whole folder names of the tree starting at the given point.
#
proc globx2 {startDir {cb ""}} {
set startDir [file normalize $startDir]
set dirStack [list $startDir]
set dirs [list]
set dc 0
# bugfix. Up until v0.06, startDir still not returned if using a callback
if {[file isdirectory $startDir]} {
incr dc
if {[string equal $cb ""]} {
set dirs $dirStack; # until v0.04, startDir wasn't returned at all...
} else {
uplevel [list $cb $startDir]
}
while {[llength $dirStack]} {
set newStack [list]
foreach dir $dirStack {
set dirsHere [list]
catch {
lappend dirsHere {*}[glob -noc -typ d -dir $dir -- *]
lappend dirsHere {*}[glob -noc -typ {d hidden} -dir $dir -- *]
}
foreach newDir $dirsHere {
set theDir [file tail $newDir]
if {[string equal $theDir "." ] || [string equal $theDir ".."]} {
# Don't push this, otherwise entering an endless
# loop (on UNIX, at least)
# v0.1: don't process . and .. at all
} else {
lappend newStack $newDir
if {[string equal $cb ""]} {
lappend dirs $newDir; # cumulation
} else {
# call back early, not at the end
incr dc
set rc [catch {uplevel [list $cb $newDir]} errMsg opts]
if {$rc == 3} {
return $dc; # TCL_BREAK means: stop here, but don't propagate
} elseif {$rc != 0} {
# propagate the error
return -options $opts $errMsg
}
}
}
}
}
set dirStack $newStack
globxUpdate
}
}
if {[string equal $cb ""]} {
return [lsort $dirs]
} else {
return $dc
}
}
#*******************************************************************************Version history
- 2009/06/14: globx2 minimally returns the name of the requested folder itself (before, only subfolders where returned, if some exist) -- Att: incompatibility!
- 2006/07/26: exposed glob's possibility of specifying more than one search mask to avoid double calls to globx. Backward compatible.
package provide Globx 0.05
proc globx {startDir {search *} {cb ""}} {
set dirStack [list [file normalize $startDir]]
set files {}
set fc 0
while {[llength $dirStack]} {
set newStack {}
foreach dir $dirStack {
# temporary var's only because eventually using CallBack
set c [list glob -noc -typ f -dir $dir --]; eval lappend c $search; set fn [eval $c]
set c [list glob -noc -typ {f hidden} -dir $dir --]; eval lappend c $search; set fh [eval $c]
if {[string equal $cb ""]} {
eval lappend files $fn $fh
} else {
foreach f [concat $fn $fh] {
incr fc
uplevel [list $cb $f]
}
}
set dn [glob -noc -typ d -dir $dir *]
set dh [glob -noc -typ {d hidden} -dir $dir *]
# eval lappend newStack $dn $dh; # v0.01
# Wikipatch Start v0.02 ---
foreach newDir [concat $dn $dh] {
set theDir [file tail $newDir]
if {[string equal $theDir "." ] || \
[string equal $theDir ".."]} {
# Don't push this, otherwise entering an endless
# loop (on UNIX, at least)
} else {
lappend newStack $newDir
}
}
# Wikipatch Ende ---
}
set dirStack $newStack
update; # keep Background alive
}
if {[string equal $cb ""]} {
return [lsort $files]
} else {
return $fc
}
}
# Die Anwendung von Wildcards hier wäre zwar möglich, aber erst
# auf UNTERSTER EBENE sinnvoll bzw. wäre ganz am Ende
# eine Filterung des Gesamtpfads mittels string match besser.
proc globx2 {startDir {cb ""}} {
set dirStack [list [file normalize $startDir]]
set dirs $dirStack; # bis v0.04: {} (ACHTUNG: potentielle Inkompatibilität!)
set dc 0
while {[llength $dirStack]} {
set newStack {}
foreach dir $dirStack {
set dn [glob -noc -typ d -dir $dir -- *]
set dh [glob -noc -typ {d hidden} -dir $dir -- *]
if {[string equal $cb ""]} {
eval lappend dirs $dn $dh
} else {
foreach d [concat $dn $dh] {
incr dc
uplevel [list $cb $d]
}
}
foreach newDir [concat $dn $dh] {
set theDir [file tail $newDir]
if {[string equal $theDir "." ] || \
[string equal $theDir ".."]} {
# Don't push this, otherwise entering an endless
# loop (on UNIX, at least)
} else {
lappend newStack $newDir
}
}
}
set dirStack $newStack
update
}
if {[string equal $cb ""]} {
return [lsort $dirs]
} else {
return $dc
}
}
#*******************************************************************************Examples:Without a callback, directly returning the filenames as a list:puts [globx c:/winnt] puts [globx c:/winnt *.dll]Returning the filenames unsorted name-by-name via callback:
proc callback file {
puts $file
}
puts [globx c:/winnt * callback]; # will return the number of files readThis is to save memory!Specifying search masks (v0.04)
puts [globx c:/winnt {*.dll *.sys *.exe}]ECS: I had to include some lines to test for "." and ".." otherwise the routine loops.MH: On my platform (W2k, Tcl 8.4.6), the original routine does not loop; the glob command never returns '..' and '.'. Which platform did you test the code on?ECS: Debian Linux: Linux babylon 2.4.26-ow2 #1 Fri Jul 9 15:19:06 BRT 2004 i686 GNU/Linux TCL is 8.4.7 (samething happens with 8.4.6). In any case it is better to be safe than sorry :-)MHo Version 0.6:
package provide Globx 0.06
proc globx {startDir {search *} {cb ""}} {
set dirStack [list [file normalize $startDir]]
set files {}
set fc 0
while {[llength $dirStack]} {
set newStack {}
foreach dir $dirStack {
# temporary var's only because eventually using CallBack
catch {
set c [list glob -noc -typ f -dir $dir --]; eval lappend c $search; set fn [eval $c]
set c [list glob -noc -typ {f hidden} -dir $dir --]; eval lappend c $search; set fh [eval $c]
if {[string equal $cb ""]} {
eval lappend files $fn $fh
} else {
foreach f [concat $fn $fh] {
incr fc
uplevel [list $cb $f]
}
}
}
catch {
set dn [glob -noc -typ d -dir $dir *]
set dh [glob -noc -typ {d hidden} -dir $dir *]
# eval lappend newStack $dn $dh; # v0.01
# Wikipatch Start v0.02 ---
foreach newDir [concat $dn $dh] {
set theDir [file tail $newDir]
if {[string equal $theDir "." ] || \
[string equal $theDir ".."]} {
# Don't push this, otherwise entering an endless
# loop (on UNIX, at least)
} else {
lappend newStack $newDir
}
}
# Wikipatch Ende ---
}
}
set dirStack $newStack
update; # keep Background alive
}
if {[string equal $cb ""]} {
return [lsort $files]
} else {
return $fc
}
}
# Die Anwendung von Wildcards hier wäre zwar möglich, aber erst auf UNTERSTER
# EBENE sinnvoll bzw. wäre ganz am Ende eine Filterung des Gesamtpfads mittels
# string match besser.
proc globx2 {startDir {cb ""}} {
set dirStack [list [file normalize $startDir]]
set dirs $dirStack; # bis v0.04: {} (ACHTUNG: potentielle Inkompatibilität!)
set dc 0
while {[llength $dirStack]} {
set newStack {}
foreach dir $dirStack {
catch {
set dn [glob -noc -typ d -dir $dir -- *]
set dh [glob -noc -typ {d hidden} -dir $dir -- *]
if {[string equal $cb ""]} {
eval lappend dirs $dn $dh
} else {
foreach d [concat $dn $dh] {
incr dc
uplevel [list $cb $d]
}
}
foreach newDir [concat $dn $dh] {
set theDir [file tail $newDir]
if {[string equal $theDir "." ] || \
[string equal $theDir ".."]} {
# Don't push this, otherwise entering an endless
# loop (on UNIX, at least)
} else {
lappend newStack $newDir
}
}
}
}
set dirStack $newStack
update
}
if {[string equal $cb ""]} {
return [lsort $dirs]
} else {
return $dc
}
}
#*******************************************************************************
