My Tcl application should read and store a lot of configurations parameters. I'd like to use regular disk file as a storage rather than registry or something else.
It would be great to store parameters hierarchically. All my parameters are strings, numbers, and lists of them. Configuration file(s) may be placed in directory (not only user's home). Normally application expects configuration file in the current directory.
Do you know any ready-to-use Tcl library?
More general question: what is the "Tcl-way" to read/write application configuration?
Thanks.
If the configuration does not necessarily need to be human-readable, I suggest you consider Sqlite -- it began as a Tcl extension, and therefore Tcl's Sqlite bindings are more mature than any other language's.
See: http://www.sqlite.org/tclsqlite.html
If you don't need random access (that is, configuration files are not huge and each can be slurped completely at once) and don't require processing by external tools, you could just use flat text files containing, say, Tcl lists. The "trick" is that in Tcl each value must have a valid string representation (when asked) and can be reconstructed from its string representation. You get that for free, that is, no special package is required and all you have to provide is some sort of structure to bind serialized values to their names.
To demonstrate:
set a "a string"
set b 536
set c {this is a list {with sublist}}
proc cf_write {fname args} {
set fd [open $fname w]
chan config $fd -encoding utf-8
set data [list]
foreach varName $args {
upvar 1 $varName var
lappend data [list $varName $var]
}
puts $fd $data
close $fd
}
proc cf_read fname {
set fd [open $fname]
chan config $fd -encoding utf-8
set data [read $fd]
close $fd
set data
}
set cfile [file join [file dir [info script]] conf.txt]
cf_write $cfile a b c
foreach entry [cf_read $cfile] {
lassign $entry name value
puts "$name: $value"
}
You'll get this output:
a: a string
b: 536
c: this is a list {with sublist}
Now if you feel like having something more fancy or "interoperable", look at YAML or JSON (you'll need to write a serializer for this one though) or INI formats--all available from Tcllib and hence are plain Tcl.
Even more fancier could be using XML via TDOM (an expat-based C extension). SQLite, which has been already proposed, is even more capable than that (provides random access to the data, is able to operate on huge data arrays). But it seems that for your task these tools appear to be too heavy-weight.
Note that my example deliberately opts to show how to store/restore an arbitrary ad-hoc list of variables so the cf_write procedure builds the Tcl list to be stored by itself. Of course, no one prevents you from building one yourself, providing for creation of hierarchical structures of arbitrary complexity. One caveat is that in this case you might (or might not) face a problem of deconstructing the restored list. But if you'll stick to a general rule of each element being a name/value pair as in my example, the deconstruction shouldn't be hard.
tcllib contains a package inifile for handling windows .ini file format configuration files. As it's part of tcllib it should be avaialble on all platforms (I've just checked and it loads ok on my Solaris 8 box). It allows you to both read and write .ini files and access the configuration by section and key.
Related
I am trying to run a tcl script through .bat file. I want to read some cmd arguments in the tcl script. Below is my code:
Command to run:
D:\Cadence\Sigrity2021.1\tools\bin\PowerSI.exe -tcl abcd.tcl %new_var%.spd %new_file_name%
Below is how I am trying to read the variable in the tcl file:
sigrity::open document [lindex $argv 0] {!}
It open up the Cadence Sigrity, but I see the below error:
How do I read cmd argument in tcl?
If you have no other way to do it that you can find (and it sounds like that might be the case) then you can fake it by writing a helper file with content like this, filling in the real arguments in the appropriate places:
# Name of script to call
set ::argv0 "abcd.tcl"
# Arguments to pass
set ::argv {}
lappend ::argv "%new_var%.spd"
lappend ::argv "%new_file_name%"
# Number of arguments (rarely used)
set ::argc [llength $::argv]
# Do the call
source $::argv0
Then you can pass that file to PowerSI and it will set things up and chain to the real file. It's messy, but practical.
If you're writing this from Tcl, use the list command to do the quoting of the strings (instead of putting them in double quotes) as it will do exactly the right thing for you. If you're writing the file from another language, you'll want to make sure you put backslashes in before \, ", $ and [ characters. The fiddlyness of doing that depends on your language.
Please help me with the script which outputs the file that contains names of the files in subdirectories and its memory in bytes, the arguement to the program is the folder path .output file should be file name in 1st column and its memory in second column
Note:folder contains subfolders...inside subfolders there are files
.I tried this way
set fp [open files_memory.txt w]
set file_names [glob ../design_data/*/*]
foreach file $file_names {
puts $fp "$file [lindex [exec du -sh $file] 0]"
}
close $fp
Result sample:
../design_data/def/ip2.def.gz 170M
../design_data/lef/tsmc13_10_5d.lef 7.1M
But i want only file name to be printed that is ip2.def.gz , tsmc13_10_5d.lef ..etc(not the entirepath) and file memorry should be aligned
TCL
The fileutil package in Tcllib defines the command fileutil::find, which can recursively list the contents of a directory. You can then use foreach to iterate over the list and get the sizes of each of them with file size, before producing the output with puts, perhaps like this:
puts "$filename\t$size"
The $filename is the name of the file, and the $size is how large it is. You will have obtained these values earlier (i.e., in the line or two before!). The \t in the middle is turned into a TAB character. Replace with spaces or a comma or virtually anything else you like; your call.
To get just the last part of the filename, I'd do:
puts $fp "[file tail $file] [file size $file]"
This does stuff with the full information about the file size, not the abbreviated form, so if you really want 4k instead of 4096, keep using that (slow) incantation with exec du. (If the consumer is a program, or a programmer, writing out the size in full is probably better.)
In addition to Donal's suggestion, there are more tools for getting files recursively:
recursive_glob (from the Tclx package) and
for_recursive_glob (also from Tclx)
fileutil::findByPattern (from the fileutil package)
Here is an example of how to use for_recursive_glob:
package require Tclx
for_recursive_glob filename {../design_data} {*} {
puts $filename
}
This suggestion, in combination with Donal's should be enough for you to create a complete solution. Good luck.
Discussion
The for_recursive_glob command takes 4 arguments:
The name of the variable representing the complete path name
A list of directory to search for (e.g. {/dir1 /dir2 /dir3})
A list of patterns to search for (e.g. {*.txt *.c *.cpp})
Finally, the body of the for loop, where you want to do something with the filename.
Based on my experience, for_recursive_glob cannot handle directories that you don't have permission to (i.e. on Mac, Linux, and BSD platforms, I don't know about Windows). In which case, the script will crash unless you catch the exception.
The recursive_glob command is similar, but it returns a list of filenames instead of structuring in a for loop.
I want to find a way to return the name of a library of a certain path in a VHDL Design in Modelsim.
Given a VHDL Design with a path like "/mega_tb/D0". This is compiled in a library that is NOT 'work', say "libnwork".
I can of course take a look in my 'do' file to get the correct lib name. Or I can search in ModelSim's Library tab. But I want to have or create a modelsim command which I can later use in a Tcl script, to get the correct library name.
One of the easiest ways to find something in a Tcl script file – which is all a Modelsim “do” file is — is to evaluate it. Tcl's very good at that. Of course, you don't want to have the commands do all the conventional things. Instead, we'll evaluate in a context where we can make everything do nothing except for the command that produces the information we want:
# Set up our evaluation context, 'worker'
interp create worker -safe
interp eval worker {proc unknown args {}}; # Our do-nothing handler
interp alias worker theInterestingCommand {} ourHandler
proc ourHandler args {
puts "We were called with: $args"
}
# Parse the file!
set f [open /the/file.tcl]
interp eval worker [read $f]
# Clean up
close $f
interp delete worker
Now you just have to make theInterestingCommand have the right name and extract the interesting information from the arguments. Which should be relatively easy…
Te only way I've found is to use the command
write report -tcl
This prints a long list where I have search for the lib names with regexps.
Something like
set data [ write report -tcl]
foreach_regexp { _ type lib entity} $data{
if {$type == "Entity" && $entity == [entity_of_path /mega_tb/D0] } {
....
}
}
Where I of course had to define my "foreach_regexp" procedure and my "entity_of_path" procedure. I then can use something like regsub to extract the library name.
I am still looking for a better and easier way.
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 need to store some logs in a file that can grow with every execution. A logical way would be to use a+ option when opening because using w+ would truncate the file. However, with the a+ option (Tcl 8.4) I cannot write anywhere in the file. seek works fine. I can verify that the pointer was moved using tell. But the output is always done at the tail end of the file.
Is there any way to resolve this? I.e. having the ability to seek and write in any place and also preserve the old file at the open.
In Tcl 8.5, the behavior of Tcl on Unix was changed so that the O_APPEND flag is passed to the open() system call. This causes the OS to always append the data to the file, and is inherited when the FD is passed to subprocesses; for logs, it is exactly the right thing. (In 8.4 and before, and in all versions on Windows, the behavior is simulated inside Tcl's file channel implementation, which will internally seek() to the end immediately before the write(); that obviously is subject to potential problems with race conditions when there are multiple processes logging to the same file and is definitely unsafe when the FD is passed to subprocesses.) You can manage truncation of the opened file with chan truncate (new in 8.5), which works just fine on a+-opened files.
If you do not want the seek-to-end behavior, you should not use a+ (or a). Try r+ or some combination of flags, like this:
set f [open $filename {RDWR CREAT}]
For comparison, the a+ option is now exactly the same as the flags RDWR CREAT APPEND, and not all combinations of longer flags can be described by short form flag specifiers. If you're not specifying APPEND, you'll need to do the seek $f 0 end yourself (and watch out for problems with multiple processes if you're appending to logs; that's when APPEND becomes required and exceptionally hard to correctly simulate any other way).
Open with r+ - it opens in read mode (thus not turncating the file) but allows writing as well.
See the documentation of open for more info: http://www.tcl.tk/man/tcl8.5/TclCmd/open.htm
I have verified that using the a+ option allow me to read/write anywhere in the file. However, by writing in the middle (or at the beginning) of a file, I overwrite the data there, not inserting. The following code illustrate that point:
#!/usr/bin/env tclsh
# Open the file, with truncation
set f [open foo w]
puts $f "one"
puts $f "two"
close $f
# Open again, with a+ ==> read/write/append
set f [open foo a+]
puts $f "three" ;# This goes to the end of the file
seek $f 4 ;# Seek to the beginning of the word "two"
puts $f "2.0" ;# Overwrite the word "two"
close $f
# Open and verify the contents
set f [open foo r]
puts [read $f]
close $f
Output:
one
2.0
three
If you are looking to insert in the middle of the file, you might want to look at the fileutil package, which contains the ::fileutil::insertIntoFile command.