Mercurial > repos > rliterman > csp2
comparison 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 |
comparison
equal
deleted
inserted
replaced
67:0e9998148a16 | 69:33d812a61356 |
---|---|
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 # Get the module files out of the subdirectories. | |
216 # - Safe Base interpreters have a restricted "glob" command that | |
217 # works in this case. | |
218 # - The "catch" was essential when there was no safe glob and every | |
219 # call in a safe interp failed; it is retained only for corner | |
220 # cases in which the eventual call to glob returns an error. | |
221 | |
222 catch { | |
223 # We always look for _all_ possible modules in the current | |
224 # path, to get the max result out of the glob. | |
225 | |
226 foreach file [glob -nocomplain -directory $currentsearchpath *.tm] { | |
227 set pkgfilename [join [lrange [file split $file] $strip end] ::] | |
228 | |
229 if {![regexp -- $pkgpattern $pkgfilename --> pkgname pkgversion]} { | |
230 # Ignore everything not matching our pattern for | |
231 # package names. | |
232 continue | |
233 } | |
234 try { | |
235 package vcompare $pkgversion 0 | |
236 } on error {} { | |
237 # Ignore everything where the version part is not | |
238 # acceptable to "package vcompare". | |
239 continue | |
240 } | |
241 | |
242 if {([package ifneeded $pkgname $pkgversion] ne {}) | |
243 && (![interp issafe]) | |
244 } { | |
245 # There's already a provide script registered for | |
246 # this version of this package. Since all units of | |
247 # code claiming to be the same version of the same | |
248 # package ought to be identical, just stick with | |
249 # the one we already have. | |
250 # This does not apply to Safe Base interpreters because | |
251 # the token-to-directory mapping may have changed. | |
252 continue | |
253 } | |
254 | |
255 # We have found a candidate, generate a "provide script" | |
256 # for it, and remember it. Note that we are using ::list | |
257 # to do this; locally [list] means something else without | |
258 # the namespace specifier. | |
259 | |
260 # NOTE. When making changes to the format of the provide | |
261 # command generated below CHECK that the 'LOCATE' | |
262 # procedure in core file 'platform/shell.tcl' still | |
263 # understands it, or, if not, update its implementation | |
264 # appropriately. | |
265 # | |
266 # Right now LOCATE's implementation assumes that the path | |
267 # of the package file is the last element in the list. | |
268 | |
269 package ifneeded $pkgname $pkgversion \ | |
270 "[::list package provide $pkgname $pkgversion];[::list source -encoding utf-8 $file]" | |
271 | |
272 # We abort in this unknown handler only if we got a | |
273 # satisfying candidate for the requested package. | |
274 # Otherwise we still have to fallback to the regular | |
275 # package search to complete the processing. | |
276 | |
277 if {($pkgname eq $name) | |
278 && [package vsatisfies $pkgversion {*}$args]} { | |
279 set satisfied 1 | |
280 | |
281 # We do not abort the loop, and keep adding provide | |
282 # scripts for every candidate in the directory, just | |
283 # remember to not fall back to the regular search | |
284 # anymore. | |
285 } | |
286 } | |
287 } | |
288 } | |
289 | |
290 if {$satisfied} { | |
291 return | |
292 } | |
293 } | |
294 | |
295 # Fallback to previous command, if existing. See comment above about | |
296 # ::list... | |
297 | |
298 if {[llength $original]} { | |
299 uplevel 1 $original [::linsert $args 0 $name] | |
300 } | |
301 } | |
302 | |
303 # ::tcl::tm::Defaults -- | |
304 # | |
305 # Determines the default search paths. | |
306 # | |
307 # Arguments | |
308 # None | |
309 # | |
310 # Results | |
311 # None. | |
312 # | |
313 # Sideeffects | |
314 # May add paths to the list of defaults. | |
315 | |
316 proc ::tcl::tm::Defaults {} { | |
317 global env tcl_platform | |
318 | |
319 regexp {^(\d+)\.(\d+)} [package provide Tcl] - major minor | |
320 set exe [file normalize [info nameofexecutable]] | |
321 | |
322 # Note that we're using [::list], not [list] because [list] means | |
323 # something other than [::list] in this namespace. | |
324 roots [::list \ | |
325 [file dirname [info library]] \ | |
326 [file join [file dirname [file dirname $exe]] lib] \ | |
327 ] | |
328 | |
329 if {$tcl_platform(platform) eq "windows"} { | |
330 set sep ";" | |
331 } else { | |
332 set sep ":" | |
333 } | |
334 for {set n $minor} {$n >= 0} {incr n -1} { | |
335 foreach ev [::list \ | |
336 TCL${major}.${n}_TM_PATH \ | |
337 TCL${major}_${n}_TM_PATH \ | |
338 ] { | |
339 if {![info exists env($ev)]} continue | |
340 foreach p [split $env($ev) $sep] { | |
341 path add $p | |
342 } | |
343 } | |
344 } | |
345 return | |
346 } | |
347 | |
348 # ::tcl::tm::roots -- | |
349 # | |
350 # Public API to the module path. See specification. | |
351 # | |
352 # Arguments | |
353 # paths - List of 'root' paths to derive search paths from. | |
354 # | |
355 # Results | |
356 # No result. | |
357 # | |
358 # Sideeffects | |
359 # Calls 'path add' to paths to the list of module search paths. | |
360 | |
361 proc ::tcl::tm::roots {paths} { | |
362 regexp {^(\d+)\.(\d+)} [package provide Tcl] - major minor | |
363 foreach pa $paths { | |
364 set p [file join $pa tcl$major] | |
365 for {set n $minor} {$n >= 0} {incr n -1} { | |
366 set px [file join $p ${major}.${n}] | |
367 if {![interp issafe]} {set px [file normalize $px]} | |
368 path add $px | |
369 } | |
370 set px [file join $p site-tcl] | |
371 if {![interp issafe]} {set px [file normalize $px]} | |
372 path add $px | |
373 } | |
374 return | |
375 } | |
376 | |
377 # Initialization. Set up the default paths, then insert the new handler into | |
378 # the chain. | |
379 | |
380 if {![interp issafe]} {::tcl::tm::Defaults} |