130 lines
3.2 KiB
Tcl
Executable File
130 lines
3.2 KiB
Tcl
Executable File
# Implements a Tcl-compatible glob command based on readdir
|
|
#
|
|
# (c) 2008 Steve Bennett <steveb@workware.net.au>
|
|
#
|
|
# See LICENCE in this directory for licensing.
|
|
|
|
package require readdir
|
|
|
|
# Implements the Tcl glob command
|
|
#
|
|
# Usage: glob ?-nocomplain? 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} {
|
|
|
|
# If $dir is a directory, return a list of all entries
|
|
# it contains which match $pattern
|
|
#
|
|
local proc glob.readdir_pattern {dir pattern} {
|
|
set result {}
|
|
|
|
# readdir doesn't return . or .., so simulate it here
|
|
if {$pattern in {. ..}} {
|
|
return $pattern
|
|
}
|
|
|
|
# If the pattern isn't actually a pattern...
|
|
if {[string match {*[*?]*} $pattern]} {
|
|
# Use -nocomplain here to return nothing if $dir is not a directory
|
|
set files [readdir -nocomplain $dir]
|
|
} elseif {[file isdir $dir] && [file exists $dir/$pattern]} {
|
|
set files [list $pattern]
|
|
} else {
|
|
set files ""
|
|
}
|
|
|
|
foreach name $files {
|
|
if {[string match $pattern $name]} {
|
|
# Only include entries starting with . if the pattern starts with .
|
|
if {[string index $name 0] eq "." && [string index $pattern 0] ne "."} {
|
|
continue
|
|
}
|
|
lappend result $name
|
|
}
|
|
}
|
|
|
|
return $result
|
|
}
|
|
|
|
# If the pattern contains a braced expression, return a list of
|
|
# patterns with the braces expanded. {c,b}* => c* b*
|
|
# Otherwise just return the pattern
|
|
# Note: Only supports one braced expression. i.e. not {a,b}*{c,d}*
|
|
proc glob.expandbraces {pattern} {
|
|
# Avoid regexp for dependency reasons.
|
|
# XXX: Doesn't handle backslashed braces
|
|
if {[set fb [string first "\{" $pattern]] < 0} {
|
|
return $pattern
|
|
}
|
|
if {[set nb [string first "\}" $pattern $fb]] < 0} {
|
|
return $pattern
|
|
}
|
|
set before [string range $pattern 0 $fb-1]
|
|
set braced [string range $pattern $fb+1 $nb-1]
|
|
set after [string range $pattern $nb+1 end]
|
|
|
|
lmap part [split $braced ,] {
|
|
set pat $before$part$after
|
|
}
|
|
}
|
|
|
|
# Core glob implementation. Returns a list of files/directories matching the pattern
|
|
proc glob.glob {pattern} {
|
|
set dir [file dirname $pattern]
|
|
if {$dir eq $pattern} {
|
|
# At the top level
|
|
return [list $dir]
|
|
}
|
|
|
|
# Recursively expand the parent directory
|
|
set dirlist [glob.glob $dir]
|
|
set pattern [file tail $pattern]
|
|
|
|
# Now collect the fiels/directories
|
|
set result {}
|
|
foreach dir $dirlist {
|
|
set globdir $dir
|
|
if {[string match "*/" $dir]} {
|
|
set sep ""
|
|
} elseif {$dir eq "."} {
|
|
set globdir ""
|
|
set sep ""
|
|
} else {
|
|
set sep /
|
|
}
|
|
foreach pat [glob.expandbraces $pattern] {
|
|
foreach name [glob.readdir_pattern $dir $pat] {
|
|
lappend result $globdir$sep$name
|
|
}
|
|
}
|
|
}
|
|
return $result
|
|
}
|
|
|
|
# Start of main glob
|
|
set nocomplain 0
|
|
|
|
if {[lindex $args 0] eq "-nocomplain"} {
|
|
set nocomplain 1
|
|
set args [lrange $args 1 end]
|
|
}
|
|
|
|
set result {}
|
|
foreach pattern $args {
|
|
lappend result {*}[glob.glob $pattern]
|
|
}
|
|
|
|
if {$nocomplain == 0 && [llength $result] == 0} {
|
|
return -code error "no files matched glob patterns"
|
|
}
|
|
|
|
return $result
|
|
}
|