diff CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/tcl8.6/tm.tcl @ 69:33d812a61356

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 17:55:14 -0400
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/tcl8.6/tm.tcl	Tue Mar 18 17:55:14 2025 -0400
@@ -0,0 +1,380 @@
+# -*- tcl -*-
+#
+# Searching for Tcl Modules. Defines a procedure, declares it as the primary
+# command for finding packages, however also uses the former 'package unknown'
+# command as a fallback.
+#
+# Locates all possible packages in a directory via a less restricted glob. The
+# targeted directory is derived from the name of the requested package, i.e.
+# the TM scan will look only at directories which can contain the requested
+# package. It will register all packages it found in the directory so that
+# future requests have a higher chance of being fulfilled by the ifneeded
+# database without having to come to us again.
+#
+# We do not remember where we have been and simply rescan targeted directories
+# when invoked again. The reasoning is this:
+#
+# - The only way we get back to the same directory is if someone is trying to
+#   [package require] something that wasn't there on the first scan.
+#
+#   Either
+#   1) It is there now:  If we rescan, you get it; if not you don't.
+#
+#      This covers the possibility that the application asked for a package
+#      late, and the package was actually added to the installation after the
+#      application was started. It shoukld still be able to find it.
+#
+#   2) It still is not there: Either way, you don't get it, but the rescan
+#      takes time. This is however an error case and we dont't care that much
+#      about it
+#
+#   3) It was there the first time; but for some reason a "package forget" has
+#      been run, and "package" doesn't know about it anymore.
+#
+#      This can be an indication that the application wishes to reload some
+#      functionality. And should work as well.
+#
+# Note that this also strikes a balance between doing a glob targeting a
+# single package, and thus most likely requiring multiple globs of the same
+# directory when the application is asking for many packages, and trying to
+# glob for _everything_ in all subdirectories when looking for a package,
+# which comes with a heavy startup cost.
+#
+# We scan for regular packages only if no satisfying module was found.
+
+namespace eval ::tcl::tm {
+    # Default paths. None yet.
+
+    variable paths {}
+
+    # The regex pattern a file name has to match to make it a Tcl Module.
+
+    set pkgpattern {^([_[:alpha:]][:_[:alnum:]]*)-([[:digit:]].*)[.]tm$}
+
+    # Export the public API
+
+    namespace export path
+    namespace ensemble create -command path -subcommands {add remove list}
+}
+
+# ::tcl::tm::path implementations --
+#
+#	Public API to the module path. See specification.
+#
+# Arguments
+#	cmd -	The subcommand to execute
+#	args -	The paths to add/remove. Must not appear querying the
+#		path with 'list'.
+#
+# Results
+#	No result for subcommands 'add' and 'remove'. A list of paths for
+#	'list'.
+#
+# Sideeffects
+#	The subcommands 'add' and 'remove' manipulate the list of paths to
+#	search for Tcl Modules. The subcommand 'list' has no sideeffects.
+
+proc ::tcl::tm::add {args} {
+    # PART OF THE ::tcl::tm::path ENSEMBLE
+    #
+    # The path is added at the head to the list of module paths.
+    #
+    # The command enforces the restriction that no path may be an ancestor
+    # directory of any other path on the list. If the new path violates this
+    # restriction an error wil be raised.
+    #
+    # If the path is already present as is no error will be raised and no
+    # action will be taken.
+
+    variable paths
+
+    # We use a copy of the path as source during validation, and extend it as
+    # well. Because we not only have to detect if the new paths are bogus with
+    # respect to the existing paths, but also between themselves. Otherwise we
+    # can still add bogus paths, by specifying them in a single call. This
+    # makes the use of the new paths simpler as well, a trivial assignment of
+    # the collected paths to the official state var.
+
+    set newpaths $paths
+    foreach p $args {
+	if {$p in $newpaths} {
+	    # Ignore a path already on the list.
+	    continue
+	}
+
+	# Search for paths which are subdirectories of the new one. If there
+	# are any then the new path violates the restriction about ancestors.
+
+	set pos [lsearch -glob $newpaths ${p}/*]
+	# Cannot use "in", we need the position for the message.
+	if {$pos >= 0} {
+	    return -code error \
+		"$p is ancestor of existing module path [lindex $newpaths $pos]."
+	}
+
+	# Now look for existing paths which are ancestors of the new one. This
+	# reverse question forces us to loop over the existing paths, as each
+	# element is the pattern, not the new path :(
+
+	foreach ep $newpaths {
+	    if {[string match ${ep}/* $p]} {
+		return -code error \
+		    "$p is subdirectory of existing module path $ep."
+	    }
+	}
+
+	set newpaths [linsert $newpaths 0 $p]
+    }
+
+    # The validation of the input is complete and successful, and everything
+    # in newpaths is either an old path, or added. We can now extend the
+    # official list of paths, a simple assignment is sufficient.
+
+    set paths $newpaths
+    return
+}
+
+proc ::tcl::tm::remove {args} {
+    # PART OF THE ::tcl::tm::path ENSEMBLE
+    #
+    # Removes the path from the list of module paths. The command is silently
+    # ignored if the path is not on the list.
+
+    variable paths
+
+    foreach p $args {
+	set pos [lsearch -exact $paths $p]
+	if {$pos >= 0} {
+	    set paths [lreplace $paths $pos $pos]
+	}
+    }
+}
+
+proc ::tcl::tm::list {} {
+    # PART OF THE ::tcl::tm::path ENSEMBLE
+
+    variable paths
+    return  $paths
+}
+
+# ::tcl::tm::UnknownHandler --
+#
+#	Unknown handler for Tcl Modules, i.e. packages in module form.
+#
+# Arguments
+#	original	- Original [package unknown] procedure.
+#	name		- Name of desired package.
+#	version		- Version of desired package. Can be the
+#			  empty string.
+#	exact		- Either -exact or ommitted.
+#
+#	Name, version, and exact are used to determine satisfaction. The
+#	original is called iff no satisfaction was achieved. The name is also
+#	used to compute the directory to target in the search.
+#
+# Results
+#	None.
+#
+# Sideeffects
+#	May populate the package ifneeded database with additional provide
+#	scripts.
+
+proc ::tcl::tm::UnknownHandler {original name args} {
+    # Import the list of paths to search for packages in module form.
+    # Import the pattern used to check package names in detail.
+
+    variable paths
+    variable pkgpattern
+
+    # Without paths to search we can do nothing. (Except falling back to the
+    # regular search).
+
+    if {[llength $paths]} {
+	set pkgpath [string map {:: /} $name]
+	set pkgroot [file dirname $pkgpath]
+	if {$pkgroot eq "."} {
+	    set pkgroot ""
+	}
+
+	# We don't remember a copy of the paths while looping. Tcl Modules are
+	# unable to change the list while we are searching for them. This also
+	# simplifies the loop, as we cannot get additional directories while
+	# iterating over the list. A simple foreach is sufficient.
+
+	set satisfied 0
+	foreach path $paths {
+	    if {![interp issafe] && ![file exists $path]} {
+		continue
+	    }
+	    set currentsearchpath [file join $path $pkgroot]
+	    if {![interp issafe] && ![file exists $currentsearchpath]} {
+		continue
+	    }
+	    set strip [llength [file split $path]]
+
+	    # Get the module files out of the subdirectories.
+	    # - Safe Base interpreters have a restricted "glob" command that
+	    #   works in this case.
+	    # - The "catch" was essential when there was no safe glob and every
+	    #   call in a safe interp failed; it is retained only for corner
+	    #   cases in which the eventual call to glob returns an error.
+
+	    catch {
+		# We always look for _all_ possible modules in the current
+		# path, to get the max result out of the glob.
+
+		foreach file [glob -nocomplain -directory $currentsearchpath *.tm] {
+		    set pkgfilename [join [lrange [file split $file] $strip end] ::]
+
+		    if {![regexp -- $pkgpattern $pkgfilename --> pkgname pkgversion]} {
+			# Ignore everything not matching our pattern for
+			# package names.
+			continue
+		    }
+		    try {
+			package vcompare $pkgversion 0
+		    } on error {} {
+			# Ignore everything where the version part is not
+			# acceptable to "package vcompare".
+			continue
+		    }
+
+		    if {([package ifneeded $pkgname $pkgversion] ne {})
+			    && (![interp issafe])
+		    } {
+			# There's already a provide script registered for
+			# this version of this package.  Since all units of
+			# code claiming to be the same version of the same
+			# package ought to be identical, just stick with
+			# the one we already have.
+			# This does not apply to Safe Base interpreters because
+			# the token-to-directory mapping may have changed.
+			continue
+		    }
+
+		    # We have found a candidate, generate a "provide script"
+		    # for it, and remember it.  Note that we are using ::list
+		    # to do this; locally [list] means something else without
+		    # the namespace specifier.
+
+		    # NOTE. When making changes to the format of the provide
+		    # command generated below CHECK that the 'LOCATE'
+		    # procedure in core file 'platform/shell.tcl' still
+		    # understands it, or, if not, update its implementation
+		    # appropriately.
+		    #
+		    # Right now LOCATE's implementation assumes that the path
+		    # of the package file is the last element in the list.
+
+		    package ifneeded $pkgname $pkgversion \
+			"[::list package provide $pkgname $pkgversion];[::list source -encoding utf-8 $file]"
+
+		    # We abort in this unknown handler only if we got a
+		    # satisfying candidate for the requested package.
+		    # Otherwise we still have to fallback to the regular
+		    # package search to complete the processing.
+
+		    if {($pkgname eq $name)
+			    && [package vsatisfies $pkgversion {*}$args]} {
+			set satisfied 1
+
+			# We do not abort the loop, and keep adding provide
+			# scripts for every candidate in the directory, just
+			# remember to not fall back to the regular search
+			# anymore.
+		    }
+		}
+	    }
+	}
+
+	if {$satisfied} {
+	    return
+	}
+    }
+
+    # Fallback to previous command, if existing.  See comment above about
+    # ::list...
+
+    if {[llength $original]} {
+	uplevel 1 $original [::linsert $args 0 $name]
+    }
+}
+
+# ::tcl::tm::Defaults --
+#
+#	Determines the default search paths.
+#
+# Arguments
+#	None
+#
+# Results
+#	None.
+#
+# Sideeffects
+#	May add paths to the list of defaults.
+
+proc ::tcl::tm::Defaults {} {
+    global env tcl_platform
+
+    regexp {^(\d+)\.(\d+)} [package provide Tcl] - major minor
+    set exe [file normalize [info nameofexecutable]]
+
+    # Note that we're using [::list], not [list] because [list] means
+    # something other than [::list] in this namespace.
+    roots [::list \
+	    [file dirname [info library]] \
+	    [file join [file dirname [file dirname $exe]] lib] \
+	    ]
+
+    if {$tcl_platform(platform) eq "windows"} {
+	set sep ";"
+    } else {
+	set sep ":"
+    }
+    for {set n $minor} {$n >= 0} {incr n -1} {
+	foreach ev [::list \
+			TCL${major}.${n}_TM_PATH \
+			TCL${major}_${n}_TM_PATH \
+        ] {
+	    if {![info exists env($ev)]} continue
+	    foreach p [split $env($ev) $sep] {
+		path add $p
+	    }
+	}
+    }
+    return
+}
+
+# ::tcl::tm::roots --
+#
+#	Public API to the module path. See specification.
+#
+# Arguments
+#	paths -	List of 'root' paths to derive search paths from.
+#
+# Results
+#	No result.
+#
+# Sideeffects
+#	Calls 'path add' to paths to the list of module search paths.
+
+proc ::tcl::tm::roots {paths} {
+    regexp {^(\d+)\.(\d+)} [package provide Tcl] - major minor
+    foreach pa $paths {
+	set p [file join $pa tcl$major]
+	for {set n $minor} {$n >= 0} {incr n -1} {
+	    set px [file join $p ${major}.${n}]
+	    if {![interp issafe]} {set px [file normalize $px]}
+	    path add $px
+	}
+	set px [file join $p site-tcl]
+	if {![interp issafe]} {set px [file normalize $px]}
+	path add $px
+    }
+    return
+}
+
+# Initialization. Set up the default paths, then insert the new handler into
+# the chain.
+
+if {![interp issafe]} {::tcl::tm::Defaults}