# package.tcl --
#
# utility procs formerly in init.tcl which can be loaded on demand
# for package management.
#
# SCCS: @(#) package.tcl 1.5 98/01/28 17:07:30
#
# 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.
#

# pkg_mkIndex --
# This procedure creates a package index in a given directory.  The
# package index consists of a "pkgIndex.tcl" file whose contents are
# a Tcl script that sets up package information with "package require"
# commands.  The commands describe all of the packages defined by the
# files given as arguments.
#
# Arguments:
# dir -			Name of the directory in which to create the index.
# args -		Any number of additional arguments, each giving
#			a glob pattern that matches the names of one or
#			more shared libraries or Tcl script files in
#			dir.

proc pkg_mkIndex {args} {
    global errorCode errorInfo
    set first [lindex $args 0]
    set direct [string match "-d*" $first]
    set more ""
    if {$direct} {
	set args [lrange $args 1 end]
	set more " -direct"
    }
    if {[llength $args] == 0} {
	return -code error "wrong # args: should be\
		\"pkg_mkIndex ?-direct? dir ?pattern ...?\"";
    }
    set dir [lindex $args 0]
    set patternList [lrange $args 1 end]
    if {[llength $patternList] == 0} {
	set patternList [list "*.tcl" "*[info sharedlibextension]"]
    }
    append index "# Tcl package index file, version 1.1\n"
    append index "# This file is generated by the \"pkg_mkIndex$more\" command\n"
    append index "# and sourced either when an application starts up or\n"
    append index "# by a \"package unknown\" script.  It invokes the\n"
    append index "# \"package ifneeded\" command to set up package-related\n"
    append index "# information so that packages will be loaded automatically\n"
    append index "# in response to \"package require\" commands.  When this\n"
    append index "# script is sourced, the variable \$dir must contain the\n"
    append index "# full path name of this file's directory.\n"
    set oldDir [pwd]
    cd $dir
    foreach file [eval glob $patternList] {
	# For each file, figure out what commands and packages it provides.
	# To do this, create a child interpreter, load the file into the
	# interpreter, and get a list of the new commands and packages
	# that are defined.  Define an empty "package unknown" script so
	# that there are no recursive package inclusions.

	set c [interp create]

	# If Tk is loaded in the parent interpreter, load it into the
	# child also, in case the extension depends on it.

	foreach pkg [info loaded] {
	    if {[lindex $pkg 1] == "Tk"} {
		$c eval {set argv {-geometry +0+0}}
		load [lindex $pkg 0] Tk $c
		break
	    }
	}
	$c eval [list set __file $file]
	$c eval [list set __direct $direct]
	if {[catch {
	    $c eval {
		proc __dummy args {}
		rename package __package_orig
		proc package {what args} {
		    switch -- $what {
			require { return ; # ignore transitive requires }
			default { eval __package_orig [list $what] $args }
		    }
		}
		if {!$__direct} {
		    proc __pkgGetAllNamespaces {{root {}}} {
			set list $root
			foreach ns [namespace children $root] {
			    eval lappend list [__pkgGetAllNamespaces $ns]
			}
			return $list
		    }
		    set __origCmds [info commands]
		}
		package unknown __dummy

		set dir ""		;# in case file is pkgIndex.tcl

		# Try to load the file if it has the shared library extension,
		# otherwise source it.  It's important not to try to load
		# files that aren't shared libraries, because on some systems
		# (like SunOS) the loader will abort the whole application
		# when it gets an error.

		if {[string compare [file extension $__file] \
			[info sharedlibextension]] == 0} {

		    # The "file join ." command below is necessary.  Without
		    # it, if the file name has no \'s and we're on UNIX, the
		    # load command will invoke the LD_LIBRARY_PATH search
		    # mechanism, which could cause the wrong file to be used.

		    if {[catch {load [file join . $__file]} __msg]} {
			tclLog "warning: error while loading $__file: $__msg"
		    }
		    set __type load
		} else {
		    if {[catch {source $__file} __msg]} {
			tclLog "warning: error while sourcing $__file: $__msg"
		    }
		    set __type source
		}
		# Using __ variable names to avoid potential namespaces
		# clash, even here in post processing because the
		# loaded package could have set up traces,...
		if {!$__direct} {
		    foreach __ns [__pkgGetAllNamespaces] {
			namespace import ${__ns}::*
		    }
		    foreach __i [info commands] {
			set __cmds($__i) 1
		    }
		    foreach __i $__origCmds {
			catch {unset __cmds($__i)}
		    }
		    foreach __i [array names __cmds] {
			# reverse engineer which namespace a command comes from
			set __absolute [namespace origin $__i]
			if {[string compare ::$__i $__absolute] != 0} {
			    set __cmds($__absolute) 1
			    unset __cmds($__i)
			}
		    }
		}
		set __pkgs {}
		foreach __i [package names] {
		    if {([string compare [package provide $__i] ""] != 0)
			    && ([string compare $__i Tcl] != 0)
			    && ([string compare $__i Tk] != 0)} {
			lappend __pkgs [list $__i [package provide $__i]]
		    }
		}
	    }
	} msg]} {
	    tclLog "error while loading or sourcing $file: $msg"
	}
	set type [$c eval set __type]
	set cmds [lsort [$c eval array names __cmds]]
	set pkgs [$c eval set __pkgs]
	if {[llength $pkgs] > 1} {
	    tclLog "warning: \"$file\" provides more than one package ($pkgs)"
	}
	foreach pkg $pkgs {
	    # cmds is empty/not used in the direct case
	    lappend files($pkg) [list $file $type $cmds]
	}
	interp delete $c
    }
    foreach pkg [lsort [array names files]] {
	append index "\npackage ifneeded $pkg "
	if {$direct} {
	    set cmdList {}
	    foreach elem $files($pkg) {
		set file [lindex $elem 0]
		set type [lindex $elem 1]
		lappend cmdList "\[list $type \[file join \$dir\
			[list $file]\]\]"
	    }
	    append index [join $cmdList "\\n"]
	} else {
	    append index "\[list tclPkgSetup \$dir [lrange $pkg 0 0]\
		    [lrange $pkg 1 1] [list $files($pkg)]\]"
	}
    }
    set f [open pkgIndex.tcl w]
    puts $f $index
    close $f
    cd $oldDir
}

# tclPkgSetup --
# This is a utility procedure use by pkgIndex.tcl files.  It is invoked
# as part of a "package ifneeded" script.  It calls "package provide"
# to indicate that a package is available, then sets entries in the
# auto_index array so that the package's files will be auto-loaded when
# the commands are used.
#
# Arguments:
# dir -			Directory containing all the files for this package.
# pkg -			Name of the package (no version number).
# version -		Version number for the package, such as 2.1.3.
# files -		List of files that constitute the package.  Each
#			element is a sub-list with three elements.  The first
#			is the name of a file relative to $dir, the second is
#			"load" or "source", indicating whether the file is a
#			loadable binary or a script to source, and the third
#			is a list of commands defined by this file.

proc tclPkgSetup {dir pkg version files} {
    global auto_index

    package provide $pkg $version
    foreach fileInfo $files {
	set f [lindex $fileInfo 0]
	set type [lindex $fileInfo 1]
	foreach cmd [lindex $fileInfo 2] {
	    if {$type == "load"} {
		set auto_index($cmd) [list load [file join $dir $f] $pkg]
	    } else {
		set auto_index($cmd) [list source [file join $dir $f]]
	    } 
	}
    }
}

# tclMacPkgSearch --
# The procedure is used on the Macintosh to search a given directory for files
# with a TEXT resource named "pkgIndex".  If it exists it is sourced in to the
# interpreter to setup the package database.

proc tclMacPkgSearch {dir} {
    foreach x [glob -nocomplain [file join $dir *.shlb]] {
	if {[file isfile $x]} {
	    set res [resource open $x]
	    foreach y [resource list TEXT $res] {
		if {$y == "pkgIndex"} {source -rsrc pkgIndex}
	    }
	    catch {resource close $res}
	}
    }
}

# tclPkgUnknown --
# This procedure provides the default for the "package unknown" function.
# It is invoked when a package that's needed can't be found.  It scans
# the auto_path directories and their immediate children looking for
# pkgIndex.tcl files and sources any such files that are found to setup
# the package database.  (On the Macintosh we also search for pkgIndex
# TEXT resources in all files.)
#
# Arguments:
# name -		Name of desired package.  Not used.
# version -		Version of desired package.  Not used.
# exact -		Either "-exact" or omitted.  Not used.

proc tclPkgUnknown {name version {exact {}}} {
    global auto_path tcl_platform env

    if {![info exists auto_path]} {
	return
    }
    for {set i [expr {[llength $auto_path] - 1}]} {$i >= 0} {incr i -1} {
	# we can't use glob in safe interps, so enclose the following
	# in a catch statement
	catch {
	    foreach file [glob -nocomplain [file join [lindex $auto_path $i] \
		    * pkgIndex.tcl]] {
		set dir [file dirname $file]
		if {[catch {source $file} msg]} {
		    tclLog "error reading package index file $file: $msg"
		}
	    }
        }
	set dir [lindex $auto_path $i]
	set file [file join $dir pkgIndex.tcl]
	# safe interps usually don't have "file readable", nor stderr channel
	if {[interp issafe] || [file readable $file]} {
	    if {[catch {source $file} msg] && ![interp issafe]}  {
		tclLog "error reading package index file $file: $msg"
	    }
	}
	# On the Macintosh we also look in the resource fork 
	# of shared libraries
	# We can't use tclMacPkgSearch in safe interps because it uses glob
	if {(![interp issafe]) && ($tcl_platform(platform) == "macintosh")} {
	    set dir [lindex $auto_path $i]
	    tclMacPkgSearch $dir
	    foreach x [glob -nocomplain [file join $dir *]] {
		if {[file isdirectory $x]} {
		    set dir $x
		    tclMacPkgSearch $dir
		}
	    }
	}
    }
}


