###############################################################################
#
# file3.eagle --
#
# Extensible Adaptable Generalized Logic Engine (Eagle)
# Eagle File 3 Package File
#
# Copyright (c) 2007-2012 by Joe Mistachkin. All rights reserved.
#
# See the file "license.terms" for information on usage and redistribution of
# this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
# RCS: @(#) $Id: $
#
###############################################################################
#
# NOTE: Use our own namespace here because even though we do not directly
# support namespaces ourselves, we do not want to pollute the global
# namespace if this script actually ends up being evaluated in Tcl.
#
namespace eval ::Eagle {
#
# NOTE: This procedure uses the type of the specified path to figure out
# the (possibly normalized) path to add to the finder results.
#
proc getDirResultPath { pattern path } {
#
# NOTE: This should work properly in both Tcl and Eagle.
# Is the result path itself already absolute?
#
if {[file pathtype $path] eq "absolute"} then {
#
# NOTE: Ok, the result path is already absolute.
# Normalize and return it.
#
return [file normalize $path]
} elseif {[file pathtype $pattern] eq "absolute"} then {
#
# NOTE: The pattern refers to an absolute path. Strip
# the final part of the pattern and join it with
# the result path (which we already know is not
# absolute).
#
return [file normalize [file join [file dirname $pattern] $path]]
} else {
#
# NOTE: Neither the result path nor the input pattern
# contain an absolute path; therefore, use the
# current directory to hang the result path on.
#
return [file normalize [file join [pwd] $path]]
}
}
#
# NOTE: This procedure returns a list of directories matching the pattern
# specified. It does not recurse into sub-directories.
#
proc findDirectories { pattern } {
if {[isEagle]} then {
#
# NOTE: Block non-Windows platforms since this is Windows specific.
#
if {![isWindows]} then {
error "not supported on this operating system"
}
#
# NOTE: This should work properly in Eagle only.
#
set dir ""; set result [list]
#
# HACK: Optimize the variable access in this procedure to be
# as fast as possible.
#
makeVariableFast dir true; makeVariableFast result true
#
# NOTE: We will need to grab the ComSpec environment variable.
#
global env
foreach dir [split [exec -unicode $env(ComSpec) /u /c dir \
/ad /b [appendArgs \" [file nativename $pattern] \"]] \n] {
set dir [string trim $dir]
if {[string length $dir] > 0} then {
set dir [getDirResultPath $pattern $dir]
if {[lsearch -variable -exact -nocase result $dir] == -1} then {
lappend result $dir
}
}
}
foreach dir [split [exec -unicode $env(ComSpec) /u /c dir \
/ahd /b [appendArgs \" [file nativename $pattern] \"]] \n] {
set dir [string trim $dir]
if {[string length $dir] > 0} then {
set dir [getDirResultPath $pattern $dir]
if {[lsearch -variable -exact -nocase result $dir] == -1} then {
lappend result $dir
}
}
}
return $result
} else {
#
# NOTE: This should work properly in Tcl only.
#
eval lappend result [glob -nocomplain -types {d} \
[file normalize $pattern]]
eval lappend result [glob -nocomplain -types {d hidden} \
[file normalize $pattern]]
return $result
}
}
#
# NOTE: This procedure returns a list of directories matching the pattern
# specified. It recurses into sub-directories.
#
proc findDirectoriesRecursive { pattern } {
if {[isEagle]} then {
#
# NOTE: Block non-Windows platforms since this is Windows specific.
#
if {![isWindows]} then {
error "not supported on this operating system"
}
#
# NOTE: This should work properly in Eagle only.
#
set dir ""; set result [list]
#
# HACK: Optimize the variable access in this procedure to be
# as fast as possible.
#
makeVariableFast dir true; makeVariableFast result true
#
# NOTE: We will need to grab the ComSpec environment variable.
#
global env
foreach dir [split [exec -unicode $env(ComSpec) /u /c dir \
/ad /s /b [appendArgs \" [file nativename $pattern] \"]] \n] {
set dir [string trim $dir]
if {[string length $dir] > 0} then {
set dir [getDirResultPath $pattern $dir]
if {[lsearch -variable -exact -nocase result $dir] == -1} then {
lappend result $dir
}
}
}
foreach dir [split [exec -unicode $env(ComSpec) /u /c dir \
/ahd /s /b [appendArgs \" [file nativename $pattern] \"]] \n] {
set dir [string trim $dir]
if {[string length $dir] > 0} then {
set dir [getDirResultPath $pattern $dir]
if {[lsearch -variable -exact -nocase result $dir] == -1} then {
lappend result $dir
}
}
}
return $result
} else {
#
# NOTE: Block non-Windows platforms since this is Windows specific.
#
if {![isWindows]} then {
error "not supported on this operating system"
}
#
# NOTE: This should work properly in Tcl only.
#
set result [list]
#
# NOTE: We will need to grab the ComSpec environment variable.
#
global env
#
# NOTE: We will also need to check the Tcl version.
#
global tcl_version
catch {
foreach dir [split [exec $env(ComSpec) /c dir /ad /s /b \
[file nativename $pattern]] \n] {
set dir [string trim $dir]
if {[string length $dir] > 0} then {
set dir [getDirResultPath $pattern $dir]
#
# HACK: The -nocase option to [lsearch] is only available
# starting with Tcl 8.5.
#
if {$tcl_version >= 8.5} then {
if {[lsearch -exact -nocase $result $dir] == -1} then {
lappend result $dir
}
} else {
if {[lsearch -exact [string tolower $result] \
[string tolower $dir]] == -1} then {
lappend result $dir
}
}
}
}
}
catch {
foreach dir [split [exec $env(ComSpec) /c dir /ahd /s /b \
[file nativename $pattern]] \n] {
set dir [string trim $dir]
if {[string length $dir] > 0} then {
set dir [getDirResultPath $pattern $dir]
#
# HACK: The -nocase option to [lsearch] is only available
# starting with Tcl 8.5.
#
if {$tcl_version >= 8.5} then {
if {[lsearch -exact -nocase $result $dir] == -1} then {
lappend result $dir
}
} else {
if {[lsearch -exact [string tolower $result] \
[string tolower $dir]] == -1} then {
lappend result $dir
}
}
}
}
}
return $result
}
}
#
# NOTE: This procedure returns a list of files matching the pattern
# specified. It does not recurse into sub-directories.
#
proc findFiles { pattern } {
if {[isEagle]} then {
#
# NOTE: Block non-Windows platforms since this is Windows specific.
#
if {![isWindows]} then {
error "not supported on this operating system"
}
#
# NOTE: This should work properly in Eagle only.
#
set fileName ""; set result [list]
#
# HACK: Optimize the variable access in this procedure to be
# as fast as possible.
#
makeVariableFast fileName true; makeVariableFast result true
#
# NOTE: We will need to grab the ComSpec environment variable.
#
global env
foreach fileName [split [exec -unicode $env(ComSpec) /u /c dir \
/a-d /b [appendArgs \" [file nativename $pattern] \"]] \n] {
set fileName [string trim $fileName]
if {[string length $fileName] > 0} then {
set fileName [getDirResultPath $pattern $fileName]
if {[lsearch -variable -exact -nocase result $fileName] == -1} then {
lappend result $fileName
}
}
}
foreach fileName [split [exec -unicode $env(ComSpec) /u /c dir \
/ah-d /b [appendArgs \" [file nativename $pattern] \"]] \n] {
set fileName [string trim $fileName]
if {[string length $fileName] > 0} then {
set fileName [getDirResultPath $pattern $fileName]
if {[lsearch -variable -exact -nocase result $fileName] == -1} then {
lappend result $fileName
}
}
}
return $result
} else {
#
# NOTE: This should work properly in Tcl only.
#
eval lappend result [glob -nocomplain -types {f} \
[file normalize $pattern]]
eval lappend result [glob -nocomplain -types {f hidden} \
[file normalize $pattern]]
return $result
}
}
#
# NOTE: This procedure returns a list of files matching the pattern
# specified. It recurses into sub-directories.
#
proc findFilesRecursive { pattern } {
if {[isEagle]} then {
#
# NOTE: Block non-Windows platforms since this is Windows specific.
#
if {![isWindows]} then {
error "not supported on this operating system"
}
#
# NOTE: This should work properly in Eagle only.
#
set fileName ""; set result [list]
#
# HACK: Optimize the variable access in this procedure to be
# as fast as possible.
#
makeVariableFast fileName true; makeVariableFast result true
#
# NOTE: We will need to grab the ComSpec environment variable.
#
global env
foreach fileName [split [exec -unicode $env(ComSpec) /u /c dir \
/a-d /s /b [appendArgs \" [file nativename $pattern] \"]] \n] {
set fileName [string trim $fileName]
if {[string length $fileName] > 0} then {
set fileName [getDirResultPath $pattern $fileName]
if {[lsearch -variable -exact -nocase result $fileName] == -1} then {
lappend result $fileName
}
}
}
foreach fileName [split [exec -unicode $env(ComSpec) /u /c dir \
/ah-d /s /b [appendArgs \" [file nativename $pattern] \"]] \n] {
set fileName [string trim $fileName]
if {[string length $fileName] > 0} then {
set fileName [getDirResultPath $pattern $fileName]
if {[lsearch -variable -exact -nocase result $fileName] == -1} then {
lappend result $fileName
}
}
}
return $result
} else {
#
# NOTE: Block non-Windows platforms since this is Windows specific.
#
if {![isWindows]} then {
error "not supported on this operating system"
}
#
# NOTE: This should work properly in Tcl only.
#
set result [list]
#
# NOTE: We will need to grab the ComSpec environment variable.
#
global env
#
# NOTE: We will also need to check the Tcl version.
#
global tcl_version
catch {
foreach fileName [split [exec $env(ComSpec) /c dir /a-d /s /b \
[file nativename $pattern]] \n] {
set fileName [string trim $fileName]
if {[string length $fileName] > 0} then {
set fileName [getDirResultPath $pattern $fileName]
#
# HACK: The -nocase option to [lsearch] is only available
# starting with Tcl 8.5.
#
if {$tcl_version >= 8.5} then {
if {[lsearch -exact -nocase $result $fileName] == -1} then {
lappend result $fileName
}
} else {
if {[lsearch -exact [string tolower $result] \
[string tolower $fileName]] == -1} then {
lappend result $fileName
}
}
}
}
}
catch {
foreach fileName [split [exec $env(ComSpec) /c dir /ah-d /s /b \
[file nativename $pattern]] \n] {
set fileName [string trim $fileName]
if {[string length $fileName] > 0} then {
set fileName [getDirResultPath $pattern $fileName]
#
# HACK: The -nocase option to [lsearch] is only available
# starting with Tcl 8.5.
#
if {$tcl_version >= 8.5} then {
if {[lsearch -exact -nocase $result $fileName] == -1} then {
lappend result $fileName
}
} else {
if {[lsearch -exact [string tolower $result] \
[string tolower $fileName]] == -1} then {
lappend result $fileName
}
}
}
}
}
return $result
}
}
#
# NOTE: This procedure attempts to copy all files matching the specified
# source directory and patterns to the destination directory. This
# procedure may raise script errors.
#
proc copyFilesRecursive {
sourceDirectory targetDirectory {patterns ""} {options ""} } {
#
# NOTE: Block non-Windows platforms since this is Windows specific.
#
if {![isWindows]} then {
error "not supported on this operating system"
}
#
# NOTE: Start out with the [exec] command and the necessary options
# to the command.
#
if {[isEagle]} then {
set command [list exec -exitcode exitCode -- robocopy]
} else {
set command [list exec -- robocopy]
}
#
# NOTE: Add the source and target directory names. These are always
# required.
#
if {[isEagle]} then {
lappend command [appendArgs \" [file nativename $sourceDirectory] \"]
lappend command [appendArgs \" [file nativename $targetDirectory] \"]
} else {
lappend command [file nativename $sourceDirectory]
lappend command [file nativename $targetDirectory]
}
#
# NOTE: If there are patterns specified, add them now. By default,
# all files are copied (i.e. "*.*").
#
if {[llength $patterns] > 0} then {
eval lappend command $patterns
}
#
# NOTE: Should the default Robocopy options be excluded? Generally,
# these options should not be excluded. All matching files
# are copied, using restartable mode, and using extra options
# when running as administrator to enable copying more files
# and metadata, e.g. using backup mode and copying the extra
# file security information.
#
if {[lsearch -exact $options -nocopyopts] == -1} then {
lappend command /E
if {[isEagle] && [isAdministrator]} then {
lappend command /ZB /COPYALL
} else {
lappend command /Z
}
lappend command /DCOPY:T
}
#
# NOTE: Should we override the default number of retries per file?
# If no retry related option is found, the Robocopy default
# behavior will be used (i.e. 1 million retries per file).
#
if {[set index [lsearch -glob $options -retries:*]] != -1} then {
#
# NOTE: Grab the retry option name/value from the list of options
# and attempt to extract the integer number of retries from
# it.
#
set value [lindex $options $index]
if {[regexp -- {^-retries:(\d+)$} $value dummy retries] && \
[string is integer -strict $retries] && $retries >= 0} then {
#
# NOTE: Use the specified number of retries. This value will
# be greater than or equal to zero and within the range
# of a 32-bit integer.
#
lappend command [appendArgs /R: $retries]
} else {
#
# NOTE: The specified number of retries is somehow invalid.
#
error "number of retries must be a positive integer"
}
} elseif {[lsearch -exact $options -retry] == -1} then {
#
# NOTE: Fallback to setting retry count to zero, which is used
# to disable per-file retries.
#
lappend command /R:0
}
#
# NOTE: By default, do not process file system junctions. This
# is used to avoid the possibility of infinite loops when
# traversing the file system (i.e. because it is trivial
# to utilize file system junctions in order to construct
# directory trees that loop back upon themselves).
#
if {[lsearch -exact $options -junctions] == -1} then {
lappend command /XJ
}
#
# NOTE: By default, do not purge any files that happen to be in
# the target directory.
#
if {[lsearch -exact $options -purge] != -1} then {
lappend command /PURGE
}
#
# NOTE: By default, do not produce a verbose log files in the
# temporary directory.
#
if {[lsearch -exact $options -logging] != -1} then {
lappend command /X /V /FP /NP
package require Eagle.Test
set logFileName [getTemporaryFileName]
if {[isEagle]} then {
lappend command [appendArgs \
\"/LOG: [file nativename $logFileName] \"]
} else {
lappend command [appendArgs \
/LOG: [file nativename $logFileName]]
}
}
#
# NOTE: Upon success, the exit code from Robocopy will be between
# zero and seven. If the exit code is eight or greater, it
# has somehow failed and that error must be reported to the
# caller via raising a script error.
#
if {[isEagle]} then {
#
# NOTE: Execute the resulting [exec] command in our context and
# capture the results.
#
set result [eval $command]
if {[info exists exitCode] && $exitCode in \
[list Success Failure Exception 0 1 2 3 4 5 6 7]} then {
#
# NOTE: Return the captured output from the Robocopy command,
# whatever it was. Generally, this will either be the
# summary and details of the files copied -OR- the log
# file name.
#
return $result
} else {
#
# NOTE: Raise a script error, using the captured errors from
# the Robocopy command, whatever it was.
#
error [appendArgs \
"command \"" $command "\" error: " $result]
}
} else {
#
# NOTE: Execute the resulting [exec] command in our context and
# capture the results.
#
if {[catch $command result] == 0 || ([info exists ::errorCode] && \
[maybeGetExitCode $::errorCode 8] < 8)} then {
#
# NOTE: Return the captured output from the Robocopy command,
# whatever it was. Generally, this will either be the
# summary and details of the files copied -OR- the log
# file name.
#
return $result
} else {
#
# NOTE: Raise a script error, using the captured errors from
# the Robocopy command, whatever it was.
#
error [appendArgs \
"command \"" $command "\" error: " $result]
}
}
}
#
# NOTE: Provide the Eagle "file finder" package to the interpreter.
#
package provide Eagle.File.Finder \
[expr {[isEagle] ? [info engine PatchLevel] : "1.0"}]
}