archie/tcl-dp/teki.tcl
2024-05-27 16:13:40 +02:00

2241 lines
69 KiB
Tcl

#!/bin/sh
# \
exec wish "$0" ${1+"$@"}
if {$tcl_version < 7.6} {
puts stderr "requires Tcl 7.6 or later"
exit
}
# teki.tcl --
#
# The Tcl Extension Kit/Installer. This file manages packages for
# Tcl/Tk
# Notes about the organization of this file:
# Major sections are delimited by a string of "-" characters -- e.g. "-------"
# At the beginning of each major section, the purpose and routines in that
# section are described.
# teki-specific packages reside in the "tekilib" directory below teki.tcl
#set tk_version 7.6
set home [string trimright [file dirname [info script]] ./]
set home [file join [pwd] $home tekilib]
lappend auto_path $home
set TekiInfo(library) $home
unset home
if [catch {package require http}] {
set TekiInfo(http) 0
} else {
set TekiInfo(http) 1
}
package require Debug
package require Undo
# Define some useful fonts
if [info exists tk_version] {
set TekiInfo(gui) 1
set majorversion [lindex [split $tk_version .] 0]
if {$majorversion >= 8} {
set TekiInfo(varfont) [font create -family Times -weight bold -size 18]
set TekiInfo(fixedfont) [font create -family Courier -size 10]
} else {
set TekiInfo(varfont) *-Times-Bold-R-Normal--*-240-*-*-*-*-*-*
set TekiInfo(fixedfont) -*-Courier-Medium-R-Normal--*-120-*-*-*-*-*-*
}
unset majorversion
} else {
set TekiInfo(gui) 0
}
if $TekiInfo(gui) {
package require Progress-Tk
package require Wise
} else {
package require Progress-Tcl
}
# -----------------------------------------------------------------------
#
# Error handling, general UI commands.
#
# This section contains code to handle errors and print messages.
# It defines basic functions whose interface can be a GUI (if we're
# running in wish) or a text based interface (if we're running in
# a shell)
# TekiError prints an error message
# TekiWarning is called to ask the user for confirmation or a decision
# (such as deleting a file or set of files)
if $TekiInfo(gui) {
proc TekiError {msg} {
bgerror "$msg"
}
proc TekiWarning {msg default options} {
Wise_Message "Warning" $msg $options 0
}
} else {
proc TekiError {msg} {
puts stderr $msg
}
proc TekiWarning {msg default options} {
global x
puts stderr "Warning: $msg"
set len [llength $options]
set done 0
while {!$done} {
for {set i 0} {$i < $len} {incr i} {
puts stderr "Type $i to [lindex $options $i]"
}
set x [string trim [gets stdin]]
if [string match {[0-9]} "$x"] {
set done [expr ($x >=0) && ($x < $len)]
}
}
return $x
}
}
# -----------------------------------------------------------------------
#
# User Interface
#
# This section of code creates and manages the user interface. The UI
# consists of a main window with two parts (top and bottom), plus a menu
# bar. The top contains the readme file of the currently selected package,
# the bottom a list of packages installed on the system. The
# functions TekiCreateMenus and TekiCreateWindow create this interface.
# The function TekiCreateUI creates the menus and main window (by
# calling TekiCreateMenus and TekiCreateWindow).
#
# The function TekiUpdateReadme redraws the top portion, and should
# be called whenever the current package -- TekiInfo(currPackage) -- is changed.
# The function TekiUpdateBrowser redraws the bottom, and should be
# called whenever tclPkgInfo(installed) changes. The function
# TekiBrowserCallback is called when the user clicks in the bottom
# area, selecting a new package. The functions TekiDoFileInstall,
# TekiDoFileUninstall, and TekiDoFileExit, are called when the user
# selects File->Install, File->Uninstall, or File->Exit menu items,
# respectively. DoFileInstall pops up a file selection dialog,
# then verifies and installs the file.
set TekiInfo(currPackage) tcl[info tclversion]
#
# TekiCreateMenus
# Create the menu bar across the top of the window
#
# Arguments:
# None
#
proc TekiCreateMenus {} {
global tclPkgInfo TekiInfo
Debug_Enter TekiCreateMenus
frame .menu -relief raised -bd 2
pack .menu -side top -fill x
# File Menu
set m .menu.file.m
menubutton .menu.file -text File -menu $m -underline 0
pack .menu.file -side left
menu $m
$m add command -label "Install" -underline 0 -command TekiDoFileInstall
$m add command -label "Uninstall" -underline 1 -command TekiDoFileUninstall
$m add separator
$m add command -label "Exit" -underline 1 -command TekiDoFileExit
# WWW Menu
set m .menu.www.m
menubutton .menu.www -text WWW -menu $m -underline 0
pack .menu.www -side left
menu $m
$m add command -label "Update" -underline 0 -command TekiDoWWWUpdate
$m add command -label "Browse" -underline 1 -command TekiDoWWWBrowse
if {$TekiInfo(http) == 0} {
.menu.www.m entryconfigure Browse -state disabled
.menu.www.m entryconfigure Update -state disabled
}
Debug_Leave
}
#
# TekiCreateWindow
# Create the main window -- this window lists the installed packages, and has a
# README section.
#
# Arguments:
# none
#
proc TekiCreateWindow {} {
global tclPkgInfo TekiInfo
Debug_Enter TekiCreateWindow
set w .browser
frame $w
pack $w -side bottom -fill x -expand no
scrollbar $w.scroll -command "$w.list yview"
listbox $w.list -font $TekiInfo(fixedfont) -relief sunken -width 100 \
-bd 2 -yscrollcommand "$w.scroll set" -setgrid 1 -height 12 \
-selectmode extended
pack $w.scroll -side right -fill y
pack $w.list -expand no -fill both
set TekiInfo(Browser) $w.list
TekiUpdateBrowser
# Create the README section of the frame
set w .readme
frame $w
pack $w -side top -fill both -expand yes
scrollbar $w.scroll -command "$w.text yview"
text $w.text -font $TekiInfo(fixedfont) -relief sunken -width 100 \
-bd 2 -yscrollcommand "$w.scroll set" -setgrid 1 -height 10
set TekiInfo(Readme) $w.text
pack $w.scroll -side right -fill y
pack $w.text -expand yes -fill both
TekiUpdateReadme
update
bind $TekiInfo(Browser) <Button-1> TekiBrowserCallback
bindtags $TekiInfo(Browser) [list Listbox all $TekiInfo(Browser)]
Debug_Leave
}
#
# TekiUpdateReadme -
# Updates the readme window of the Info window.
# Called whenever the current package changes.
# The associated widget is a text widget.
#
proc TekiUpdateReadme {} {
global tclPkgInfo TekiInfo
Debug_Enter TekiUpdateReadme
set w $TekiInfo(Readme)
set currPkg [lindex $TekiInfo(currPackage) 0]
if {[string length $currPkg] == 0} {
return
}
set filename $tclPkgInfo($currPkg,infoFile)
set name $tclPkgInfo($currPkg,name)
$w delete 1.0 end
$w insert end "Information on the $currPkg package\n\n"
set m1 .menu.file.m
set m2 .menu.www.m
if {($name == "Tcl") || ($name == "Tk")} {
$m1 entryconfigure Uninstall -state disabled
$m2 entryconfigure Update -state disabled
} else {
$m1 entryconfigure Uninstall -state normal
if $TekiInfo(http) {
$m2 entryconfigure Update -state normal
}
}
if {[string length $filename] &&
[file exists $filename] &&
[file readable $filename]} {
set f [open $filename r]
$w insert end [read $f]
close $f
} else {
$w insert end "No information available"
}
wm title . "TEKI -- $currPkg"
Debug_Leave
}
#
# TekiUpdateBrowser -
# Updates the browser window.
# Called whenever the list of installed packages changes.
#
proc TekiUpdateBrowser {} {
global tclPkgInfo TekiInfo
Debug_Enter TekiUpdateBrowser
set w $TekiInfo(Browser)
$w delete 0 end
$w insert end "Packages installed:"
$w insert end [format "%-11s%-8s %-15s%-28s" "package" "version" "requires" "description"]
if {[winfo depth $w] > 1} {
set hot "-background #808080 -relief raised -borderwidth 1"
set normal "-background {} -relief flat"
} else {
set hot "-foreground white -background black"
set normal "-foreground {} -background {}"
}
set tclPkgInfo(installed) [lsort $tclPkgInfo(installed)]
foreach pkg $tclPkgInfo(installed) {
set des $tclPkgInfo($pkg,description)
set ver $tclPkgInfo($pkg,version)
set req $tclPkgInfo($pkg,requires)
# The following code does one of two things, depending
# on whether the package is part of another package.
set split [file split $pkg]
set lsplit [llength $split]
$w insert end [format "%-15s%-8s%-16s%-28s" $pkg $ver $req $des]
}
Debug_Leave
}
#
# TekiBrowserCallback --
# This procedure is called when the user clicks on a package in the browser window.
# It is responsible for updating the UI to show details of the selected package
#
# Arguments:
# index The index of the character that the user clicked on.
#
proc TekiBrowserCallback {} {
global tclPkgInfo TekiInfo
Debug_Enter "TekiBrowserCallback"
set w $TekiInfo(Browser)
set packages {}
foreach i [$w curselection] {
if {$i < 2} {
# The first two elements are the labels, so don't allow the
# user to select these
$w selection clear $i
} else {
set info [$w get $i]
set pkg [lindex $info 0]
lappend packages $pkg
}
}
set TekiInfo(currPackage) $packages
TekiUpdateReadme
Debug_Leave
}
#
# TekiCreateUI
# Create the TEKI User Interface in the main window (".")
# 1. Create the menus
# 2. Create & show the browse windows
#
proc TekiCreateUI {} {
global tclPkgInfo TekiInfo
Debug_Enter TekiCreateUI
TekiCreateMenus
TekiCreateWindow
set currPkg $TekiInfo(currPackage)
wm title . "TEKI -- $currPkg"
wm iconname . "TEKI"
Debug_Leave
}
#-----------------------------------------------------------------------
# WISE user interface support
# The following procedures control the WISE user interface (defined in the
# Wise package). The wizards are called by the Teki*Wizard procedures.
#
# The function TekiInteractiveInstall coordinates everything -- it
# reads/verifies the Teki file, calls the wizards to select various
# parameters, downloads any files that are needed, and calls TekiInstall
#
# The functions TekiNormalizeDirName
# Bring the name of a directory into "canonical" form. What a pain!
proc TekiNormalizeDirName {d} {
Debug_Enter TekiNormalizeDirName
set cwd [pwd]
switch [file pathtype $d] {
absolute -
volumerelative {
set path {}
set len 0
}
relative {
set path [file split $cwd]
set len [llength $path]
}
}
foreach c [file split $d] {
switch $c {
{.} {}
{..} {
incr len -1;
set path [lrange $path 0 [expr $len-1]]
}
default {
lappend path $c
incr len
}
}
}
Debug_Leave
return [eval file join $path]
}
# Try to make the directory writable
proc TekiMakeWritable {dir} {
global tcl_platform tcl_version
set majorversion [lindex [split $tcl_version .] 0]
switch $tcl_platform(platform) {
unix {
if {$majorversion >= 8} {
file attributes $dir -permissions 755
} else {
exec chmod a+wx $dir
}
}
mac* -
windows {
if {$majorversion >= 8} {
file attributes $dir -readonly 0
} else {
error "Permission denied"
}
}
}
}
# Make sure the directory exists, is writable, and is a directory.
# If $dir exists and is a file, flag an error
# If $dir doesn't exist, ask the user if they want to create it
# if they do, then try to create it, otherwise flag an error
# If the return value is 0, then the directory can't be used
# for installation. If the return value is 1, then we can proceed
proc TekiVerifyDir {dir} {
#
# Make the directory if needed
#
if {[file exists $dir]} {
if {![file isdirectory $dir]} {
TekiError "Error: $dir exists and is not a directory"
return 0
}
} else {
# Doesn't exist. Make it
set rv [TekiWarning "Directory $dir does not exist, create?" 0 {Ok Cancel}]
if {$rv == 0} {
if [catch {file mkdir $dir} err] {
TekiError "Error creating $dir\n $err"
return 0
}
} else {
# Cancelled by user
return 0
}
}
# If we get to here, $dir exists and is a directory
# Try to make it writable if it's not
if {![file writable $dir]} {
if [catch {TekiMakeWritable $dir} err] {
TekiError "Error: $dir is not writable\n$err"
return 0
}
}
return 1
}
# Utility to delete an item from a list
proc TekiListDelete {list item} {
set index [lsearch $list $item]
if {$index != -1} {
set list [concat [lrange $list 0 [expr $index-1]] \
[lrange $list [expr $index+1] end]]
}
return $list
}
# The (Teki*Wizard) procedures encode the logic of the wizards.
# Each procedure corresponds to one wizard. The information gathered
# through the wizards is used to set elements in the global array TekiInfo()
#
# The entry point to the machine is the TekiTypeWizard (abbreviated
# "type"). At any time, pressing the cancel button aborts the wizards,
# pressing "finish" goes directly to "verify", pressing "back" goes back
# to the previous state, and, generally, pressing "next" goes on to
# the next state. The only exception is in the initial wizard where
# the user selects between "custom" and "typical" installation. If
# the user selects "typical" and selects "next", the installation
# parameters are reset to their defaults and the wizard goes directly
# to the verify state.
#
# To encode this logic, a sequence of procedure calls are executed, one
# for each wizard. Each wizard calls the next wizard directly. To
# implement the "back" button, the procedure just returns. To implement
# the "cancel" and "finish" buttons, unusual return codes
# ($TekiInfo(cancelCode) for cancel, $TekiInfo(finishCode) for finish)
# are returned by the wizard procedures and caught at the entry point
# of the first wizard.
#
# The wizards are:
# type - get installation type (custom or typical)
# packages - let the user select which packages to install (only used
# if there's more than one package available)
# archList - get the list of architectures to install for
# extras - ask whether to install examples, docs, and data
# codeDir - get target directory for code files
# docDir - get target directory for documentation (only if installing
# documentation)
# verify - verify all paramters
set TekiInfo(cancelCode) 5
set TekiInfo(finishCode) 6
# Reset the TekiInfo variables that corespond to the wizard parameters
# to their default values.
proc TekiWizardDefaults {} {
global TekiInfo newPackage
# reset to defaults
set TekiInfo(packages) $newPackage(defaultPackages)
set TekiInfo(archList) $newPackage(defaultArch)
if {$TekiInfo(archList) == "all"} {
set TekiInfo(archList) {}
foreach sys $TekiInfo(systemNames) {
lappend TekiInfo(archList) [lindex $sys 0]
}
}
set TekiInfo(extras) {}
if $newPackage(defaultInstallDoc) {
lappend TekiInfo(extras) doc
}
if $newPackage(defaultInstallData) {
lappend TekiInfo(extras) data
}
if $newPackage(defaultInstallExamples) {
lappend TekiInfo(extras) examples
}
set TekiInfo(codeDir) [info library]
set x [file dirname [file dirname $TekiInfo(codeDir)]]
set TekiInfo(docDir) [file join $x doc]
}
proc TekiTypeWizard {} {
global TekiInfo
# Ask if typical or custom install
while (1) {
set rv [Wise_Radiolist "Select installation type" \
{{typical typical 1} {custom custom 0}} 1]
switch $rv {
{next typical} -
{finish typical} {
TekiWizardDefaults
TekiVerifyWizard
}
{next custom} {
TekiPackagesWizard
}
{finish custom} {
TekiVerifyWizard
}
cancel {
return -code $TekiInfo(cancelCode)
}
default {
TekiError "Internal error:\n $rv\ndoesn't match case in switch"
}
}
}
}
proc TekiPackagesWizard {} {
global TekiInfo newPackage
set packages $newPackage(available)
#
# If there's only one package, just pretend they hit "next"
#
if {[llength $packages] != 1} {
while (1) {
set choices {}
foreach pkg $packages {
if {[lsearch $TekiInfo(packages) $pkg] == -1} {
lappend choices [list $newPackage($pkg,name) $pkg 0]
} else {
lappend choices [list $newPackage($pkg,name) $pkg 1]
}
}
set rv [Wise_Checklist "Select packages to install" $choices]
switch [lindex $rv 0] {
next {
set TekiInfo(packages) [lrange $rv 1 end]
TekiArchListWizard
}
back {
return
}
finish {
set TekiInfo(packages) [lrange $rv 1 end]
TekiVerifyWizard
}
cancel {
return -code $TekiInfo(cancelCode)
}
}
}
} else {
set TekiInfo(packages) $packages
TekiArchListWizard
}
}
proc TekiArchListWizard {} {
global TekiInfo newPackage
set objFileCount 0
foreach pkg $TekiInfo(packages) {
incr objFileCount [llength $newPackage($pkg,objFiles)]
}
if {$objFileCount == 0} {
TekiExtrasWizard
return
}
while (1) {
#
# We need to rebuild the choices list each time through the
# loop so we can retain the users setting when they go on
# to the next wizard and then hit the "back" button. If
# we don't rebuild it, then the wizard contents might
# be out of sync with TekiInfo(archList), since this is
# changed on next/finish
#
set choices {}
foreach pkg $TekiInfo(packages) {
foreach pair $newPackage($pkg,objFiles) {
set name [lindex $pair 0]
if {[lsearch $choices [list $name]*] == -1} {
if {[lsearch $TekiInfo(archList) $name] == -1} {
lappend choices [list $name $name 0]
} else {
lappend choices [list $name $name 1]
}
}
}
}
set rv [Wise_Checklist "Select architectures to install" $choices]
switch [lindex $rv 0] {
next {
set TekiInfo(archList) [lrange $rv 1 end]
TekiExtrasWizard
}
back {
return
}
finish {
set TekiInfo(archList) [lrange $rv 1 end]
TekiVerifyWizard
}
cancel {
return -code $TekiInfo(cancelCode)
}
}
}
}
proc TekiExtrasWizard {} {
global TekiInfo newPackage
#
# Figure out if there are any extras to install! If not, skip
# this step.
#
set docFileCount 0
set dataFileCount 0
set exampleFileCount 0
foreach pkg $TekiInfo(packages) {
incr docFileCount [llength $newPackage($pkg,docFiles)]
incr dataFileCount [llength $newPackage($pkg,dataFiles)]
incr exampleFileCount [llength $newPackage($pkg,exampleFiles)]
}
if {$docFileCount == 0} {
set TekiInfo(extras) [TekiListDelete $TekiInfo(extras) doc]
}
if {$dataFileCount == 0} {
set TekiInfo(extras) [TekiListDelete $TekiInfo(extras) data]
}
if {$exampleFileCount == 0} {
set TekiInfo(extras) [TekiListDelete $TekiInfo(extras) example]
}
if {($docFileCount + $dataFileCount + $exampleFileCount) == 0} {
TekiCodeDirWizard
return
}
while (1) {
set choices {}
if $docFileCount {
lappend choices [list "Install documentation" doc \
[expr [lsearch $TekiInfo(extras) doc] != -1]]
}
if $dataFileCount {
lappend choices [list "Install data files" data \
[expr [lsearch $TekiInfo(extras) data] != -1]]
}
if $exampleFileCount {
lappend choices [list "Install examples" examples \
[expr [lsearch $TekiInfo(extras) examples] != -1]]
}
set rv [Wise_Checklist "Select Extras to install" $choices]
switch [lindex $rv 0] {
next {
set TekiInfo(extras) [lrange $rv 1 end]
TekiCodeDirWizard
}
back {
return
}
finish {
set TekiInfo(extras) [lrange $rv 1 end]
TekiVerifyWizard
}
cancel {
return -code $TekiInfo(cancelCode)
}
}
}
}
proc TekiCodeDirWizard {} {
global TekiInfo auto_path env tcl_pkgPath
while (1) {
# Give the user several standard options of where to install
# the code:
# option #1: the current value of the codeDir
# option #2: [info library]
# option #3: the TCLLIBPATH environment variable
# option #4: the parent of [info library]
# option #5: all the elements of tclPkgPath
# option #6: all the elements of auto_path
# option #7: "other"
# The logic makes sure each choice only appears once.
set choices {}
set dir [TekiNormalizeDirName $TekiInfo(codeDir)]
lappend choices [list $dir $dir 1]
set dir [TekiNormalizeDirName [info library]]
if {[lsearch $choices [list $dir $dir ?]] == -1} {
lappend choices [list $dir $dir 0]
}
if [info exists env(TCLLIBPATH)] {
set dir [TekiNormalizeDirName $env(TCLLIBPATH)]
if {[lsearch $choices [list $dir $dir ?]] == -1} {
lappend choices [list $dir $dir 0]
}
}
set dir [TekiNormalizeDirName [info library]/..]
if {[lsearch $choices [list $dir $dir ?]] == -1} {
lappend choices [list $dir $dir 0]
}
foreach dir $tcl_pkgPath {
set dir [TekiNormalizeDirName $dir]
if {[lsearch $choices [list $dir $dir ?]] == -1} {
lappend choices [list $dir $dir 0]
}
}
foreach dir $auto_path {
set dir [TekiNormalizeDirName $dir]
if {[lsearch $choices [list $dir $dir ?]] == -1} {
lappend choices [list $dir $dir 0]
}
}
lappend choices [list other other 0]
set rv [Wise_Radiolist "Select root directory for code files" $choices]
#
# If they choose other, let them chose a directory.
# After we have the candidate directory, make sure it
# exists and is writable.
#
set option [lindex $rv 0]
set rv [lrange $rv 1 end]
switch $option {
next -
finish {
if {$rv == "other"} {
set dir [Wise_GetDirName $dir]
if {$dir == ""} {
continue; # try again....
}
} else {
set dir $rv
}
#
# Warn them if they're installing in a non-standard place
#
set found 0
foreach d $auto_path {
set nd [TekiNormalizeDirName $d]
if [string match $nd $dir] {
set found 1
break
}
}
if {!$found} {
foreach d $tcl_pkgPath {
set nd [TekiNormalizeDirName $d]
if [string match $nd $dir] {
set found 1
break
}
}
}
if {!$found} {
TekiWarning "Warning: you are installing in a non-standard location
Be sure to add $dir to your TCLLIBPATH environment variable" 0 Ok
}
if {![TekiVerifyDir $dir]} {
continue
}
set TekiInfo(codeDir) $dir
if {$option == "next"} {
TekiDocDirWizard
} else {
TekiVerifyWizard
}
}
back {
return
}
cancel {
return -code $TekiInfo(cancelCode)
}
}
}
}
proc TekiDocDirWizard {} {
global TekiInfo
if {[lsearch $TekiInfo(extras) doc] == -1} {
TekiVerifyWizard
return
}
while (1) {
# Give the user several standard options of where to install
# the documentation:
# option #1: the current value of the docDir
# option #2: [info library]/../../doc
# option #3: $codeDir/doc
# option #4: "other"
# The logic makes sure each choice only appears once.
set choices {}
set dir [TekiNormalizeDirName $TekiInfo(docDir)]
lappend choices [list $dir $dir 1]
set dir [TekiNormalizeDirName [info library]/../../doc]
if {[lsearch $choices [list $dir $dir ?]] == -1} {
lappend choices [list $dir $dir 0]
}
set dir [TekiNormalizeDirName $TekiInfo(codeDir)/doc]
if {[lsearch $choices [list $dir $dir ?]] == -1} {
lappend choices [list $dir $dir 0]
}
lappend choices [list other other 0]
set rv [Wise_Radiolist "Select root directory for documentation files" $choices]
set option [lindex $rv 0]
set rv [lrange $rv 1 end]
switch $option {
next -
finish {
if {$rv == "other"} {
set dir [Wise_GetDirName $dir]
if {$dir == ""} {
continue; # try again....
}
} else {
set dir $rv
}
if {![TekiVerifyDir $dir]} {
continue
}
set TekiInfo(docDir) $dir
TekiVerifyWizard
}
back {
return
}
cancel {
return -code $TekiInfo(cancelCode)
}
}
}
}
proc TekiVerifyWizard {} {
global TekiInfo newPackage
set installDoc [expr [lsearch $TekiInfo(extras) doc] != -1]
set installData [expr [lsearch $TekiInfo(extras) data] != -1]
set installExamples [expr [lsearch $TekiInfo(extras) examples] != -1]
set msg "Install these packages:\n"
foreach pkg $TekiInfo(packages) {
set msg [format "%s %s %s\n" $msg \
$newPackage($pkg,name) \
$newPackage($pkg,version)]
}
set msg [format "%s\n...for these architectures:\n" $msg]
foreach arch $TekiInfo(archList) {
set msg [format "%s %s\n" $msg $arch]
}
set msg [format "%s\nInstall code in \n %s\n" $msg $TekiInfo(codeDir)]
if {$installDoc} {
set msg [format "%s\nInstall documentation in \n %s\n" \
$msg $TekiInfo(docDir)]
}
if {$installExamples} {
set msg [format "%s\nInstall examples in \n %s\n" \
$msg $TekiInfo(codeDir)]
}
if {$installData} {
set msg [format "%s\nInstall data files in \n %s\n" \
$msg $TekiInfo(codeDir)]
}
set rv [TekiWarning $msg 0 {Finish {< Back} Cancel}]
if {$rv == 0} {
return -code $TekiInfo(finishCode)
}
if {$rv == 2} {
return -code $TekiInfo(cancelCode)
}
}
#
# Display copyrights for all selected packages.
# Returns a list of packages for which the user accepted the copyright
#
proc TekiDisplayCopyrights {pkgList} {
global tclPkgInfo TekiInfo newPackage auto_path env
Debug_Enter TekiDisplayCopyrights
set packages {}
set accept 0
set dontaccept 1
set acceptrest 2
set abort 3
set code 0
foreach {pkg fn} $pkgList {
if {($code != $acceptrest) &&
[file exists $fn]} {
set f [open $fn r]
set msg [read $f]
close $f
set name $newPackage($pkg,name)
set code [Wise_Message "Copyright for $name" \
$msg {Accept {Do not Accept} {Accept Rest} Abort}]
if {$code == $accept} {
lappend packages $pkg
}
if {$code == $acceptrest} {
lappend packages $pkg
}
if {$code == $dontaccept} {
TekiWarning "Skipping installation of $name" 0 {Ok}
}
if {$code == $abort} {
TekiWarning "Aborting installation" 0 {Ok}
return {}
}
} else {
lappend packages $pkg
}
}
Debug_Leave
return $packages
}
# The following procedure is called by DoFileInstall and
# DoWWWBrowse to
# 1. Read/verify the teki file
# 2. Gather the installation parameters
# 3. Fetch & display copyright notices
# 4. Install the extension
#
# It does a web based install if $web is true, a file system
# based install if $web if false
proc TekiInteractiveInstall {tekiFile web} {
global tclPkgInfo TekiInfo newPackage auto_path env
Debug_Enter TekiInteractiveInstall
# 1. Read and verify it
catch {unset newPackage}
set code [catch {TekiReadFile $tekiFile} err]
if $code {
TekiError "Error reading $tekiFile\n $err\nerrorCode = $code"
catch {unset newPackage}
return
}
set code [catch {TekiVerifyFile} err]
if $code {
TekiError "Invalid Teki file $tekiFile:\n$err"
catch {unset newPackage}
return
}
# 2. Collect all the information from the user.
TekiWizardDefaults
set code [catch TekiTypeWizard err]
if {$code == $TekiInfo(cancelCode)} {
return
}
if {$code != $TekiInfo(finishCode)} {
TekiError $err
return
}
# 3. Fetch/display all the copyright notices. If web based install,
# download the copyright files. The wwwCleanup variable is a list
# of all the files in tmpdir that we have to delete when we're done.
set fileList {}
set nocopyright {}
set tmpdir $TekiInfo(tmpDir)
set index 0
if $web {
foreach pkg $TekiInfo(packages) {
set inFile $newPackage($pkg,copyright)
set srcURL $newPackage($pkg,srcURL)
set outFile [file join $tmpdir copyright$index]
if [string length $inFile] {
if [catch {TekiDownloadFile $srcURL/$inFile $outFile} err] {
TekiWarning "Couldn't find copyright file\n$srcURL/$inFile\n$err\n\nskipping package" 0 {Ok}
} else {
lappend fileList $pkg $outFile
}
} else {
lappend nocopyright $pkg
}
}
} else {
foreach pkg $TekiInfo(packages) {
set srcDir $newPackage($pkg,srcDir)
set copyright $newPackage($pkg,copyright)
set inFile [file join [file dirname $tekiFile] $srcDir $copyright]
if [string length $copyright] {
if {![file exists $inFile]} {
TekiWarning "Couldn't find copyright file\n$inFile\n\nskipping package" 0 {Ok}
} else {
lappend fileList $pkg $inFile
}
} else {
lappend nocopyright $pkg
}
}
}
set TekiInfo(packages) [concat $nocopyright [TekiDisplayCopyrights $fileList]]
if $web {
foreach file $fileList {
catch {file delete -force $file}
}
}
if {$TekiInfo(packages) == ""} {
Debug_Leave
return;
}
# 4. Install it!
set installDoc [expr [lsearch $TekiInfo(extras) doc] != -1]
set installData [expr [lsearch $TekiInfo(extras) data] != -1]
set installExamples [expr [lsearch $TekiInfo(extras) examples] != -1]
TekiInstall [file dirname $tekiFile] $web \
$TekiInfo(codeDir) $TekiInfo(docDir) \
$TekiInfo(packages) $TekiInfo(archList) \
$installData $installDoc $installExamples
TekiGetAllPkgInfo
TekiUpdateBrowser
catch {unset newPackage}
Debug_Leave
}
#-----------------------------------------------------------------------
#
# File menu callbacks --
#
# TekiDoFileInstall is called when the user select the Install item in the
# File menu
# TekiDoFileUninstall is called when the user select the Uninstall item in the
# File menu
# TekiDoFileExit is called when the user select the Exit item in the File menu
#
# TekiDoFileInstall -- called from the "File->Install" menu item.
#
proc TekiDoFileInstall {} {
global tclPkgInfo TekiInfo newPackage auto_path env
Debug_Enter TekiDoFileInstall
catch {unset newPackage}
# Get the teki install file
set file [tk_getOpenFile -title "Open TEKI file" -filetypes {{{Tek Files} .tek} {{All Files} *}}]
if {$file == ""} {
return;
}
TekiInteractiveInstall $file 0
Debug_Leave
}
#
# TekiDoFileUninstall {} {
#
# Uninstall the current package, update UI
#
proc TekiDoFileUninstall {} {
global tclPkgInfo tcl_library TekiInfo
Debug_Enter TekiDoFileUninstall
set packages {}
foreach pkg $TekiInfo(currPackage) {
set name $tclPkgInfo($pkg,name)
if {($name == "Tcl") || ($name == "Tk")} {
tk_dialog .error "Error" "Can't uninstall $pkg" {} 0 Ok
continue
}
lappend packages $pkg
}
TekiUninstall $packages
TekiGetAllPkgInfo
TekiUpdateBrowser
set w $TekiInfo(Browser)
$w selection set end
set info [$w get end]
set TekiInfo(currPackage) [lindex $info 0]
TekiUpdateReadme
Debug_Leave
}
proc TekiDoFileExit {} {
Debug_Enter TekiDoFileExit
exit
}
#-----------------------------------------------------------------------
#
# WWW menu callbacks --
#
#
# Update the currently selected package.
# 1. Fetching the .tek file using the package's the URL field
# 2. Report the current version number and the new version number.
# Ask the user if they want to proceed.
# 3. If so, copy all the package files into a temporary directory
# and install from there
#
proc TekiDoWWWUpdate {} {
global tclPkgInfo TekiInfo
Debug_Enter TekiDoWWWUpdate
Debug_Leave
}
proc TekiWWWBrowseInstall {} {
global TekiInfo
set tmpDir $TekiInfo(tmpDir)
# Gather files for a batch install, and generate/use a bundle file
set filelist {}
set pkgList {}
set index 0
foreach i [.toc.list curselection] {
if {$i == 0} {
# skip labels :-)
continue
}
# The first element of the selection is the package number,
# which is the index in TekiInfo(contents). Retrieve it so
# we know which package the user selected.
set info [.toc.list get $i]
set index [lindex $info 0]
incr index -1
set tuple [lindex $TekiInfo(contents) $index]
set name [lindex $tuple 0]
set inFile [lindex $tuple 5]
set pkg [lindex $tuple 6]
set tmpDir $TekiInfo(tmpDir)
set outFile [file join $tmpDir tekiTmp$index]
if [catch {TekiDownloadFile $inFile $outFile} err] {
set rv [TekiWarning "Couldn't install package $name\nError occured while downloading\n$inFile\n$err" 0 {Ok Cancel}]
if {$rv == 0} {
foreach f $filelist {
file delete -force $f
}
return
}
} else {
lappend filelist $outFile
lappend pkgList $pkg
incr index
}
}
if {[llength $filelist] == 0} {
TekiWarning "No valid packages found to install" 0 {Ok}
return
}
set bundleFile [file join $tmpDir tekiBundle.tek]
set f [open $bundleFile w]
puts $f {# TekiFile 1.0
# Automatically generated TEKI bundle file
}
foreach file $filelist {
puts $f "TekiReadFile $file"
}
puts $f "set newPackage(defaultPackages) [list $pkgList]"
close $f
TekiInteractiveInstall $bundleFile 1
file delete -force $bundleFile
foreach f $filelist {
file delete -force $f
}
}
proc TekiWWWBrowseReadme {} {
global TekiInfo
set packages {}
set tmpDir $TekiInfo(tmpDir)
foreach i [.toc.list curselection] {
if {$i == 0} {
# skip labels :-)
continue
}
# The first element of the selection is the package number,
# which is the index in TekiInfo(contents). Retrieve it so
# we know which package the user selected.
set info [.toc.list get $i]
set index [lindex $info 0]
incr index -1
set tuple [lindex $TekiInfo(contents) $index]
set name [lindex $tuple 0]
set ver [lindex $tuple 1]
set url [lindex $tuple 4]
if {[string length $url] == 0} {
set rv [TekiWarning "No information available on $name $ver" 0 {Ok Cancel}]
if {$rv == 1} {
return
}
continue
}
set outFile [file join $tmpDir readme]
if [catch {TekiDownloadFile $url $outFile} err] {
set rv [TekiWarning "Coulnd't find readme for $name $ver:\n $err" 0 {Ok Cancel}]
if {$rv == 1} {
return
}
continue
}
set f [open $outFile r]
set msg [read $f]
close $f
file delete -force $outFile
set code [Wise_Message "Information for $name" $msg {Ok Cancel}]
if {$code == 1} {
return
}
}
}
proc TekiWWWBrowseAll {} {
global TekiInfo
.toc.list delete 0 end
set str [format "%-4s%-11s%-8s %-15s%-28s" "#" "package" "version" "requires" "description"]
.toc.list insert end $str
set i 1
foreach tuple $TekiInfo(contents) {
set pkg [lindex $tuple 0]
set ver [lindex $tuple 1]
set req [lindex $tuple 2]
set des [lindex $tuple 3]
.toc.list insert end [format "%-4d%-15s%-8s%-16s%-28s" $i $pkg $ver $req $des]
incr i
}
}
proc TekiWWWBrowseCompat {} {
global tcl_version tk_version TekiInfo
.toc.list delete 0 end
set str [format "%-4s%-11s%-8s %-15s%-28s" "#" "package" "version" "requires" "description"]
.toc.list insert end $str
set i 1
foreach tuple $TekiInfo(contents) {
set sat 1
foreach pair [lindex $tuple 2] {
set pkg [lindex $pair 0]
set ver [lindex $pair 1]
Debug_Print $pkg $ver
if {($pkg == "Tcl") && ![package vsatisfies $tcl_version $ver]} {
set sat 0
}
if {($pkg == "Tk") && ![package vsatisfies $tk_version $ver]} {
set sat 0
}
}
if $sat {
set pkg [lindex $tuple 0]
set ver [lindex $tuple 1]
set req [lindex $tuple 2]
set des [lindex $tuple 3]
.toc.list insert end [format "%-4d%-15s%-8s%-16s%-28s" $i $pkg $ver $req $des]
}
incr i
}
}
proc TekiDoWWWBrowse {} {
global TekiInfo
set url $TekiInfo(browseURL)
set tmpDir $TekiInfo(tmpDir)
set outFile [file join $tmpDir contents.tkr]
if [catch {TekiDownloadFile $url $outFile} err] {
TekiWarning "Couldn't find browse information file\n$err" 0 {Ok}
return
}
TekiReadTOCFile $outFile
file delete -force $outFile
# Ok, TekiInfo(contents) should contain a list of
# name version requires descript readme-url tek-file-url
if {[info commands .toc] == ""} {
toplevel .toc
set w .toc
set numButtons 5
button $w.install -text Install -command TekiWWWBrowseInstall
button $w.readme -text "View Readme" -command TekiWWWBrowseReadme
button $w.showall -text "Show All Packages" -command TekiWWWBrowseAll
button $w.showcompat -text "Show Compatible Packages" -command TekiWWWBrowseCompat
button $w.cancel -text "Close" -command "wm withdraw .toc"
scrollbar $w.scroll -command "$w.list yview"
listbox $w.list -font $TekiInfo(fixedfont) -relief sunken -width 100 \
-bd 2 -yscrollcommand "$w.scroll set" -setgrid 1 -height 12 \
-selectmode extended
grid $w.scroll -column $numButtons -row 0 -sticky ns
grid $w.list -column 0 -columnspan $numButtons -row 0 -sticky nsew
grid $w.install -column 0 -row 1
grid $w.readme -column 1 -row 1
grid $w.showall -column 2 -row 1
grid $w.showcompat -column 3 -row 1
grid $w.cancel -column 4 -row 1 -columnspan 2
}
Wise_CenterWindow .toc
wm transient .toc .
TekiWWWBrowseAll
}
#-----------------------------------------------------------------------
#
# WWW support --
#
# This section contains code to download files and directories from
# a URL into a temp file.
#
# Get a decent value for the temporary directory
#
switch $tcl_platform(platform) {
windows {set TekiInfo(tmpDir) C:/TEMP}
unix {set TekiInfo(tmpDir) /tmp}
}
if {[info exists env(TMP)] && [file isdirectory $env(TMP)]} {
set TekiInfo(tmpDir) $env(TMP)
}
if {[info exists env(TMPDIR)] && [file isdirectory $env(TMPDIR)]} {
set TekiInfo(tmpDir) $env(TMPDIR)
}
if {[info exists env(TEMP)] && [file isdirectory $env(TEMP)]} {
set TekiInfo(tmpDir) $env(TEMP)
}
if {[info exists env(TEMPDIR)] && [file isdirectory $env(TEMPDIR)]} {
set TekiInfo(tmpDir) $env(TEMPDIR)
}
set TekiInfo(browseURL) http://www2.cs.cornell.edu/zeno/bsmith/contents.tkc
# -----------------------------------------------------------------------
#
# Routines to read and verify .tek files
#
# Todo:
# Bullet-proof this section using a safe interpreter
#
proc TekiReadTOC1.0 {filename} {
global TekiInfo
if [catch {source $filename} err] {
global errorCode errorInfo
error "Error reading version 1.0 TEKI TOC file $filename.
Execute \"source $filename\" in a Tcl shell to find error" \
$errorInfo $errorCode
}
}
proc TekiReadTOCFile {filename} {
Debug_Enter TekiReadTOCFile
set f [open $filename r]
set x [split [gets $f]]
close $f
if {([llength $x] != 3) ||
[string compare [lindex $x 0] #] ||
[string compare [lindex $x 1] TekiTOC] } {
error "Invalid Teki Table-of-contents file"
}
set tekiFileVersion [lindex $x 2]
if [catch {TekiReadTOC$tekiFileVersion $filename} err] {
global errorCode errorInfo
error $err $errorInfo $errorCode
}
Debug_Leave
}
proc TekiReadVersion1.0 {filename} {
global newPackage
if [catch {source $filename} err] {
global errorCode errorInfo
error "Error reading version 1.0 TEKI file $filename.
Execute \"source $filename\" in a Tcl shell to find error" \
$errorInfo $errorCode
}
}
proc TekiReadFile {filename} {
Debug_Enter ReadTekiFile
set cwd [pwd]
cd [file dirname $filename]
set filename [file tail $filename]
set f [open $filename r]
set x [split [gets $f]]
close $f
if {([llength $x] != 3) ||
[string compare [lindex $x 0] #] ||
[string compare [lindex $x 1] TekiFile] } {
cd $cwd
error "Invalid Teki file"
}
set tekiFileVersion [lindex $x 2]
if [catch {TekiReadVersion$tekiFileVersion $filename} err] {
global errorCode errorInfo
cd $cwd
error $err $errorInfo $errorCode
}
cd $cwd
Debug_Leave
}
proc TekiVerifyFile {} {
global newPackage
foreach attr {available defaultPackages defaultArch
defaultInstallDoc defaultInstallExamples defaultInstallData} {
if ![info exists newPackage($attr)] {
error "Teki file does not define attribute '$attr'"
}
}
foreach pkg $newPackage(available) {
foreach attr {name version description requires updateURL srcURL
registerURL srcDir destDir infoFile tclFiles dataFiles
docDir docFiles exampleFiles objFiles copyright} {
if ![info exists newPackage($pkg,$attr)] {
error "Package $pkg does not define attribute '$attr'"
}
}
}
foreach pkg $newPackage(defaultPackages) {
if ![info exists newPackage($pkg,name)] {
error "Teki file defines invalid package $pkg in as part of defaultPackages"
}
}
}
# ---------------------------------------------------------------------- -
#
# TekiInstall
#
# Install a package.
# The file has been read and verified, and the packages variable refers
# to good packages (they exist in newPackage, haven't already been
# installed, etc). The errors we have to worry about are errors copying
# files.
#
# TekiDownloadFile --
#
# Copy $url into $tmpDir/$file, making directories as needed
#
# Throws an error if user presses cancel button during download
# or another error occurs.
# The caller must delete the file when done.
proc TekiCancelDownload {} {
global TekiInfo
wm withdraw .dl
http_reset $TekiInfo(token)
}
proc TekiDownloadProgressEnd {} {
wm withdraw .dl
}
proc TekiDownloadProgressStart {url} {
global TekiInfo
if {[info commands .dl] == ""} {
toplevel .dl
label .dl.l -font $TekiInfo(fixedfont)
scale .dl.s -orient horizontal -sliderrelief flat \
-sliderlength 0 -showvalue 0
button .dl.cancel -text Cancel -command TekiCancelDownload
grid .dl.l -row 0 -column 0 -sticky ew
grid .dl.s -row 1 -column 0 -sticky ew
grid .dl.cancel -row 2 -column 0
} else {
.dl.s configure -sliderlength 0
}
wm title .dl "Downloading $url"
.dl.l configure -text $url
Wise_CenterWindow .dl
}
proc TekiDownloadProgress {token max curr} {
global TekiInfo
set TekiInfo(token) $token
if {$max > 0} {
set width [winfo width .dl.s]
set pixels [expr int($curr*$width/$max)]
.dl.s configure -sliderlength $pixels
update idletasks
}
}
proc TekiDownloadFile {url dest} {
global errorInfo errorCode
file mkdir [file dirname $dest]
set f [open $dest w]
fconfigure $f -translation binary
TekiDownloadProgressStart $url
set code [catch {http_get $url -channel $f -progress TekiDownloadProgress} token]
TekiDownloadProgressEnd
close $f
if {$code} {
catch {file delete -force $dest}
error "HTTP error:\n $token\n\nwhile fetching\n $url" \
$errorInfo $errorCode
}
upvar #0 $token state
# The variable state(http) contains the server response
# code. If the code is [45]xx, there's a problem with the
# URL.
if {[string match {[45]*} [lindex $state(http) 1]]} {
catch {file delete -force $dest}
set err [lrange $state(http) 2 end]
error "HTTP error:\n $err\n\nwhile fetching\n $url" \
}
# Handle URL redirects
if {$state(status) == "reset"} {
file delete -force $dest
uplevel #0 unset $token
error "Connection reset by user"
}
foreach {name value} $state(meta) {
if {[regexp -nocase ^location$ $name]} {
file delete -force $dest
TekiDownloadFile [string trim $value] $dest
}
}
uplevel #0 unset $token
}
#
# Copy from $src to $dest
# Make directories as needed
# Download file is source is a URL
# Example:
# src = http://www2.cs.cornell.edu/bsmith/tmp/tcl-files/foo.tcl
# dest = /usr/local/lib/tcl/dp4.0/tcl-files/foo.tcl
#
proc TekiCopy {src dest} {
file mkdir [file dirname $dest]
if [string match http://* $src] {
TekiDownloadFile $src $dest
} else {
file copy $src $dest
}
}
#
# TekiInstall --
#
# Install the packages passed in. Can be called
# with through the command line or the Teki GUI.
#
# Parameters:
#
# dirBase -- directory where teki file is located. Other
# file (code, etc) are given by the srcDir relative
# to this directory
# web -- if true if we're doing a Web-based install and the
# $srcURL for each package is used to locate files to copy.
# If false, $dirBase/$srcDir for each package gives the
# place where the source files are located.
# codePrefix -- prefix for installing code, data, and examples. For
# example, /usr/local/lib/tcl
# docPrefix -- prefix for installing documentation. (e.g., /usr/man)
# packages -- list of packages to install (members of newPackage(installed))
# archList -- list of archtectures, (members of TekiInfo(systemNames))
# installData -- Boolean indicating whether to install data files
# installDoc -- Boolean indicating whether to install documentation
# installExamples -- Boolean indicating whether to install examples
#
# Notes
# Installs documentation in $docPrefix/$newPackage(destDir)
# Installs tcl files, object files, data, and examples in
# $codePrefix/$newPackage(destDir)
#
#
proc TekiInstall {dirBase web codePrefix docPrefix packages archList
{installData 1} {installDoc 1} {installExamples 1}} {
global newPackage tcl_platform TekiInfo errorCode errorInfo
Debug_Enter TekiInstall
if {![file isdirectory $codePrefix]} {
TekiError "Error: Destination directory $codePrefix doesn't exist"
return
}
foreach pkg $packages {
if $web {
set srcDir $newPackage($pkg,srcURL)
} else {
set srcDir [file join $dirBase $newPackage($pkg,srcDir)]
}
set steps {
"Making directory"
"Creating pkgIndex.tcl"
"Information file"
"Tcl files"
}
if {$installData && [string length $newPackage($pkg,dataFiles)]} {
lappend steps "Data files"
}
if {$installExamples && [string length $newPackage($pkg,exampleFiles)]} {
lappend steps "Examples"
}
if {$installDoc && [string length $newPackage($pkg,docFiles)]} {
lappend steps "Documentation"
}
foreach pair $newPackage($pkg,objFiles) {
set arch [lindex $pair 0]
if {[string match "all" $archList] ||
([lsearch -exact $archList $arch] != -1)} {
lappend steps "Files for $arch"
}
}
Progress_StepInit "Installing $pkg" $steps
Undo_Add $pkg Progress_StepEnd
# Make the destination directory. If it exists, verify that
# we should replace it
set destDir [file join $codePrefix $newPackage($pkg,destDir)]
if [file isdirectory $destDir] {
set rval [TekiWarning \
"Warning: Directory $destDir already exists.
Delete will replace it
Cancel will abort the installation of this package
Abort will abort the installation of this and all remaining packages" \
1 {Delete Cancel Abort}]
if {$rval == 2} {
Undo_All $pkg
break
}
if {$rval == 1} {
Undo_All $pkg
continue
}
if [catch {file delete -force $destDir} err] {
set rval [TekiWarning \
"Warning: Couldn't delete $destDir\n$err
Cancel will abort the installation of this package
Abort will abort the installation of all packages" \
0 {Cancel Abort}]
Undo_All $pkg
if {$rval == 0} {
continue
} else {
break
}
}
}
Progress_StepPrint
# Create $destDir, open $destDir/pkgIndex.tcl, and start writing it...
set fn [file join $destDir pkgIndex.tcl]
if [catch {file mkdir [file dirname $fn]} err] {
set rval [TekiWarning \
"Couldn't create target directory $destDir\n$err
Cancel will abort the installation of this package
Abort will abort the installation of all packages" \
0 {Cancel Abort}]
Undo_All $pkg
if {$rval == 0} {
continue
} else {
break
}
}
Undo_Add $pkg "file delete -force \"$destDir\""
if [catch {set pkgIndex [open $fn w]} err] {
set rval [TekiWarning \
"Couldn't create pkgIndex.tcl in $destDir\n$err
Cancel will abort the installation of this package
Abort will abort the installation of all packages" \
0 {Cancel Abort}]
Undo_All $pkg
if {$rval == 0} {
continue
} else {
break
}
}
Undo_Add $pkg "close $pkgIndex"
puts $pkgIndex "
# This file was automatically generated by TEKI
global tclPkgInfo
if {!\[info exists tclPkgInfo(installed)\] ||
\[lsearch \$tclPkgInfo(installed) $pkg\] == -1} {
lappend tclPkgInfo(installed) $pkg
set tclPkgInfo($pkg,codePrefix) [list $codePrefix]
set tclPkgInfo($pkg,docPrefix) [list $docPrefix]
set tclPkgInfo($pkg,installData) $installData
set tclPkgInfo($pkg,installDoc) $installDoc
set tclPkgInfo($pkg,installExamples) $installExamples
set tclPkgInfo($pkg,name) [list $newPackage($pkg,name)]
set tclPkgInfo($pkg,version) [list $newPackage($pkg,version)]
set tclPkgInfo($pkg,description) [list $newPackage($pkg,description)]
set tclPkgInfo($pkg,requires) [list $newPackage($pkg,requires)]
set tclPkgInfo($pkg,tekiFile) [list $newPackage($pkg,tekiFile)]
set tclPkgInfo($pkg,updateURL) [list $newPackage($pkg,updateURL)]
set tclPkgInfo($pkg,registerURL) [list $newPackage($pkg,registerURL)]
set tclPkgInfo($pkg,destDir) [list $newPackage($pkg,destDir)]
set tclPkgInfo($pkg,tclFiles) [list $newPackage($pkg,tclFiles)]
set tclPkgInfo($pkg,systemNames) [list $TekiInfo(systemNames)]"
Progress_StepPrint
# Copy in all the platform independent files to $destDir
# Preserve the package directory hierarchy
set infoFile $newPackage($pkg,infoFile)
if [string length $infoFile] {
if [catch {TekiCopy $srcDir/$infoFile $destDir/$infoFile} err] {
set rval [TekiWarning \
"Couldn't copying\n$infoFile\nto\n$destDir\n$err
Cancel will abort the installation of this package
Abort will abort the installation of all packages
Ignore will ignore this error and continue installation" \
0 {Cancel Abort Ignore}]
if {$rval == 0} {
Undo_All $pkg
continue
}
if {$rval == 1} {
Undo_All $pkg
break
}
set f {}
} else {
set f [file join $destDir $newPackage($pkg,infoFile)]
}
} else {
set f {}
}
puts $pkgIndex " set tclPkgInfo($pkg,infoFile) [list $f]"
Progress_StepPrint
set code 2
foreach file $newPackage($pkg,tclFiles) {
if [catch {TekiCopy $srcDir/$file $destDir/$file} err] {
set code [TekiWarning \
"Error copying\n$file\nto\n$destDir\n$err
Cancel will abort the installation of this package
Abort will abort the installation of all packages" \
0 {Cancel Abort}]
break;
}
}
if {$code == 0} {
Undo_All $pkg
continue
}
if {$code == 1} {
Undo_All $pkg
break
}
Progress_StepPrint
set dataFiles {}
if $installData {
set dataFiles $newPackage($pkg,dataFiles)
foreach file $dataFiles {
if [catch {TekiCopy $srcDir/$file $destDir/$file} err] {
set code [TekiWarning \
"Error copying data file\n$file\nto\n$destDir\n$err
Cancel will abort the installation of this package
Abort will abort the installation of all packages
Ignore will ignore this error and continue installation" \
0 {Cancel Abort Ignore}]
if {$code != 2} {
break
}
}
}
if {$code == 0} {
Undo_All $pkg
continue
}
if {$code == 1} {
Undo_All $pkg
break
}
Progress_StepPrint
}
puts $pkgIndex " set tclPkgInfo($pkg,dataFiles) [list $dataFiles]"
set exampleFiles {}
if $installExamples {
set exampleFiles $newPackage($pkg,exampleFiles)
foreach file $exampleFiles {
if [catch {TekiCopy $srcDir/$file $destDir/$file} err] {
set code [TekiWarning \
"Error copying\n$file\nto\n$destDir\n$err
Cancel will abort the installation of this package
Abort will abort the installation of all packages
Ignore will ignore this error and continue installation" \
0 {Cancel Abort Ignore}]
if {$code != 2} {
break
}
}
}
if {$code == 0} {
Undo_All $pkg
continue
}
if {$code == 1} {
Undo_All $pkg
break
}
Progress_StepPrint
}
puts $pkgIndex " set tclPkgInfo($pkg,exampleFiles) [list $exampleFiles]"
set docFiles {}
set docSrcDir $srcDir/$newPackage($pkg,docDir)
set docDestDir [file join $docPrefix $newPackage($pkg,destDir)]
if $installDoc {
#
# Make the destination directory. If it exists, verify that
# we should replace it
#
if [file isdirectory $docDestDir] {
set rval [TekiWarning \
"Warning: Directory $docDestDir already exists.
Delete will replace it
Cancel will abort the installation of this package
Abort will abort the installation of all packages" \
1 {Delete Cancel Abort}]
if {$rval == 1} {
Undo_All $pkg
continue
}
if {$rval == 2} {
Undo_All $pkg
break
}
if [catch {file delete -force $docDestDir} err] {
set code [TekiWarning \
"Error: Couldn't delete $docDestDir:\n$err
Cancel will abort the installation of this package
Abort will abort the installation of all packages
Ignore will ignore this error and continue installation" \
0 {Cancel Abort Ignore}]
if {$code == 0} {
Undo_All $pkg
continue
}
if {$code == 1} {
Undo_All $pkg
break
}
}
}
# Create $docDestDir
if [catch {file mkdir $docDestDir} err] {
set code [TekiWarning \
"Error creating documentation target directory $docDestDir\n$err
Cancel will abort the installation of this package
Abort will abort the installation of all packages
Ignore will ignore this error and continue installation" \
0 {Cancel Abort Ignore}]
if {$code == 0} {
Undo_All $pkg
continue
}
if {$code == 1} {
Undo_All $pkg
break
}
} else {
Undo_Add $pkg "file delete -force \"$docDestDir\""
}
# Copy all the documentation files
foreach file $newPackage($pkg,docFiles) {
if [catch {TekiCopy $docSrcDir/$file $docDestDir/$file} err] {
set code [TekiWarning \
"Error copying\n$file\nto\n$docDestDir\n$err
Cancel will abort the installation of this package
Abort will abort the installation of all packages
Ignore will ignore this error and continue installation" \
0 {Cancel Abort Ignore}]
break
}
}
if {$code == 0} {
Undo_All $pkg
continue
}
if {$code == 1} {
Undo_All $pkg
break
}
Progress_StepPrint
}
# Copy in all the platform dependent files
# If we get an error and have to abort in the middle,
# we'll throw an error to break out of the nested
# loops.
set installedArchs {}
set code [catch {
foreach pair $newPackage($pkg,objFiles) {
set system [lindex $pair 0]
#
# system is a name for the system, such as Solaris
# one Windows. $archList is either the
# string "all" or a list of these names
#
if {[string match "all" $archList] ||
([lsearch -exact $archList $system] != -1)} {
set fileList {}
lappend installedArchs $system
foreach file [lindex $pair 1] {
if [catch {TekiCopy $srcDir/$file $destDir/$file} err] {
set rval [TekiWarning \
"Error copying\n$file\nto\n$destDir\n$err\n
Cancel will abort the installation of this package
Abort will abort the installation of all packages
Ignore will ignore this error and continue installation" \
0 {Cancel Abort Ignore}]
switch $rval {
0 {
Undo_All $pkg
error {} 5; # continue
}
1 {
Undo_All $pkg
error {} 6; # break
}
2 {}
}
}
lappend fileList $file
}
Progress_StepPrint
puts $pkgIndex " set tclPkgInfo($pkg,objFiles,$system) [list $fileList]"
}
}
} err]
switch $code {
0 {}
5 continue
6 break
default {
error $err $errorInfo $errorCode
}
}
puts $pkgIndex " set tclPkgInfo($pkg,archList) [list $installedArchs]"
# Finish up!
# puts $pkgIndex " TekiPackageInit $pkg"
puts $pkgIndex "}"
set tclfilesdir "$codePrefix/$pkg"
set outstr [concat package ifneeded dp 4.0 \[list LoadLib $tclfilesdir\]]
puts $pkgIndex $outstr
puts $pkgIndex "proc LoadLib dir {"
set outstr [concat foreach file \[glob \$dir/library/*.tcl\]]
puts -nonewline $pkgIndex " $outstr"
puts $pkgIndex " {"
set outstr [concat uplevel #0 source \[list \$file\]]
puts $pkgIndex " $outstr"
puts $pkgIndex " }"
set outstr [concat uplevel #0 load \[list \$dir/$file\]]
puts $pkgIndex " $outstr"
puts $pkgIndex "}"
close $pkgIndex
Undo_Clear $pkg
Progress_StepEnd
}
TekiGetAllPkgInfo
Debug_Leave
}
#
# TekiUninstall --
#
# Uninstall all the packages passed in. Can be called with through the
# command line or the Teki GUI.
#
proc TekiUninstall {pkgList} {
global tclPkgInfo
set noWarn 0
foreach pkg $pkgList {
if {!$noWarn} {
set rval [TekiWarning "Uninstall package $pkg ?" \
0 {Yes {Yes All} Cancel}]
if {$rval == 2} {
return
}
if {$rval == 1} {
set noWarn 1
}
}
# Delete all the code/data/example files.
# These are stored under the $codePrefix directory
set codePrefix $tclPkgInfo($pkg,codePrefix)
set destDir [file join $codePrefix $tclPkgInfo($pkg,destDir)]
if [catch {file delete -force $destDir} err] {
TekiError "Error: Couldn't delete $destDir:\n$err\nAborting uninstallation"
break
}
#
# delete doc files
#
if {$tclPkgInfo($pkg,installDoc)} {
set docPrefix $tclPkgInfo($pkg,docPrefix)
set dir [file join $docPrefix $tclPkgInfo($pkg,destDir)]
if [catch {file delete -force $dir} err] {
TekiError "Error: Couldn't delete $dir:\n$err\nAborting uninstallation"
break
}
}
}
}
# ---------------------------------------------------------------------
#
# Support procedures for all extensions. The following two procedures
# are used at runtime to source/load the right files (including architecture
# dependent files). TekiPackageInit is called by the pkgIndex.tcl file
# with the package name, after the tclPkgInfo variables have been set up.
# It determines if the OS is supported, and if so, makes the right calls
# to package ifneeded. TekiPackageSetup is called when package require
# is called. It loads all the files (including architecture dependent
# files
#
#
# Predefined system names
#
set TekiInfo(systemNames) {
{Solaris SunOS 5*}
{SunOS SunOS 4*}
{HPUX HP* 9*}
{Linux Linux* 2*}
{FreeBSD FreeBSD* 2*}
{Win95/NT Win* *}
}
proc TekiPackageInit {pkg} {
global tclPkgInfo tcl_platform
set os $tcl_platform(os)
set ver $tcl_platform(osVersion)
set system {}
foreach tuple $tclPkgInfo($pkg,systemNames) {
set osPattern [lindex $tuple 1]
set verPattern [lindex $tuple 2]
if {[string match $osPattern $os] &&
[string match $verPattern $ver]} {
set system [lindex $tuple 0]
break;
}
}
if [string length $system] {
set name $tclPkgInfo($pkg,name)
set version $tclPkgInfo($pkg,version)
package ifneeded $name $version "TekiPackageSetup $pkg $system"
}
}
proc TekiPackageSetup {pkg system} {
global tclPkgInfo
set currDir [pwd]
set prefix $tclPkgInfo($pkg,codePrefix)
set destDir [file join $prefix $tclPkgInfo($pkg,destDir)]
cd $destDir
set tclFiles $tclPkgInfo($pkg,tclFiles)
if [info exists tclPkgInfo($pkg,objFiles,$system)] {
set objFiles $tclPkgInfo($pkg,objFiles,$system)
} else {
set objFiles {}
}
foreach f $tclFiles {
catch {uplevel #0 "source $f"}
}
foreach f $objFiles {
if [string match *.tcl $f] {
catch {uplevel #0 "source $f"}
} else {
catch {uplevel #0 "load $f"}
}
}
cd $currDir
}
proc TekiGetAllPkgInfo {} {
global tcl_pkgPath auto_path tclPkgInfo tcl_version tk_version
catch {unset tclPkgInfo}
set tclver tcl$tcl_version
set tclPkgInfo(installed) $tclver
set tclPkgInfo($tclver,name) Tcl
set tclPkgInfo($tclver,requires) {}
set tclPkgInfo($tclver,version) $tcl_version
set tclPkgInfo($tclver,description) "Tcl core"
set tclPkgInfo($tclver,infoFile) {}
if [info exists tk_version] {
set tkver tk$tk_version
lappend tclPkgInfo(installed) $tkver
set tclPkgInfo($tkver,name) Tk
set tclPkgInfo($tkver,requires) [list [list Tcl $tcl_version]]
set tclPkgInfo($tkver,version) $tk_version
set tclPkgInfo($tkver,description) "Tk core"
set tclPkgInfo($tkver,infoFile) {}
}
#
# Get list of all pkgIndex.tcl files
#
set fileList {}
foreach dir [concat $auto_path [list $tcl_pkgPath]] {
if {![catch {glob [file join $dir pkgIndex.tcl] [file join $dir * pkgIndex.tcl]} files]} {
set fileList [concat $files $fileList]
}
}
set code 0
foreach file $fileList {
if [catch {source $file} err] {
if {$code != 1} {
set code [TekiWarning \
"Error sourcing $file:\n $err?" 0 {Ignore {Ignore All} Abort}]
}
if {$code == 2} {
exit
}
}
}
}
# ---------------------------------------------------------------------
#
# Initialization code
#
TekiGetAllPkgInfo
if $TekiInfo(gui) {
TekiCreateUI
wm deiconify .
}
if {$tcl_version < 8.0} {
proc fcopy args {
eval unsupported0 $args
}
}