# Implements a mostly Tcl-compatible glob command based on readdir # # (c) 2008 Steve Bennett # (c) 2012 Alexander Shpilkin # # See LICENCE in this directory for licensing. package require readdir # Return a list of all entries in $dir that match the pattern. proc glob.globdir {dir pattern} { if {[file exists $dir/$pattern]} { # Simple case return [list $pattern] } set result {} set files [readdir $dir] lappend files . .. foreach name $files { if {[string match $pattern $name]} { # Starting dots match only explicitly if {[string index $name 0] eq "." && [string index $pattern 0] ne "."} { continue } lappend result $name } } return $result } # Return the list of patterns resulting from expanding any braced # alternatives inside the given pattern, prepending the unprocessed # part of the pattern. Does _not_ handle escaped braces or commas. proc glob.explode {pattern} { set oldexp {} set newexp {""} while 1 { set oldexp $newexp set newexp {} set ob [string first \{ $pattern] set cb [string first \} $pattern] if {$ob < $cb && $ob != -1} { set mid [string range $pattern 0 $ob-1] set subexp [lassign [glob.explode [string range $pattern $ob+1 end]] pattern] if {$pattern eq ""} { error "unmatched open brace in glob pattern" } set pattern [string range $pattern 1 end] foreach subs $subexp { foreach sub [split $subs ,] { foreach old $oldexp { lappend newexp $old$mid$sub } } } } elseif {$cb != -1} { set suf [string range $pattern 0 $cb-1] set rest [string range $pattern $cb end] break } else { set suf $pattern set rest "" break } } foreach old $oldexp { lappend newexp $old$suf } list $rest {*}$newexp } # Core glob implementation. Returns a list of files/directories inside # base matching pattern, in {realname name} pairs. proc glob.glob {base pattern} { set dir [file dirname $pattern] if {$pattern eq $dir || $pattern eq ""} { return [list [file join $base $dir] $pattern] } elseif {$pattern eq [file tail $pattern]} { set dir "" } # Recursively expand the parent directory set dirlist [glob.glob $base $dir] set pattern [file tail $pattern] # Collect the files/directories set result {} foreach {realdir dir} $dirlist { if {![file isdir $realdir]} { continue } if {[string index $dir end] ne "/" && $dir ne ""} { append dir / } foreach name [glob.globdir $realdir $pattern] { lappend result [file join $realdir $name] $dir$name } } return $result } # Implements the Tcl glob command # # Usage: glob ?-nocomplain? ?-directory dir? ?--? pattern ... # # Patterns use 'string match' (glob) pattern matching for each # directory level, plus support for braced alternations. # # e.g. glob {te[a-e]*/*.{c,tcl}} # # Note: files starting with . will only be returned if matching component # of the pattern starts with . proc glob {args} { set nocomplain 0 set base "" set tails 0 set n 0 foreach arg $args { if {[info exists param]} { set $param $arg unset param incr n continue } switch -glob -- $arg { -d* { set switch $arg set param base } -n* { set nocomplain 1 } -ta* { set tails 1 } -- { incr n break } -* { return -code error "bad option \"$arg\": must be -directory, -nocomplain, -tails, or --" } * { break } } incr n } if {[info exists param]} { return -code error "missing argument to \"$switch\"" } if {[llength $args] <= $n} { return -code error "wrong # args: should be \"glob ?options? pattern ?pattern ...?\"" } set args [lrange $args $n end] set result {} foreach pattern $args { set escpattern [string map { \\\\ \x01 \\\{ \x02 \\\} \x03 \\, \x04 } $pattern] set patexps [lassign [glob.explode $escpattern] rest] if {$rest ne ""} { return -code error "unmatched close brace in glob pattern" } foreach patexp $patexps { set patexp [string map { \x01 \\\\ \x02 \{ \x03 \} \x04 , } $patexp] foreach {realname name} [glob.glob $base $patexp] { incr n if {$tails} { lappend result $name } else { lappend result [file join $base $name] } } } } if {!$nocomplain && [llength $result] == 0} { set s $(([llength $args] > 1) ? "s" : "") return -code error "no files matched glob pattern$s \"[join $args]\"" } return $result }