figenc

[RADIOACTIVE] rsa and symmetric key encryption scripts and executables
git clone git://git.figbert.com/figenc.git
Log | Files | Refs | README

tm.tcl (11633B)


      1 # -*- tcl -*-
      2 #
      3 # Searching for Tcl Modules. Defines a procedure, declares it as the primary
      4 # command for finding packages, however also uses the former 'package unknown'
      5 # command as a fallback.
      6 #
      7 # Locates all possible packages in a directory via a less restricted glob. The
      8 # targeted directory is derived from the name of the requested package, i.e.
      9 # the TM scan will look only at directories which can contain the requested
     10 # package. It will register all packages it found in the directory so that
     11 # future requests have a higher chance of being fulfilled by the ifneeded
     12 # database without having to come to us again.
     13 #
     14 # We do not remember where we have been and simply rescan targeted directories
     15 # when invoked again. The reasoning is this:
     16 #
     17 # - The only way we get back to the same directory is if someone is trying to
     18 #   [package require] something that wasn't there on the first scan.
     19 #
     20 #   Either
     21 #   1) It is there now:  If we rescan, you get it; if not you don't.
     22 #
     23 #      This covers the possibility that the application asked for a package
     24 #      late, and the package was actually added to the installation after the
     25 #      application was started. It shoukld still be able to find it.
     26 #
     27 #   2) It still is not there: Either way, you don't get it, but the rescan
     28 #      takes time. This is however an error case and we dont't care that much
     29 #      about it
     30 #
     31 #   3) It was there the first time; but for some reason a "package forget" has
     32 #      been run, and "package" doesn't know about it anymore.
     33 #
     34 #      This can be an indication that the application wishes to reload some
     35 #      functionality. And should work as well.
     36 #
     37 # Note that this also strikes a balance between doing a glob targeting a
     38 # single package, and thus most likely requiring multiple globs of the same
     39 # directory when the application is asking for many packages, and trying to
     40 # glob for _everything_ in all subdirectories when looking for a package,
     41 # which comes with a heavy startup cost.
     42 #
     43 # We scan for regular packages only if no satisfying module was found.
     44 
     45 namespace eval ::tcl::tm {
     46     # Default paths. None yet.
     47 
     48     variable paths {}
     49 
     50     # The regex pattern a file name has to match to make it a Tcl Module.
     51 
     52     set pkgpattern {^([_[:alpha:]][:_[:alnum:]]*)-([[:digit:]].*)[.]tm$}
     53 
     54     # Export the public API
     55 
     56     namespace export path
     57     namespace ensemble create -command path -subcommands {add remove list}
     58 }
     59 
     60 # ::tcl::tm::path implementations --
     61 #
     62 #	Public API to the module path. See specification.
     63 #
     64 # Arguments
     65 #	cmd -	The subcommand to execute
     66 #	args -	The paths to add/remove. Must not appear querying the
     67 #		path with 'list'.
     68 #
     69 # Results
     70 #	No result for subcommands 'add' and 'remove'. A list of paths for
     71 #	'list'.
     72 #
     73 # Sideeffects
     74 #	The subcommands 'add' and 'remove' manipulate the list of paths to
     75 #	search for Tcl Modules. The subcommand 'list' has no sideeffects.
     76 
     77 proc ::tcl::tm::add {args} {
     78     # PART OF THE ::tcl::tm::path ENSEMBLE
     79     #
     80     # The path is added at the head to the list of module paths.
     81     #
     82     # The command enforces the restriction that no path may be an ancestor
     83     # directory of any other path on the list. If the new path violates this
     84     # restriction an error wil be raised.
     85     #
     86     # If the path is already present as is no error will be raised and no
     87     # action will be taken.
     88 
     89     variable paths
     90 
     91     # We use a copy of the path as source during validation, and extend it as
     92     # well. Because we not only have to detect if the new paths are bogus with
     93     # respect to the existing paths, but also between themselves. Otherwise we
     94     # can still add bogus paths, by specifying them in a single call. This
     95     # makes the use of the new paths simpler as well, a trivial assignment of
     96     # the collected paths to the official state var.
     97 
     98     set newpaths $paths
     99     foreach p $args {
    100 	if {$p in $newpaths} {
    101 	    # Ignore a path already on the list.
    102 	    continue
    103 	}
    104 
    105 	# Search for paths which are subdirectories of the new one. If there
    106 	# are any then the new path violates the restriction about ancestors.
    107 
    108 	set pos [lsearch -glob $newpaths ${p}/*]
    109 	# Cannot use "in", we need the position for the message.
    110 	if {$pos >= 0} {
    111 	    return -code error \
    112 		"$p is ancestor of existing module path [lindex $newpaths $pos]."
    113 	}
    114 
    115 	# Now look for existing paths which are ancestors of the new one. This
    116 	# reverse question forces us to loop over the existing paths, as each
    117 	# element is the pattern, not the new path :(
    118 
    119 	foreach ep $newpaths {
    120 	    if {[string match ${ep}/* $p]} {
    121 		return -code error \
    122 		    "$p is subdirectory of existing module path $ep."
    123 	    }
    124 	}
    125 
    126 	set newpaths [linsert $newpaths 0 $p]
    127     }
    128 
    129     # The validation of the input is complete and successful, and everything
    130     # in newpaths is either an old path, or added. We can now extend the
    131     # official list of paths, a simple assignment is sufficient.
    132 
    133     set paths $newpaths
    134     return
    135 }
    136 
    137 proc ::tcl::tm::remove {args} {
    138     # PART OF THE ::tcl::tm::path ENSEMBLE
    139     #
    140     # Removes the path from the list of module paths. The command is silently
    141     # ignored if the path is not on the list.
    142 
    143     variable paths
    144 
    145     foreach p $args {
    146 	set pos [lsearch -exact $paths $p]
    147 	if {$pos >= 0} {
    148 	    set paths [lreplace $paths $pos $pos]
    149 	}
    150     }
    151 }
    152 
    153 proc ::tcl::tm::list {} {
    154     # PART OF THE ::tcl::tm::path ENSEMBLE
    155 
    156     variable paths
    157     return  $paths
    158 }
    159 
    160 # ::tcl::tm::UnknownHandler --
    161 #
    162 #	Unknown handler for Tcl Modules, i.e. packages in module form.
    163 #
    164 # Arguments
    165 #	original	- Original [package unknown] procedure.
    166 #	name		- Name of desired package.
    167 #	version		- Version of desired package. Can be the
    168 #			  empty string.
    169 #	exact		- Either -exact or ommitted.
    170 #
    171 #	Name, version, and exact are used to determine satisfaction. The
    172 #	original is called iff no satisfaction was achieved. The name is also
    173 #	used to compute the directory to target in the search.
    174 #
    175 # Results
    176 #	None.
    177 #
    178 # Sideeffects
    179 #	May populate the package ifneeded database with additional provide
    180 #	scripts.
    181 
    182 proc ::tcl::tm::UnknownHandler {original name args} {
    183     # Import the list of paths to search for packages in module form.
    184     # Import the pattern used to check package names in detail.
    185 
    186     variable paths
    187     variable pkgpattern
    188 
    189     # Without paths to search we can do nothing. (Except falling back to the
    190     # regular search).
    191 
    192     if {[llength $paths]} {
    193 	set pkgpath [string map {:: /} $name]
    194 	set pkgroot [file dirname $pkgpath]
    195 	if {$pkgroot eq "."} {
    196 	    set pkgroot ""
    197 	}
    198 
    199 	# We don't remember a copy of the paths while looping. Tcl Modules are
    200 	# unable to change the list while we are searching for them. This also
    201 	# simplifies the loop, as we cannot get additional directories while
    202 	# iterating over the list. A simple foreach is sufficient.
    203 
    204 	set satisfied 0
    205 	foreach path $paths {
    206 	    if {![interp issafe] && ![file exists $path]} {
    207 		continue
    208 	    }
    209 	    set currentsearchpath [file join $path $pkgroot]
    210 	    if {![interp issafe] && ![file exists $currentsearchpath]} {
    211 		continue
    212 	    }
    213 	    set strip [llength [file split $path]]
    214 
    215 	    # We can't use glob in safe interps, so enclose the following in a
    216 	    # catch statement, where we get the module files out of the
    217 	    # subdirectories. In other words, Tcl Modules are not-functional
    218 	    # in such an interpreter. This is the same as for the command
    219 	    # "tclPkgUnknown", i.e. the search for regular packages.
    220 
    221 	    catch {
    222 		# We always look for _all_ possible modules in the current
    223 		# path, to get the max result out of the glob.
    224 
    225 		foreach file [glob -nocomplain -directory $currentsearchpath *.tm] {
    226 		    set pkgfilename [join [lrange [file split $file] $strip end] ::]
    227 
    228 		    if {![regexp -- $pkgpattern $pkgfilename --> pkgname pkgversion]} {
    229 			# Ignore everything not matching our pattern for
    230 			# package names.
    231 			continue
    232 		    }
    233 		    try {
    234 			package vcompare $pkgversion 0
    235 		    } on error {} {
    236 			# Ignore everything where the version part is not
    237 			# acceptable to "package vcompare".
    238 			continue
    239 		    }
    240 
    241 		    if {[package ifneeded $pkgname $pkgversion] ne {}} {
    242 			# There's already a provide script registered for
    243 			# this version of this package.  Since all units of
    244 			# code claiming to be the same version of the same
    245 			# package ought to be identical, just stick with
    246 			# the one we already have.
    247 			continue
    248 		    }
    249 
    250 		    # We have found a candidate, generate a "provide script"
    251 		    # for it, and remember it.  Note that we are using ::list
    252 		    # to do this; locally [list] means something else without
    253 		    # the namespace specifier.
    254 
    255 		    # NOTE. When making changes to the format of the provide
    256 		    # command generated below CHECK that the 'LOCATE'
    257 		    # procedure in core file 'platform/shell.tcl' still
    258 		    # understands it, or, if not, update its implementation
    259 		    # appropriately.
    260 		    #
    261 		    # Right now LOCATE's implementation assumes that the path
    262 		    # of the package file is the last element in the list.
    263 
    264 		    package ifneeded $pkgname $pkgversion \
    265 			"[::list package provide $pkgname $pkgversion];[::list source -encoding utf-8 $file]"
    266 
    267 		    # We abort in this unknown handler only if we got a
    268 		    # satisfying candidate for the requested package.
    269 		    # Otherwise we still have to fallback to the regular
    270 		    # package search to complete the processing.
    271 
    272 		    if {($pkgname eq $name)
    273 			    && [package vsatisfies $pkgversion {*}$args]} {
    274 			set satisfied 1
    275 
    276 			# We do not abort the loop, and keep adding provide
    277 			# scripts for every candidate in the directory, just
    278 			# remember to not fall back to the regular search
    279 			# anymore.
    280 		    }
    281 		}
    282 	    }
    283 	}
    284 
    285 	if {$satisfied} {
    286 	    return
    287 	}
    288     }
    289 
    290     # Fallback to previous command, if existing.  See comment above about
    291     # ::list...
    292 
    293     if {[llength $original]} {
    294 	uplevel 1 $original [::linsert $args 0 $name]
    295     }
    296 }
    297 
    298 # ::tcl::tm::Defaults --
    299 #
    300 #	Determines the default search paths.
    301 #
    302 # Arguments
    303 #	None
    304 #
    305 # Results
    306 #	None.
    307 #
    308 # Sideeffects
    309 #	May add paths to the list of defaults.
    310 
    311 proc ::tcl::tm::Defaults {} {
    312     global env tcl_platform
    313 
    314     lassign [split [info tclversion] .] major minor
    315     set exe [file normalize [info nameofexecutable]]
    316 
    317     # Note that we're using [::list], not [list] because [list] means
    318     # something other than [::list] in this namespace.
    319     roots [::list \
    320 	    [file dirname [info library]] \
    321 	    [file join [file dirname [file dirname $exe]] lib] \
    322 	    ]
    323 
    324     if {$tcl_platform(platform) eq "windows"} {
    325 	set sep ";"
    326     } else {
    327 	set sep ":"
    328     }
    329     for {set n $minor} {$n >= 0} {incr n -1} {
    330 	foreach ev [::list \
    331 			TCL${major}.${n}_TM_PATH \
    332 			TCL${major}_${n}_TM_PATH \
    333         ] {
    334 	    if {![info exists env($ev)]} continue
    335 	    foreach p [split $env($ev) $sep] {
    336 		path add $p
    337 	    }
    338 	}
    339     }
    340     return
    341 }
    342 
    343 # ::tcl::tm::roots --
    344 #
    345 #	Public API to the module path. See specification.
    346 #
    347 # Arguments
    348 #	paths -	List of 'root' paths to derive search paths from.
    349 #
    350 # Results
    351 #	No result.
    352 #
    353 # Sideeffects
    354 #	Calls 'path add' to paths to the list of module search paths.
    355 
    356 proc ::tcl::tm::roots {paths} {
    357     regexp {^(\d+)\.(\d+)} [package present Tcl] - major minor
    358     foreach pa $paths {
    359 	set p [file join $pa tcl$major]
    360 	for {set n $minor} {$n >= 0} {incr n -1} {
    361 	    set px [file join $p ${major}.${n}]
    362 	    if {![interp issafe]} {set px [file normalize $px]}
    363 	    path add $px
    364 	}
    365 	set px [file join $p site-tcl]
    366 	if {![interp issafe]} {set px [file normalize $px]}
    367 	path add $px
    368     }
    369     return
    370 }
    371 
    372 # Initialization. Set up the default paths, then insert the new handler into
    373 # the chain.
    374 
    375 if {![interp issafe]} {::tcl::tm::Defaults}