home *** CD-ROM | disk | FTP | other *** search
Text File | 1999-01-31 | 23.4 KB | 713 lines | [TEXT/ALFA] |
- ## -*-Tcl-*-
- # ###################################################################
- # Vince's Additions - an extension package for Alpha
- #
- # FILE: "install.tcl"
- # created: 25/7/97 {1:12:02 am}
- # last update: 31/1/1999 {11:27:40 pm}
- # Author: Vince Darley
- # E-mail: <darley@fas.harvard.edu>
- # mail: Division of Engineering and Applied Sciences, Harvard University
- # Oxford Street, Cambridge MA 02138, USA
- # www: <http://www.fas.harvard.edu/~darley/>
- #
- # Copyright (c) 1997-1998 Vince Darley, all rights reserved
- #
- # This file contains a pretty complex package installation
- # procedure, and some more rudimentary code which queries
- # an ftp site for a list of packages and checks dates etc
- # to see if there's something new. The idea being you can
- # then just select from a menu to download and subsequently
- # install.
- #
- # Package installation:
- #
- # There is a new install mode 'Inst' which adds the Install menu.
- # Install mode is trigerred when a file's name ends in 'Install'
- # or 'INSTALL', or when the first line of the file contains the
- # letters 'install', provided in this last case, that the file
- # is not in Alpha's Tcl hierarchy. This last case is useful so
- # that a single .tcl file can be a package and be installed by
- # Alpha using these nice scripts, without the need for a separate
- # install-script-file. However once that .tcl file is installed,
- # if you open it you certainly wouldn't want it opened in Install mode!
- #
- # Once you've opened a file in install mode:
- #
- # You can select 'install this package' from the menu. (If the file's
- # first line contains 'auto-install' the menu item is automatically
- # selected, provided no modifier key is pressed). In any case, this
- # does the following: if there's an install file in the current directory
- # it is sourced. An install file is defined as a file at the same
- # level as the current file whose name matches "*install*.tcl".
- # If no install file is found, a default (but still rather
- # sophisticated) installation takes place, by calling the procedure
- # 'install::packageInstallationDialog'. Any install script in your
- # *install*.tcl file may wish to use that procedure anyway. For
- # instance, the installer for Vince's Additions uses just the
- # following lines in its installation file:
- #
- # install::packageInstallationDialog "Vince's Additions" "\
- # These additions include a number of different packages, designed to \
- # make using Alpha an even more pleasant experience! They include a \
- # more sophisticated completion and template mechanism, some bibliography \
- # conversion routines, and a general projects/documents organisation scheme."
- #
- # In any case, 'install::packageInstallationDialog' does the following:
- # It scans the current directory for files which may need installing.
- # This includes any .tcl file which is not the *install*.tcl script.
- # It also includes the same in any subdirectories of the current
- # directory. Intelligent guesses are made as to whether files are
- # Modes, Menus, Packages, Completions, Extensions, Help files or
- # UserModifications.
- #
- # Extensions are *+\d.tcl files, these go in tclExtensionsFolder
- # Modes are *Mode.tcl files, or all files in a subdir *Mode*
- # Menus are *Menu.tcl files, or all files in a subdir *Menu*
- # Completions are all files *Completions.tcl
- # Help files end in 'help' or 'tutorial' (any case)
- # UserModifications are any files in a UserModifications subdir.
- # Packages are anything else.
- #
- # UserModifications are files which a package installs once, but
- # the user is expected to edit afterwards. Hence if the package
- # is reinstalled, those files are not overwritten.
- #
- # Clearly if the original install file was in fact a .tcl file on
- # its own (with 'install' in the first line) then we don't search
- # the directory in which it sits. This is now implemented.
- #
- # ----------
- # OK, we've got all the files and worked out where they should go.
- # Now we build an installation dialog, from which the user can
- # select 'Easy Install', or 'Custom Install'. Easy install does
- # the works, custom allows the user to choose amongst all the
- # available sub-pieces. A sub-piece is any single item in the
- # install directory: so you can package up blocks of files as a single
- # package by putting them in a sub-dir.
- #
- # If you hit 'Ok' installation takes place, with optional backup
- # of removed files.
- #
- # Currently package indices and tcl indices are then rebuilt. This
- # last thing needs to be a bit more sophisticated...
- #
- # ----------
- # Caveats:
- #
- # Currently not clever enough to install, say, HTML mode in the
- # way it currently is: here we wish to install all HTML files in
- # one sub-dir of the Modes dir, but we wish to allow the user to
- # pick which sub-sets of files will go in that 'HTML and CSS modes'
- # directory. So the user could install just HTML files and ignore
- # the CSS ones. The solution I propose is to store such items in
- # separate subfolder of the base HTML subfolder. Such items would
- # then be sub-choices of the base 'install HTML mode' choice, and
- # when installed, would be installed directly into the HTML mode
- # dir.
- #
- # I think I need more feedback before embarking on further
- # modifications to this code.
- #
- # ###################################################################
- ##
-
- namespace eval install {}
-
- proc installMenu {} {}
-
- set installMenu "Install"
- set menu::items(Install) [list \
- "installThisPackage" "(-" "rebuildPackageIndices" "rebuildTclIndices"]
-
- menu::buildSome Install
-
- proc install::rebuildPackageIndices {} { alpha::rebuildPackageIndices }
-
- ##
- # -------------------------------------------------------------------------
- #
- # "install::installThisPackage" --
- #
- # DO NOT CALL THIS PROCEDURE FROM YOUR *install.tcl INSTALLATION SCRIPT
- # IT WILL CAUSE INFINITE RECURSION AND CRASH ALPHA. THIS PROCEDURE IS
- # DESIGNED TO SOURCE YOUR *install.tcl FILE AUTOMATICALLY IF IT EXISTS.
- #
- # Instead call install::packageInstallationDialog
- # and install::askRebuildQuit
- # -------------------------------------------------------------------------
- ##
- proc install::installThisPackage {} {
- # single-file packages by definition don't have an installer.
- if {[file extension [set name [install::name]]] == ".tcl"} {
- install::packageInstallationDialog "Package"
- } else {
- set currD [file dirname $name]
- if {[regexp -nocase {auto-install-script} [getText [minPos] [nextLineStart [minPos]]]]} {
- set installer [list $name]
- } else {
- set installer [glob -nocomplain [file join $currD *nstall*.tcl]]
- if {[llength $installer] > 1} {
- alertnote "This package has two installation files. This is bad; I'll do a standard installaton."
- }
- }
-
- if {[llength $installer] == 1} {
- global installation_dir
- set installation_dir $currD
- # installer is a one-item list, so no need to wrap it
- uplevel \#0 source $installer
- unset installation_dir
- } else {
- install::packageInstallationDialog "Package"
- }
- }
- global install::forcequit
- install::askRebuildQuit ${install::forcequit}
- }
-
- proc install::sourceUpdatedSystem {} {
- global HOME install::time
- if {![info exists install::time]} { return }
- foreach f [glob -nocomplain [file join ${HOME} Tcl SystemCode *.tcl]] {
- if {[file tail $f] == "AlphaBits.tcl" \
- || [file tail $f] == "globals.tcl"} {continue}
- getFileInfo $f info
- if {$info(modified) > ${install::time}} {
- catch [list uplevel \#0 [list source $f]]
- }
- }
- }
-
- proc install::askRebuildQuit {{force 0}} {
- alertnote "All indices must now be rebuilt for the installation to work."
- if {![key::optionPressed] \
- || [dialog::yesno "Shall I rebuild the indices?"]} {
- install::sourceUpdatedSystem
- set n [alpha::package names]
- alpha::rebuildPackageIndices
- set new [lremove -l [alpha::package names] $n]
- if {![key::optionPressed] \
- || [dialog::yesno "Shall I rebuild the Tcl indices?"]} {
- rebuildTclIndices
- }
- auto_reset
- if {[llength $new]} {
- if {[dialog::yesno "You just installed the following new packages: $new; do you want to activate them at next startup?"]} {
- global global::features
- eval lappend global::features $new
- }
- }
- }
- if {$force || [dialog::yesno "It is recommended that you quit and restart Alpha. Quit now?"]} {
- if {$force} {alertnote "Alpha must now quit."}
- if {[win::CurrentTail] == "Installation report"} {
- setWinInfo read-only 0
- setWinInfo dirty 1
- }
- quit
- }
- }
-
- ##
- # -------------------------------------------------------------------------
- #
- # "install::openHook" --
- #
- # Used when opening an install file to check for an 'auto-install' line.
- # -------------------------------------------------------------------------
- ##
- proc install::openHook {name} {
- if {![getModifiers] && [regexp -nocase {auto-install} [getText [minPos] [nextLineStart [minPos]]]]} {
- moveWin $name 10000 10000
- global install::_name
- set install::_name $name
- catch {install::installThisPackage}
- unset install::_name
- if {![catch {bringToFront $name}]} {
- killWindow
- }
- }
- }
-
- proc install::name {} {
- global install::_name
- if {[info exists install::_name]} {
- return ${install::_name}
- } else {
- return [win::Current]
- }
- }
-
- proc install::readAtStartup {w} {
- global alpha::readAtStartup modifiedVars
- lappend alpha::readAtStartup $w
- lappend modifiedVars alpha::readAtStartup
- }
-
- ##
- # -------------------------------------------------------------------------
- #
- # "install::packageInstallationDialog" --
- #
- # Optional arguments are as follows:
- #
- # -ignore {list of files to ignore}
- # -remove {list of files to remove from Alpha hierarchy}
- # -rebuildquit '0 or 1'
- # (prompts the user to rebuild indices and quit; default 1)
- # -require {Pkg version Pkg version …}
- # e.g. -require {Alpha 6.52 elecCompletions 7.99}
- #
- # and
- #
- # -SystemCode -Modes -Menus
- # -BugFixes -Completions -Packages
- # -ExtensionsCode -UserModifications -Tools
- #
- # which force the placement of the following list of files.
- # -------------------------------------------------------------------------
- ##
- proc install::packageInstallationDialog {{pkgname "Package"} {description ""} args} {
- set win::Current [install::name]
- set currD [file dirname ${win::Current}]
- if {[file extension ${win::Current}] == ".tcl"} {
- # single file to install
- set pkgname [file root [file tail ${win::Current}]]
- set description "I'll install this single-file package, placing\
- it in its correct location in Alpha's code base."
- set rebuild [eval [list install::_packageInstallationDialog $pkgname $description \
- $currD [list [file tail ${win::Current}]]] $args]
- } else {
- global file::separator
- set toplevels [glob -nocomplain [file join $currD *.tcl]]
- eval lappend toplevels [glob -t TEXT -nocomplain [file join $currD "* *"]]
- set toplevels [lremove -glob $toplevels *\[Ii\]nstall*]
- set toplevels [lremove -glob $toplevels *INSTALL*]
- set subdirs [glob -nocomplain "[file join $currD *]${file::separator}"]
- foreach item $toplevels {
- lappend items [file tail $item]
- }
- if {[file exists [file join $currD Changes]]} {
- lappend items Changes
- }
- foreach dir $subdirs {
- lappend items "[file tail [file dirname $dir]]${file::separator}"
- }
- set subdirs [lremove -glob $subdirs "*Completions${file::separator}"]
- set completions [glob -nocomplain "[file join $currD Completions]${file::separator}"]
- set usermods [glob -nocomplain "[file join $currD UserModifications]${file::separator}"]
- eval [list install::_packageInstallationDialog $pkgname $description \
- $currD $items] $args
- }
- }
-
- proc install::_packageInstallationDialog {{pkgname "Package"} {description ""} currD items args} {
- global install::time file::separator
- set install::time [now]
- set install_types [list SystemCode CorePackages \
- Modes Menus BugFixes Completions Packages Home \
- ExtensionsCode UserModifications Help QuickStart Tools remove]
- set opts(-ignore) ""
- set opts(-forcequit) 0
- set opts(-require) ""
- foreach type $install_types {
- set opts(-$type) ""
- }
- getOpts [concat provide ignore require rebuildquit forcequit $install_types]
-
- set assigned ""
- foreach type $install_types {
- if {$opts(-$type) != ""} {
- eval lappend assigned $opts(-$type)
- set $type $opts(-$type)
- }
- }
- # check if package requires others:
- array set req $opts(-require)
- foreach pkg [array names req] {
- eval package::reqInstalledVersion [list $pkg] $req($pkg)
- }
- catch {unset req}
- unset opts(-require)
- # check on -provide option
- if {[info exists opts(-provide)]} {
- array set prov $opts(-provide)
- foreach pkg [array names prov] {
- # check currently installed version isn't newer
- if {![catch {alpha::package versions $pkg} v]} {
- switch -- [alpha::package vcompare $v $prov($pkg)] {
- 0 {
- alertnote "Package $pkg version $v is already installed.\
- You may wish to cancel the installation."
- }
- 1 {
- alertnote "This installer is for $pkg version $prov($pkg)\
- but version $v is already installed. You may wish to\
- cancel the installation."
- }
- }
- }
- }
- catch {unset prov}
- unset opts(-provide)
- }
- # check if package has over-ridden default
- global install::forcequit
- set install::forcequit $opts(-forcequit)
- catch {unset opts(-rebuildquit)}
- unset opts(-forcequit)
- # Now assume packages/modes are sub-dirs, completions are in the
- # Completions dir, and toplevels are obvious from their name.
- # (Mode, Menu, BugFixes or default is in Packages dir)
-
- # Create a dialog:
- if {$description == ""} {
- set description "I'll do a complete installation, placing all modes,\
- menus, completions, help files, tools, extensions and packages in their\
- correct locations. In\
- addition, any core bug fixes this package contains will be patched into\
- Alpha's core Tcl code."
- }
- set y 80
- set names [list "Easy Install" "Custom Install"]
- lappend dial -n [lindex $names 0]
- eval lappend dial \
- [dialog::text "$description" 15 y 55]
- incr y 10
- eval lappend dial \
- [dialog::checkbox "Backup removed files" 1 20 y]
- eval lappend dial \
- [dialog::checkbox "Show installation log" 1 20 y]
- incr y 22
- eval lappend dial \
- [dialog::text "Click OK to continue with the installation" 15 y]
- if {${install::forcequit}} {
- eval lappend dial \
- [dialog::text "Alpha will quit after this installation." 15 y]
- }
- set othery [expr {$y +10}]
- lappend dial -n [lindex $names 1]
- set y 60
- eval lappend dial \
- [dialog::checkbox "Backup removed files" 1 20 y]
- eval lappend dial \
- [dialog::checkbox "Show installation log" 1 20 y]
- incr y 5
- foreach item $items {
- if {[lsearch $opts(-ignore) $item] != -1 \
- || [lsearch $assigned $item] != -1} {
- continue
- }
- if {[string match *+*.tcl $item]} {
- lappend ExtensionsCode $item
- } elseif {[regexp "SystemCode" $item]} {
- lappend SystemCode $item
- } elseif {$item == "Changes" || [string match "Writing *" $item]} {
- lappend Help $item
- } elseif {[regexp "(H|h)elp(/|:)?$" $item]} {
- lappend Help $item
- } elseif {[regexp -nocase "quick *start$" $item]} {
- lappend QuickStart $item
- } elseif {[regexp "Modes(/|:)?$" $item]} {
- lappend Modes $item
- } elseif {[regexp "Menus(/|:)?$" $item]} {
- lappend Menus $item
- } elseif {[regexp "Docs(/|:)" $item]} {
- lappend Home $item
- } elseif {[regexp "Tools" $item]} {
- lappend Tools $item
- } elseif {[regexp -nocase "mode(:|/|\.tcl)?$" $item]} {
- lappend Modes $item
- } elseif {[regexp -nocase "menu(:|/|\.tcl)?$" $item]} {
- lappend Menus $item
- } elseif {[regexp -nocase "bugfixes" $item]} {
- lappend BugFixes $item
- } elseif {[regexp "Completions" $item]} {
- lappend Completions $item
- } elseif {[regexp "Tools" $item]} {
- lappend Tools $item
- } elseif {[regexp "UserModifications" $item]} {
- lappend UserModifications $item
- } elseif {[regexp "CorePackages" $item]} {
- lappend CorePackages $item
- } else {
- lappend Packages $item
- }
- }
- set x 20
- set continue 0
- foreach items $install_types {
- if {[info exists $items]} {
- if {$continue} {
- set continue 0
- if {$y + 10 > $othery} { set othery [expr {$y +10}] }
- set y 100
- incr x 190
- eval lappend dial [dialog::text "continued…" $x y]
- }
- if {$items != "remove"} {
- set t "Install $items"
- } else {
- set t "Remove obsolete files"
- }
- eval lappend dial [dialog::text $t $x y]
- foreach item [set $items] {
- lappend options [list $items $item]
- regsub "\[/:\]\$" $item " ƒ" item
- eval lappend dial [dialog::checkbox $item 1 [expr {$x + 20}] y]
- if {$y > 360} {
- set continue 1
- }
- }
- }
- }
- incr y 10
- set h [expr {$othery > $y ? $othery : $y}]
- set yb [expr {$h - 70}]
- set w [expr {390 + ($x/2)}]
- set dials [list dialog -w $w -h $h]
- set y 10
- eval lappend dials [dialog::text "$pkgname installation options" 20 y 35]
- eval lappend dials [dialog::button "OK" [expr {$w -80}] yb]
- eval lappend dials [dialog::button "Cancel" [expr {$w -80}] yb]
- set res [eval [concat $dials [list -m [concat [list [lindex $names 0]] $names] 250 10 405 30] $dial]]
- if {[lindex $res 1]} { error "Cancel" }
- # cancel was pressed
- set easy_install [expr 1 - [lsearch $names [lindex $res 2]]]
- if {$easy_install} {
- set make_backup [lindex $res 3]
- set make_log [lindex $res 4]
- } else {
- set make_backup [lindex $res 5]
- set make_log [lindex $res 6]
- }
- if {$make_backup} {
- global HOME
- set make_backup [file join $HOME InstallationBackup]
- } else {
- set make_backup ""
- }
- set i 6
- global install::_ignore install::log
- set install::_ignore $opts(-ignore)
- set install::log ""
- foreach o $options {
- incr i
- if {!$easy_install && ![lindex $res $i]} { continue }
- set type [lindex $o 0]
- set name [lindex $o 1]
- message "Installing $type '$name'"
- install::files $type $currD $name $make_backup
- }
- unset install::_ignore
- if {$make_log} {
- install::showLog
- } else {
- unset install::log
- }
- }
-
- proc install::showLog {{title "Installation report"}} {
- global install::log
- new -g 0 160 640 300 -n $title -info \
- [expr {${install::log} == "" ? \
- "No changes were made. You must have already installed this package." \
- : "${install::log}End of report."}]
- unset install::log
- }
-
-
- # Install 'name' from $currD into where it should go
- # If 'name' ends in a colon, it's a directory. We can just
- # use glob to get a list!
- proc install::files {type from name backup} {
- global HOME PREFS tclExtensionsFolder file::separator
- set flist [glob -nocomplain [file join $from $name*]]
- switch -- $type {
- Tools {
- set to [file join ${HOME} Tools]
- foreach f $flist {
- install::file_to $f $to $backup
- }
- }
- remove {
- if {![catch {file::standardFind $name} what]} {
- if {[regexp "(/|:)\$" $name]} {
- foreach f [glob -nocomplain ${what}*] {
- file::removeOne $f $backup
- }
- install::log "Removed dir: $name"
- rmdir $what
- } else {
- file::removeOne $what $backup
- }
- }
- }
- SystemCode -
- Modes -
- Menus -
- Packages {
- set to [file join ${HOME} Tcl ${type}]
- if {[regexp "(.*)(/|:)\$" $name "" first] && $first != $type} {
- install::file_to $name $to
- set to [file join $to [file dirname $name]]
- }
- foreach f $flist {
- install::file_to $f $to $backup
- }
- }
- CorePackages {
- set to [file join ${HOME} Tcl SystemCode CorePackages]
- if {[regexp "(.*)(/|:)\$" $name "" first] && $first != $type} {
- install::file_to $name $to
- set to [file join $to [file dirname $name]]
- }
- foreach f $flist {
- install::file_to $f $to $backup
- }
- }
- QuickStart {
- set to [file join ${HOME} QuickStart]
- foreach f $flist {
- install::file_to $f $to $backup
- install::readAtStartup [file join ${HOME} QuickStart [file tail $f]]
- }
- }
- Home {
- set to "${HOME}"
- if {[regexp "(.*)(/|:)\$" $name "" first] && $first != $type} {
- install::file_to $name $to
- set to [file join $to [file dirname $name]]
- }
- foreach f $flist {
- install::file_to $f $to $backup
- }
- }
- Help {
- set to [file join ${HOME} Help]
- foreach f $flist {
- install::file_to $f $to $backup
- }
- }
- BugFixes {
- foreach f $flist {
- procs::patchOriginalsFromFile $f 0
- install::log "Installed patches from $f"
- }
- }
- Completions {
- set to [file join ${HOME} Tcl Completions]
- foreach f $flist {
- install::file_to $f $to $backup
- }
- }
- UserModifications {
- set to [file join ${HOME} Tcl UserModifications]
- global install::noreplace
- set install::noreplace 1
- foreach f $flist {
- install::file_to $f $to $backup
- }
- set install::noreplace 0
- }
- ExtensionsCode {
- if {![info exists tclExtensionsFolder]} {
- set tclExtensionsFolder $PREFS
- alertnote "This installation contains extension\
- (+.tcl) files. These require\
- the 'Smarter Source' package, which you do not have\
- installed. I've put the extension\
- files in your prefs directory, but they will not operate\
- without that package."
- }
- set to "$tclExtensionsFolder"
- foreach f $flist {
- install::file_to $f $to $backup
- }
- }
- }
- message "File installation complete"
- }
-
- proc install::log {text} {
- global install::log
- append install::log "${text}\r"
- }
-
- proc install::file_to {file to {backup ""}} {
- if {[regexp -nocase {(help|tutorial)$} [file tail $file]] \
- || ([file tail $file] == "Changes")} {
- global HOME
- install::_file_to $file [file join $HOME Help] $backup
- } elseif {[regexp {\+[0-9]*.tcl} [file tail $file]]} {
- global tclExtensionsFolder PREFS
- if {![info exists tclExtensionsFolder]} { set tclExtensionsFolder $PREFS }
- install::_file_to $file $tclExtensionsFolder $backup
- } else {
- if {[file isdirectory $file]} {
- set to [file join ${to} [file tail $file]]
- if {![file exists $to]} {mkdir $to}
- foreach f [glob [file join $file *]] {
- install::file_to $f $to $backup
- }
- } else {
- install::_file_to $file $to $backup
- }
- }
- }
-
- proc install::_file_to {file to {backup ""}} {
- global install::_ignore file::separator
- foreach suffix ${install::_ignore} {
- if {[string match *${file::separator}${suffix} $file] \
- || [string match ${suffix} $file]} {
- return
- }
- }
- message "Installing [file tail $file]"
- if {[file::ensureDirExists $to]} {
- install::log "Created dir '$to'"
- }
- if {[regexp "(/|:)\$" $file]} {
- # Install a directory
- if {[file::ensureDirExists [file join ${to} [file tail [file dirname $file]]]]} {
- install::log "Created dir '[file join ${to} [file tail [file dirname $file]]]'"
- }
- return
- }
- set files [glob -nocomplain "${file}*"]
- global install::noreplace
- if {[info exists install::noreplace] && ${install::noreplace}} {
- foreach ff $files {
- foreach suffix ${install::_ignore} {
- if {[string match *${suffix} $file]} { continue }
- }
- set f [file tail $ff]
- if {![file exists [file join $to $f]]} {
- if {[file exists "$ff" ]} {
- cp "$ff" [file join $to $f]
- install::log "copied '[file tail $ff]' to '[file join $to $f]'"
- }
- }
- }
- } else {
- foreach ff $files {
- foreach suffix ${install::_ignore} {
- if {[string match *${suffix} $file]} { continue }
- }
- set f [file tail $ff]
-
- if {[regexp "tclIndexx?" [file tail $f]]} {
- continue
- }
-
- file::replaceSecondIfOlder "$ff" [file join ${to} $f] 0 $backup
- }
- }
- }
-
-
-
-
-