After importing procs from a namespace with force option, the procs in the global namespace are used by the auto_load in certain conditions - namespaces

I've been working on a project where scripts are written for a single hardware (say Type A) and have lot of procs very specific for that particular hardware. And now when I'm trying use the same scripts for a new hardware (say Type B) I thought of the following approach,
Re-define all the procs in a new library file within a new namespace.
When the main script starts check the hardware type.
a. If type A do nothing. The procs from the global namespace (legacy) gets used
b. If type B do a "namespace import -force" from the library to the global namespace.
The problem here is, the project uses auto_load so when I'm importing from the new library the legacy procs are not in the context yet. When a proc that is common to both the hardwares is called and if the file containing this proc also has another proc that was redefined for Type B, then that proc gets overwritten by the global namespace proc (legacy Type A) even though I have done a force import earlier.
If the wordings are difficult to follow, refer the below sample code and it's output. If the dummy proc is moved from TypeB.tcl to a new file, then there is no issue.
[wizard # work]$ cat main.tcl
#!/usr/bin/tclsh
auto_mkindex ./
set auto_path "$auto_path ./"
namespace import -force TypeB::*
print_name
dummy
print_name
[wizard # work]$ cat TypeA.tcl
proc print_name { } {
puts "From the TypeA"
}
proc dummy {} {
puts "Do nothing"
}
[wizard # work]$ cat TypeB.tcl
namespace eval TypeB {
namespace export *
proc print_name {} {
puts "From the TypeB"
}
}
[wizard # work]$ ./main.tcl
From the TypeB
Do nothing
From the TypeA
[wizard # work]$

Consider what auto_mkindex does: it creates a tclIndex file that contains
set auto_index(print_name) [list source [file join $dir TypeA.tcl]]
set auto_index(dummy) [list source [file join $dir TypeA.tcl]]
set auto_index(::TypeB::print_name) [list source [file join $dir TypeB.tcl]]
When you namespace import TypeB::*, then tcl executes source [file join $dir TypeB.tcl] and the print_name proc is pulled into the global namespace.
What happens when you call dummy? this: source [file join $dir TypeA.tcl] -- that pulls the dummy proc into the global namespace. But at the same time, the print_name proc is redefined.
At this point, you can still call TypeB::print_name
You'll have to do one of:
restructure your code,
re-execute the namespace import TypeB::*, or
be very careful about the order in which you call things, for instance by calling dummy first.

Related

TCL how protect from override?

I have 2 scripts
listing file 1.mac:
proc setByUpvar { value } {
return [expr {$value *2}]
}
set ex_result [catch {
set c [setByUpvar 12 ]
echo "$c [argument -this]"
sym run macro -file C:/Custom/2.mac
echo "$c [argument -this]"
} errors]
listing file 2.mac:
proc setByUpvar { value } {
return [expr {$value *2}]
}
set ex_result [catch {
set c [setByUpvar 4]
echo "$c [argument -this]"
} errors]
first macro calls second macro
after run file 2.mac, in 1.mac variable $c has value from 2.mac
How protect value $c in 1.mac from overriding in 2.mac?
changing variable name in 2.mac not good for me
Tcl normally doesn't protect you against that. Script files are not normally isolated from each other. By policy/design.
For light isolation, you can put the code in the script files inside a namespace eval:
namespace eval ::mac1 {
proc setByUpvar { value } {
return [expr {$value *2}]
}
# Setting variables directly at the namespace-eval level is a bit tricky so avoid that
proc task {} {
variable ex_result
variable errors
variable c
set ex_result [catch {
set c [setByUpvar 12 ]
echo "$c [argument -this]"
sym run macro -file C:/Custom/2.mac
echo "$c [argument -this]"
} errors]
}
task
}
In this case, the code can see the other variables, but not by default. It has to go looking elsewhere.
For strong isolation, create an interpreter (with interp create) and source the script within it. Interpreters can't reach other interpreters (except under very defined circumstances that don't matter here) so the code really can't have any impact on the other script. However, you might lose access to special commands and so on; isolation really is isolation. And this is a comparatively expensive option. (Not as expensive as starting another process.)
set isolated [interp create]
interp eval $isolated [list source 1.mac]
interp delete $isolated
You can strengthen the isolation by making the interpreter be safe (disables OS access) and you can weaken it by poking callback holes with interp alias to provide the special commands you actually want.
proc myFoo args {
puts "this is a callback in the master interpreter: arguments=$args"
}
interp alias $isolated foo {} myFoo
How any of this will interact with the non-standard Tcl commands you're running (sym, argument) is a complete unknown to me. Standard commands are known, as are procedures using them.

Requiring packages

using init.tcl we have refined the clock command for international use. This works fine until I attempt to initialize a variable within a package with the clock command. A simple package like:
package provide mylib [lindex {Revision: 1.0 } 1]
namespace eval mylib {
set X [clock format [clock seconds] -format %y%m%d]
proc naught {} {
variable X
puts $X
return
}
}
When I run pkg_mkIndex I get:
warning: error while sourcing mylib.pkg: unknown namespace in import pattern "::msgcat::mcload"
The revised clock command - clock.tcl - uses the msgcat package like:
uplevel #0 {
package require msgcat 1.6.1
If I add a puts like puts "PKG: [package require msgcat 1.6.1]" it return empty string not version
Of course clock.tcl is called from init.tcl
I note in the pkg_mkIndex code the following:
$c eval {
# Stub out the package command so packages can require other
# packages.
rename package __package_orig
proc package {what args} {
switch -- $what {
require {
return; # Ignore transitive requires
}
default {
__package_orig $what {*}$args
}
}
}
Which would explain why the msgcat package is not loaded.
How would I load the msgcat package from the clock.tcl procedure?
All your pkgIndex.tcl really needs is a line like this, which you can write by hand (or derive from a template trivially):
package ifneeded mylib 1.0 [list source [file join $dir mylib.tcl]]
That's assuming that you want your package to use immediate loading. (I always want that for my packages.) You only need to use the tooling to generate an index file if you are using lazy loading, and I don't really recommend that on a package or application of any complexity, as it can occasionally trigger weird bugs. Modern computers are fast enough to not need lazy loading (and have been so in my experience for 20 years or more).

Meaning of a proc name ending with ::

In the tk code base I found the construct:
proc ::tk::dialog::file::chooseDir:: {args} {
Normally I would expect the procedure name after the last set of :: but here it is empty. Is this some sort of constructor in a namespace?
(Might look like a trivial question but I'm not a tcl programmer and need to know it to, automatically, generate some documentation.
Some more of the code (maybe gives some background, it is the beginning of the file)
namespace eval ::tk::dialog {}
namespace eval ::tk::dialog::file {}
namespace eval ::tk::dialog::file::chooseDir {
namespace import -force ::tk::msgcat::*
}
proc ::tk::dialog::file::chooseDir:: {args} {
variable ::tk::Priv
set dataName __tk_choosedir
upvar ::tk::dialog::file::$dataName data
Config $dataName $args
...
Normally I would expect the procedure name after the last set of ::
but here it is empty
The empty string is a valid name for a procedure in Tcl (as it for variables).
% namespace eval ::tk::dialog::file::chooseDir {}
% proc ::tk::dialog::file::chooseDir:: {args} { return "called!" }
% ::tk::dialog::file::chooseDir::
called!
% namespace eval ::tk::dialog::file::chooseDir { "" }
called!
% info procs ::tk::dialog::file::chooseDir::*
::tk::dialog::file::chooseDir::
I don't know the history behind these Tk internals, but a procedure named using the empty string might be the main procedure for the same-named namespace chooseDir (as a kind of naming convention), rather than just duplicating the name: proc ::tk::dialog::file::chooseDir::chooseDir {args} {;}. Or, it is because the entire directory-picking functionality is auto_loaded, which requires a proc (command) name rather than a namespace name?
automatically, generate some documentation.
Maybe, when harvesting a Tcl interpreter for pieces to document, take the containing namespace name chooseDir as the documented name of such a procedure?

Way to list all procedures in a Tcl file

Is there any way to list all the procedures(proc) in a myFile.tcl using another tcl file or in the same file.
You can use [info procs] before and after sourcing the file in question and compare the results to determine which procs were added. For example:
proc diff {before after} {
set result [list]
foreach name $before {
set procs($name) 1
}
foreach name $after {
if { ![info exists procs($name)] } {
lappend result $name
}
}
return [lsort $result]
}
set __before [info procs]
source myFile.tcl
set __after [info procs]
puts "Added procs: [diff $__before $__after]"
One thing I like about this solution is that the diff procedure is really just a generic set differencing utility -- it's not specific to comparing lists of defined procedures.
The cheapest way is to just open the file and use regexp to pick out the names. It's not perfectly accurate, but it does a reasonably good job.
set f [open "sourcefile.tcl"]
set data [read $f]
close $f
foreach {dummy procName} [regexp -all -inline -line {^[\s:]*proc (\S+)} $data] {
puts "Found procedure $procName"
}
Does it deal with all cases? No. Does it deal with a useful subset? Yes. Is the subset large enough for you? Quite possibly.
Yes it is, although not that easy. The basic idea is to source the file in a modified slave interp that only executes some commands:
proc proc_handler {name arguments body} {
puts $name
}
set i [interp create -safe]
interp eval $i {proc unknown args {}}
interp alias $i proc {} proc_handler
interp invokehidden source yourfile.tcl
This approach will fail if the file requires other packages (package require will not work), relies on the result of some usually auto_load'ed commands etc..
It also does not take namespaces into account. (namespace eval ::foo {proc bar a {}} creates a proc with the name ::foo::bar
For a more complex implementation you could look into auto.tcl's auto_mkindex, which has a similar goal.
Here is a different approach:
Create a temporary namespace
Source (include) the script in question, then
Use the info procs command to get a list of procs
Delete the temporary namespace upon finish
Here is my script, *list_procs.tcl*:
#!/usr/bin/env tclsh
# Script to scan a Tcl script and list all the procs
proc listProcsFromFile {fileName} {
namespace eval TempNamespace {
source $fileName
set procsList [info procs]
}
set result $::TempNamespace::procsList
namespace delete TempNamespace
return $result
}
set fileName [lindex $::argv 0]
set procsList [listProcsFromFile $fileName]
puts "File $fileName contains the following procs: $procsList"
For example, if you have the following script, procs.tcl:
proc foo {a b c} {}
proc bar {a} {}
Then running the script will produce:
$ tclsh list_procs.tcl procs.tcl
File procs.tcl contains the following procs: foo bar

SWIG + TCL flags

Will the ownership of a pointer last only in the block in which we set the -acquire flag for it?
Eg.:
{
{
$xyz -acquire
}
}
Firstly, Tcl doesn't define blocks with {/}. The scope is defined by the procedure call or namespace.
Secondly, Tcl commands are always defined to have lifetime that corresponds to the namespace that owns them; they are never† scoped to a procedure call. They must be manually disposed one way or another; there are two ways to do this manual disposal: calling $xyz -delete or rename $xyz "" (or to anything else that is the empty string). Frankly, I prefer the first method.
But if you do want the lifespan to be tied to a procedure call, that's actually quite possible to do. It just requires some extra code:
proc tieLifespan args {
upvar 1 "____lifespan handle" v
if {[info exists v]} {
trace remove variable v unset $v
set args [concat [lindex $v 1] $args]
}
set v [concat Tie-Garbage-Collect $args]
trace add variable v unset $v
}
proc Tie-Garbage-Collect {handles var dummy1 dummy2} {
upvar 1 $var v
foreach handle $handles {
# According to SWIG docs, this is how to do explicit destruction
$handle -delete
# Alternatively: rename $handle ""
}
}
That you'd use like this in the scope that you want to tie $xyz's life to:
tieLifespan $xyz
# You can register multiple objects at once too
And that's it. When the procedure (or procedure-like entity if you're using Tcl 8.5 or later) exits, the tied object will be deleted. It's up to you to decide if that's what you really want; if you later disown the handle, you probably ought to not use tying.
† Well, hardly ever; some extensions do nasty things. Discount this statement as it doesn't apply to SWIG-generated code!