So I am working with a large TCL project and thought it would be cool to build a treeview of how files were being source in the project. I modified the source command to do the following:
rename ::source ::real_source
proc ::source args {
set file_handle [open "file_source.tcl" a]
puts $file_handle $args
puts $file_handle $argv0
close $file_handle
uplevel 1 ::real_source $args
}
Which works and saves all of the files being sourced but I was wondering if anyone had any ideas on how I could determine which files are calling the source command?
Another interesting issue I am running into is that my new source procedure seems to only work in some files. File A sources File B and all of the sources in File B seem to work correctly but anything under that seems to go back to using the old source procedure. Any ideas on why this is happening?
[info script] will give you the name of the file invoking source
Example:
a.tcl
rename ::source ::real_source
proc ::source args {
puts "[info script] sources $args"
uplevel 1 ::real_source $args
}
source b.tcl
b.tcl
puts "in file b"
source c.tcl
c.tcl
puts "in file c"
outputs
a.tcl sources b.tcl
in file b
b.tcl sources c.tcl
in file c
Related
I need to recursively find all files that end with .vhd inside a directory and then copy all of them to the directory where the tcl script has been called from. This is the same directory that the user is in when the TCL script is invoked.
So what I need to so to find all .vhd files recursively and regardless of where they are, copy all of them into a single folder. I tried to use glob search but it seems perhaps that it cannot do recursive search. How can this be achieved using TCL?
You can use fileutil::findByPattern from tcllib to get the file names and copy them in a loop:
#!/usr/bin/env tclsh
package require fileutil
proc copy_tree {from_dir pat to_dir} {
foreach f [::fileutil::findByPattern $from_dir -glob -- $pat] {
file copy -force -- $f $to_dir
}
}
copy_tree some/dir *.vhd destination/
Unless you can provide for a tcllib installation for your environment, you will have to implement the directory traversal yourself. Below is but one of the many options (adapted from ftw_4 at Tclers' Wiki; this assumes Tcl 8.5+):
proc copy_tree {from_dirs pat to_dir} {
set files [list]
while {[llength $from_dirs]} {
set from_dirs [lassign $from_dirs name]
set from_dirs [list {*}[glob -nocomplain -directory $name -type d *] {*}$from_dirs]
lappend files {*}[glob -nocomplain -directory $name -type f $pat]
}
if {[llength $files]} {
file copy -force -- {*}$files $to_dir
}
}
Some remarks: This implements an iterative, depth-first directory traversal, and
glob is used twice, once to collect sub-directories, once to collect the source files.
file copy is executed just once (for the entire collection of source files), not for every source file.
The -nocomplain flag on glob is convenient, but maybe not useful in your scenario (whatever that might be).
Eg. get_abc, get_xyz command which generate collection, set_pqr receives collection are custom commands and following is tcl file
#Tcl file start
get_abc
get_xyz
set_pqr -object [get_abc]
#Tcl file end
Now the requirement is we need to skip the set_pqr command and this tcl file is big and read only, we can't change it.
Now we added this handling in set_pqr command callback to skip processing, but still get_abc command in same line get processed which is anyway to be discarded and not needed when the collection goes to set_pqr. Also we can't skip get_abc from software because its valid and can be used at other places.
Does tcl provides capability to skip the full line of set_pqr
The only ways to make Tcl do what you want are to not use standard source to load the command. Let's write our own version instead:
proc skippingSource {filename} {
set f [open $filename]
set code [read $f]
close $f
# Comment out the offending lines; this is a cheap hack BTW
set code [regsub -all -line {^set_pqr } $code "#set_pqr "]
# Override [info script] until this procedure returns
info script $filename
uplevel 1 $code
}
Now we just need to use skippingSource instead of source. We can of course call it directly, the easiest method, or we can substitute in for source:
# Keep the original in case we want to put it back
rename source originalSource
rename skippingSource source
You redefine set_pqr to a proc that does nothing:
# Back up original set_pqr
rename set_pqr set_pqr_original
# Make new proc that does nothing
proc set_pqr {args} {}
Now you can source the file and each seq_pqr command will do nothing (and takes an arbitrary number of arguments).
When you need the original command back again:
rename set_pqr_original set_pqr
This whole thing could be wrapped up in one proc too:
proc source_with_skip {filename "skip_commands {}"} {
foreach command $skip_commands {
rename $command ${command}_original
}
source $filename
foreach command $skip_commands {
rename ${command}_original $command
}
}
% source my_file.tcl set_pqr
Note that the above may not be sufficient if the procs are in different namespaces than the current namespace.
This is a bizarre issue that I can't seem to figure out. I am using TCL 8.5 and I am trying read data from a CSV file into matrix using the csv::read2matrix command. However, every time I do it, it says the matrix I am trying to write to is an invalid command. Snippet of what I am doing:
package require csv
package require struct::matrix
namespace eval ::iostandards {
namespace export *
}
proc iostandards::parse_stds { io_csv } {
# Create matrix
puts "Creating matrix..."
struct::matrix iostdm
# Add columns
puts "Adding columns to matrix..."
iostdm add columns 6
# Open File
set fid [open $io_csv r]
puts $fid
# Read CSV to matrix
puts "Reading data into matrix..."
csv::read2matrix $fid iostdm {,}
close $fid
}
When I run this code in a TCLSH, I get this error:
invalid command name "iostdm"
As far as I can tell, my code is correct (when I don't put it in a namespace. I tried the namespace import ::csv::* ::struct::matrix::* and it didn't do anything.
Is there something I am missing with these packages? Nothing on the wiki.tcl.tk website mentions anything of the sort, and all man packages for packages don't mention anything about being called within another namespace.
The problem is iostdm is defined inside the iostandards namespace. That means, it should be referenced as iostandards::iostdm, and that is how you should pass to csv::read2matrix:
csv::read2matrix $fid iostandards::iostdm {,}
Update
I noticed that you hard-coded adding 6 columns to the matrix before reading. A better way is to tell csv::read2matrix to expand the matrix automatically:
csv::read2matrix $fid iostandards::iostdm , auto
I want to add to Hai Vu's answer
From my testing, for commands such as csv::read2matrix and csv::write2matrix, if you have nested namespaces, it appears you have to go to the highest one.
I had a case where the structure was...
csv::read2matrix $fid ::highest::higher::high::medium::low::iostdm , auto
I have two tcl scripts. I want to run the second script when the first finished. How can I do it?
Depends on what do you really mean.
One way is to write a third ("master") script which would do
source /the/path/to/the/first.tcl
source /the/path/to/the/second.tcl
Another way is to just add the second call to source from the above example to the bottom of the first script.
Amendment to the first approach: if the scripts to be executed are located in the same directory as the master script, an idiomatic way to source them is
set where [file dirname [info script]]
source [file join $where first.tcl]
source [file join $where second.tcl]
This way sourcing will work no matter what the current process's directory is and where the project directory is located.
While this is generally a correct answer, because the question was not precisely formulated there are tons of ways to achieve the goal of running Tcl code from within Tcl.
I want to get into this in detail because understanding the execution of code is one major point in understanding Tcl itself.
There is source
The source command should not be confound with executing scripts in a classical way, what I think the thread starter has asked.
The source command is like the "include" command in c/perl/php.
Languages like java or python on the other hand only have "import" mechanisms.
The difference is that those languages create a internal database of available packages, who are linked to the corresponding source/binary/bytecode files. By writing a import statement, linked source or bytecode or binary files are loaded. This allows more in-depth dependency management without writing additional code.
In Tcl this can be achieved with namespaces and the package require command.
Example:
Suppose you have this source.tcl:
proc foo {bar} {puts "baz"}
set BAM "BOO"
Now, you have your "master" script like you call it. I call it "main". It has the content:
set BAM {my important data}
source source.tcl
#also the function foo can now be used because the source reads the whole script
foo {wuz}
set BAM
#will output "BOO"
The exec command
If you can live with additional overhead of starting a whole new interpreter instance you could also do:
set BAM {my important data}
exec tclsh source.tcl
#The variable BAM will not be modified. You can not use the function foo.
The eval command
The command eval can evaluate a string or a list (in Tcl everything is a string) like it would be programmed code.
You would have to load the complete source file to a string. And then use eval, to evaluate the code within a separate scope, to not overwrite stuff in your main source file.
set fp [open "somefile" r]
set code_string [read $fp]
close $fp
eval $code_string
You just need to use source to run the 2nd script.
source "/tmp/whatever.tcl"
Simplest possible working example I could find:
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$ tclsh main.tcl
hello world
7
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$ cat main.tcl
lappend auto_path /home/thufir/NetBeansProjects/spawnTelnet/telnet/api
package require weather 1.0
tutstack::hello
set A 3
set B 4
puts [tutstack::sum $A $B]
#puts [tutstack::hello "fred"]
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$ cat api/weather.tcl
package provide weather 1.0
package require Tcl 8.5
namespace eval ::tutstack {
}
proc ::tutstack::hello {} {
puts "hello world"
}
proc ::tutstack::sum {arg1 arg2} {
set x [expr {$arg1 + $arg2}];
return $x
}
proc ::tutstack::helloWorld {arg1} {
return "hello plus arg"
}
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$ cat api/pkgIndex.tcl
# Tcl package index file, version 1.1
# This file is generated by the "pkg_mkIndex" command
# and sourced either when an application starts up or
# by a "package unknown" script. It invokes the
# "package ifneeded" command to set up package-related
# information so that packages will be loaded automatically
# in response to "package require" commands. When this
# script is sourced, the variable $dir must contain the
# full path name of this file's directory.
package ifneeded weather 1.0 [list source [file join $dir weather.tcl]]
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$
i am a newbie in TCL Programming
I am having a tcl script called test1.tcl and test2.tcl separately in two different
directories F:\TCLPrograms\SamplePrograms\test1.tcl and F:\TCLPrograms\test2.tcl
i want to know the full path of test2.tcl which is a proc
if i give info [script] inside proc disp {} its returning the path from where it is invoked
i.e F:\TCLPrograms\SamplePrograms\test1.tcl
kindly someone tell me to get the path of the proc
test1.tcl:
puts "Processing test1..."
source "F:\\TCLPrograms\\test2.tcl"
set rc [disp]
puts "Executed...."
test2.tcl:
proc disp { } {
puts "Successfully executed test2.tcl"
set path [info script]
puts "Script is invoked from the path: $path"
}
Thanks in advance
The result of info script depends on the current innermost source, and procedures don't maintain that information. (Well, it's maintained in debugging information for 8.6 and some builds of 8.5 from ActiveState, but it's truly awkward to access.)
The easiest way is to use a variable to hold the name of the file, like this:
variable dispScriptFile [file normalize [info script]]
proc disp {} {
variable dispScriptFile
puts "Successfully executed test2.tcl"
set path [file dirname $dispScriptFile]
puts "Script is invoked from the path: $path"
}
Note that we use the normalized filename, so that it remains valid even if you use a relative pathname and then cd to some other directory.
(I also recommend putting the whole contents of test2.tcl inside its own namespace; it makes it easier to keep things separate.)