auto.tcl –
utility procs formerly in init.tcl dealing with auto execution
of commands and can be auto loaded themselves.
RCS: @(#) $Id: auto.tcl,v 1.12.2.10 2005/07/23 03:31:41 dgp Exp $
Copyright (c) 1991-1993 The Regents of the University of California.
Copyright (c) 1994-1998 Sun Microsystems, Inc.
See the file “license.terms” for information on usage and redistribution
of this file, and for a DISCLAIMER OF ALL WARRANTIES.
auto_reset –
Destroy all cached information for auto-loading and auto-execution,
so that the information gets recomputed the next time it’s needed.
Also delete any procedures that are listed in the auto-load index
except those defined in this file.
Arguments:
None.
proc auto_reset {} {
global auto_execs auto_index auto_oldpath
foreach p [info procs] {
if {[info exists auto_index($p)] && ![string match auto_* $p]
&& ([lsearch -exact {unknown pkg_mkIndex tclPkgSetup
tcl_findLibrary pkg_compareExtension
tclPkgUnknown tcl::MacOSXPkgUnknown
tcl::MacPkgUnknown} $p] < 0)} {
rename $p {}
}
}
unset -nocomplain auto_execs auto_index auto_oldpath
}
tcl_findLibrary –
This is a utility for extensions that searches for a library directory
using a canonical searching algorithm. A side effect is to source
the initialization script and set a global library variable.
Arguments:
basename Prefix of the directory name, (e.g., “tk”)
version Version number of the package, (e.g., “8.0”)
patch Patchlevel of the package, (e.g., “8.0.3”)
initScript Initialization script to source (e.g., tk.tcl)
enVarName environment variable to honor (e.g., TK_LIBRARY)
varName Global variable to set when done (e.g., tk_library)
proc tcl_findLibrary {basename version patch initScript enVarName varName} {
upvar #0 $varName the_library
global env errorInfo
set dirs {}
set errors {}
# The C application may have hardwired a path, which we honor
if {[info exists the_library] && $the_library ne ""} {
lappend dirs $the_library
} else {
# Do the canonical search
# 1. From an environment variable, if it exists.
# Placing this first gives the end-user ultimate control
# to work-around any bugs, or to customize.
if {[info exists env($enVarName)]} {
lappend dirs $env($enVarName)
}
# 2. In the package script directory registered within
# the configuration of the package itself.
#
# Only do this for Tcl 8.5+, when Tcl_RegsiterConfig() is available.
#if {[catch {
# ::${basename}::pkgconfig get scriptdir,runtime
#} value] == 0} {
# lappend dirs $value
#}
# 3. Relative to auto_path directories. This checks relative to the
# Tcl library as well as allowing loading of libraries added to the
# auto_path that is not relative to the core library or binary paths.
foreach d $::auto_path {
lappend dirs [file join $d $basename$version]
if {$::tcl_platform(platform) eq "unix"
&& $::tcl_platform(os) eq "Darwin"} {
# 4. On MacOSX, check the Resources/Scripts subdir too
lappend dirs [file join $d $basename$version Resources Scripts]
}
}
# 3. Various locations relative to the executable
# ../lib/foo1.0 (From bin directory in install hierarchy)
# ../../lib/foo1.0 (From bin/arch directory in install hierarchy)
# ../library (From unix directory in build hierarchy)
set parentDir [file dirname [file dirname [info nameofexecutable]]]
set grandParentDir [file dirname $parentDir]
lappend dirs [file join $parentDir lib $basename$version]
lappend dirs [file join $grandParentDir lib $basename$version]
lappend dirs [file join $parentDir library]
# Remaining locations are out of date (when relevant, they ought
# to be covered by the $::auto_path seach above).
#
# ../../library (From unix/arch directory in build hierarchy)
# ../../foo1.0.1/library
# (From unix directory in parallel build hierarchy)
# ../../../foo1.0.1/library
# (From unix/arch directory in parallel build hierarchy)
#
# For the sake of extra compatibility safety, we keep adding these
# paths during the 8.4.* release series.
if {1} {
lappend dirs [file join $grandParentDir library]
lappend dirs [file join $grandParentDir $basename$patch library]
lappend dirs [file join [file dirname $grandParentDir] \
$basename$patch library]
}
}
# uniquify $dirs in order
array set seen {}
foreach i $dirs {
# For Tcl 8.4.9, we've disabled the use of [file normalize] here.
# This means that two different path names that are the same path
# in normalized form, will both remain on the search path. There
# should be no harm in that, just a bit more file system access
# than is strictly necessary.
#
# [file normalize] has been disabled because of reports it has
# caused difficulties with the freewrap utility. To keep
# compatibility with freewrap's needs, we'll keep this disabled
# throughout the 8.4.x (x >= 9) releases. See Bug 1072136.
if {1 || [interp issafe]} {
set norm $i
} else {
set norm [file normalize $i]
}
if {[info exists seen($norm)]} { continue }
set seen($norm) ""
lappend uniqdirs $i
}
set dirs $uniqdirs
foreach i $dirs {
set the_library $i
set file [file join $i $initScript]
# source everything when in a safe interpreter because
# we have a source command, but no file exists command
if {[interp issafe] || [file exists $file]} {
if {![catch {uplevel #0 [list source $file]} msg]} {
return
} else {
append errors "$file: $msg\n$errorInfo\n"
}
}
}
unset -nocomplain the_library
set msg "Can't find a usable $initScript in the following directories: \n"
append msg " $dirs\n\n"
append msg "$errors\n\n"
append msg "This probably means that $basename wasn't installed properly.\n"
error $msg
}
–––––––––––––––––––––––––––––––––––
auto_mkindex
–––––––––––––––––––––––––––––––––––
The following procedures are used to generate the tclIndex file
from Tcl source files. They use a special safe interpreter to
parse Tcl source files, writing out index entries as “proc”
commands are encountered. This implementation won’t work in a
safe interpreter, since a safe interpreter can’t create the
special parser and mess with its commands.
if {[interp issafe]} {
return ;# Stop sourcing the file here
}
auto_mkindex –
Regenerate a tclIndex file from Tcl source files. Takes as argument
the name of the directory in which the tclIndex file is to be placed,
followed by any number of glob patterns to use in that directory to
locate all of the relevant files.
Arguments:
dir - Name of the directory in which to create an index.
args - Any number of additional arguments giving the
names of files within dir. If no additional
are given auto_mkindex will look for *.tcl.
proc auto_mkindex {dir args} {
global errorCode errorInfo
if {[interp issafe]} {
error "can't generate index within safe interpreter"
}
set oldDir [pwd]
cd $dir
set dir [pwd]
append index "# Tcl autoload index file, version 2.0\n"
append index "# This file is generated by the \"auto_mkindex\" command\n"
append index "# and sourced to set up indexing information for one or\n"
append index "# more commands. Typically each line is a command that\n"
append index "# sets an element in the auto_index array, where the\n"
append index "# element name is the name of a command and the value is\n"
append index "# a script that loads the command.\n\n"
if {[llength $args] == 0} {
set args *.tcl
}
auto_mkindex_parser::init
foreach file [eval [linsert $args 0 glob --]] {
if {[catch {auto_mkindex_parser::mkindex $file} msg] == 0} {
append index $msg
} else {
set code $errorCode
set info $errorInfo
cd $oldDir
error $msg $info $code
}
}
auto_mkindex_parser::cleanup
set fid [open "tclIndex" w]
puts -nonewline $fid $index
close $fid
cd $oldDir
}
Original version of auto_mkindex that just searches the source
code for “proc” at the beginning of the line.
proc auto_mkindex_old {dir args} {
global errorCode errorInfo
set oldDir [pwd]
cd $dir
set dir [pwd]
append index “# Tcl autoload index file, version 2.0\n”
append index “# This file is generated by the "auto_mkindex" command\n”
append index “# and sourced to set up indexing information for one or\n”
append index “# more commands. Typically each line is a command that\n”
append index “# sets an element in the auto_index array, where the\n”
append index “# element name is the name of a command and the value is\n”
append index “# a script that loads the command.\n\n”
if {[llength $args] == 0} {
set args .tcl
}
foreach file [eval [linsert $args 0 glob –]] {
set f “”
set error [catch {
set f [open $file]
while {[gets $f line] >= 0} {
if {[regexp {^proc[ ]+([^ ])} $line match procName]} {
set procName [lindex [auto_qualify procName "::"] 0] append index "set [list auto_index(procName)]”
append index “ [list source [file join $dir [list $file]]]\n”
}
}
close f } msg] if {error} {
set code $errorCode
set info $errorInfo
catch {close $f}
cd $oldDir
error $msg $info $code
}
}
set f “”
set error [catch {
set f [open tclIndex w]
puts -nonewline $f $index
close $f
cd oldDir } msg] if {error} {
set code $errorCode
set info $errorInfo
catch {close $f}
cd $oldDir
error $msg $info $code
}
}
Create a safe interpreter that can be used to parse Tcl source files
generate a tclIndex file for autoloading. This interp contains
commands for things that need index entries. Each time a command
is executed, it writes an entry out to the index file.
namespace eval auto_mkindex_parser {
variable parser “” ;# parser used to build index
variable index “” ;# maintains index as it is built
variable scriptFile “” ;# name of file being processed
variable contextStack “” ;# stack of namespace scopes
variable imports “” ;# keeps track of all imported cmds
variable initCommands “” ;# list of commands that create aliases
proc init {} {
variable parser
variable initCommands
if {![interp issafe]} {
set parser [interp create -safe]
$parser hide info
$parser hide rename
$parser hide proc
$parser hide namespace
$parser hide eval
$parser hide puts
$parser invokehidden namespace delete ::
$parser invokehidden proc unknown {args} {}
# We'll need access to the "namespace" command within the
# interp. Put it back, but move it out of the way.
$parser expose namespace
$parser invokehidden rename namespace _%@namespace
$parser expose eval
$parser invokehidden rename eval _%@eval
# Install all the registered psuedo-command implementations
foreach cmd $initCommands {
eval $cmd
}
}
}
proc cleanup {} {
variable parser
interp delete $parser
unset parser
}
}
auto_mkindex_parser::mkindex –
Used by the “auto_mkindex” command to create a “tclIndex” file for
the given Tcl source file. Executes the commands in the file, and
handles things like the “proc” command by adding an entry for the
index file. Returns a string that represents the index file.
Arguments:
file Name of Tcl source file to be indexed.
proc auto_mkindex_parser::mkindex {file} {
variable parser
variable index
variable scriptFile
variable contextStack
variable imports
set scriptFile $file
set fid [open $file]
set contents [read $fid]
close $fid
# There is one problem with sourcing files into the safe
# interpreter: references like "$x" will fail since code is not
# really being executed and variables do not really exist.
# To avoid this, we replace all $ with \0 (literally, the null char)
# later, when getting proc names we will have to reverse this replacement,
# in case there were any $ in the proc name. This will cause a problem
# if somebody actually tries to have a \0 in their proc name. Too bad
# for them.
set contents [string map "$ \u0000" $contents]
set index ""
set contextStack ""
set imports ""
$parser eval $contents
foreach name $imports {
catch {$parser eval [list _%@namespace forget $name]}
}
return $index
}
auto_mkindex_parser::hook command
Registers a Tcl command to evaluate when initializing the
slave interpreter used by the mkindex parser.
The command is evaluated in the master interpreter, and can
use the variable auto_mkindex_parser::parser to get to the slave
proc auto_mkindex_parser::hook {cmd} {
variable initCommands
lappend initCommands $cmd
}
auto_mkindex_parser::slavehook command
Registers a Tcl command to evaluate when initializing the
slave interpreter used by the mkindex parser.
The command is evaluated in the slave interpreter.
proc auto_mkindex_parser::slavehook {cmd} {
variable initCommands
# The $parser variable is defined to be the name of the
# slave interpreter when this command is used later.
lappend initCommands "\$parser eval [list $cmd]"
}
auto_mkindex_parser::command –
Registers a new command with the “auto_mkindex_parser” interpreter
that parses Tcl files. These commands are fake versions of things
like the “proc” command. When you execute them, they simply write
out an entry to a “tclIndex” file for auto-loading.
This procedure allows extensions to register their own commands
with the auto_mkindex facility. For example, a package like
[incr Tcl] might register a “class” command so that class definitions
could be added to a “tclIndex” file for auto-loading.
Arguments:
name Name of command recognized in Tcl files.
arglist Argument list for command.
body Implementation of command to handle indexing.
proc auto_mkindex_parser::command {name arglist body} {
hook [list auto_mkindex_parser::commandInit $name $arglist $body]
}
auto_mkindex_parser::commandInit –
This does the actual work set up by auto_mkindex_parser::command
This is called when the interpreter used by the parser is created.
Arguments:
name Name of command recognized in Tcl files.
arglist Argument list for command.
body Implementation of command to handle indexing.
proc auto_mkindex_parser::commandInit {name arglist body} {
variable parser
set ns [namespace qualifiers $name]
set tail [namespace tail $name]
if {$ns eq ""} {
set fakeName [namespace current]::_%@fake_$tail
} else {
set fakeName [namespace current]::[string map {:: _} _%@fake_$name]
}
proc $fakeName $arglist $body
# YUK! Tcl won't let us alias fully qualified command names,
# so we can't handle names like "::itcl::class". Instead,
# we have to build procs with the fully qualified names, and
# have the procs point to the aliases.
if {[string match *::* $name]} {
set exportCmd [list _%@namespace export [namespace tail $name]]
$parser eval [list _%@namespace eval $ns $exportCmd]
# The following proc definition does not work if you
# want to tolerate space or something else diabolical
# in the procedure name, (i.e., space in $alias)
# The following does not work:
# "_%@eval {$alias} \$args"
# because $alias gets concat'ed to $args.
# The following does not work because $cmd is somehow undefined
# "set cmd {$alias} \; _%@eval {\$cmd} \$args"
# A gold star to someone that can make test
# autoMkindex-3.3 work properly
set alias [namespace tail $fakeName]
$parser invokehidden proc $name {args} "_%@eval {$alias} \$args"
$parser alias $alias $fakeName
} else {
$parser alias $name $fakeName
}
return
}
auto_mkindex_parser::fullname –
Used by commands like “proc” within the auto_mkindex parser.
Returns the qualified namespace name for the “name” argument.
If the “name” does not start with “::”, elements are added from
the current namespace stack to produce a qualified name. Then,
the name is examined to see whether or not it should really be
qualified. If the name has more than the leading “::”, it is
returned as a fully qualified name. Otherwise, it is returned
as a simple name. That way, the Tcl autoloader will recognize
it properly.
Arguments:
name - Name that is being added to index.
proc auto_mkindex_parser::fullname {name} {
variable contextStack
if {![string match ::* $name]} {
foreach ns $contextStack {
set name "${ns}::$name"
if {[string match ::* $name]} {
break
}
}
}
if {[namespace qualifiers $name] eq ""} {
set name [namespace tail $name]
} elseif {![string match ::* $name]} {
set name "::$name"
}
# Earlier, mkindex replaced all $'s with \0. Now, we have to reverse
# that replacement.
return [string map "\u0000 $" $name]
}
Register all of the procedures for the auto_mkindex parser that
will build the “tclIndex” file.
AUTO MKINDEX: proc name arglist body
Adds an entry to the auto index list for the given procedure name.
auto_mkindex_parser::command proc {name args} {
variable index
variable scriptFile
# Do some fancy reformatting on the “source” call to handle platform
# differences with respect to pathnames. Use format just so that the
# command is a little easier to read (otherwise it’d be full of
# backslashed dollar signs, etc.
append index [list set auto_index([fullname $name])]
[format { [list source [file join $dir %s]]}
[file split $scriptFile]] “\n”
}
Conditionally add support for Tcl byte code files. There are some
tricky details here. First, we need to get the tbcload library
initialized in the current interpreter. We cannot load tbcload into the
slave until we have done so because it needs access to the tcl_patchLevel
variable. Second, because the package index file may defer loading the
library until we invoke a command, we need to explicitly invoke auto_load
to force it to be loaded. This should be a noop if the package has
already been loaded
auto_mkindex_parser::hook {
if {![catch {package require tbcload}]} {
if {[namespace which -command tbcload::bcproc] eq “”} {
auto_load tbcload::bcproc
}
load {} tbcload $auto_mkindex_parser::parser
# AUTO MKINDEX: tbcload::bcproc name arglist body
# Adds an entry to the auto index list for the given pre-compiled
# procedure name.
auto_mkindex_parser::commandInit tbcload::bcproc {name args} {
variable index
variable scriptFile
# Do some nice reformatting of the "source" call, to get around
# path differences on different platforms. We use the format
# command just so that the code is a little easier to read.
append index [list set auto_index([fullname $name])] \
[format { [list source [file join $dir %s]]} \
[file split $scriptFile]] "\n"
}
}
}
AUTO MKINDEX: namespace eval name command ?arg arg…?
Adds the namespace name onto the context stack and evaluates the
associated body of commands.
AUTO MKINDEX: namespace import ?-force? pattern ?pattern…?
Performs the “import” action in the parser interpreter. This is
important for any commands contained in a namespace that affect
the index. For example, a script may say “itcl::class …”,
or it may import “itcl::*” and then say “class …”. This
procedure does the import operation, but keeps track of imported
patterns so we can remove the imports later.
auto_mkindex_parser::command namespace {op args} {
switch – $op {
eval {
variable parser
variable contextStack
set name [lindex $args 0]
set args [lrange $args 1 end]
set contextStack [linsert $contextStack 0 $name]
$parser eval [list _%@namespace eval $name] $args
set contextStack [lrange $contextStack 1 end]
}
import {
variable parser
variable imports
foreach pattern $args {
if {$pattern ne "-force"} {
lappend imports $pattern
}
}
catch {$parser eval "_%@namespace import $args"}
}
}
}
return