Trouble using catch in simple tcl proc - return

I have the following proc which basically looks upp a couple of values in a dictonary and returns them as a list.
proc GetAllow { PID Ply } {
# read a dictonary from a file
catch {
append PlyAndDirXt $Ply "_xt"
append PlyAndDirYt $Ply "_yt"
set x_allow_tens [ dict get $allowables $PID $PlyAndDirXt ]
set y_allow_tens [ dict get $allowables $PID $PlyAndDirYt ]
set allowables [ list $x_allow_tens $y_allow_tens ]
} res
if { $res == 0 } {
return $allowables
}
if { $res != 0 } {
return 999
}
}
As I understand "catch" if everything is ok $res should be 0 = TCL_OK. In that case I would like the proc to return the list $allowables.
In case the values are not found in the dict due to none matching keys. I would like it to return 999. But I always get 999 back. What am I'm doing wrong here ?

As per the manual:
If script raises an error, catch will return a non-zero integer value corresponding to the exceptional return code returned by evaluation of script. Tcl defines the normal return code from script evaluation to be zero (0), or TCL_OK.
If the varName argument is given, then the variable it names is set to the result of the script evaluation. When the return code from the script is 1 (TCL_ERROR), the value stored in varName is an error message. When the return code from the script is 0 (TCL_OK), the value stored in resultVarName is the value returned from script.
As such, $res will not be equal to 0 unless the result of your script returns 0.
You can set catch to a variable like this:
set err [catch {
append PlyAndDirXt $Ply "_xt"
append PlyAndDirYt $Ply "_yt"
set x_allow_tens [ dict get $allowables $PID $PlyAndDirXt ]
set y_allow_tens [ dict get $allowables $PID $PlyAndDirYt ]
set allowables [ list $x_allow_tens $y_allow_tens ]
} res]
Then check
if { $err == 0 } {
return $allowables ;# Or return $res since that's the last evaluated line
}
if { $err != 0 } {
return 999
}

Related

Jenkinsfile/Groovy: why is result of dictionary AND integer an integer?

In Groovy/Jenkinsfile declarative syntax, why is the result of the boolean AND operation on dictionary, dictionary, and integer objects an integer instead of boolean true/false?
pipeline {
agent any
stages {
stage( "1" ) {
steps {
script {
a = [:]
a.a = [:]
a.a["a"] = "1"
a.a["b"] = "2"
echo "${a}"
echo "${a.a}"
echo "${a.a.size()}"
def my_bool = (a && a.a && a.a.size())
echo "my_bool ${my_bool}"
}
}
}
stage( "2" ) {
when {
expression { true == (a && a.a && a.a.size()) } // Fails because result is integer "2", not boolean "true"
}
steps {
script {
echo "hello, world!"
}
}
}
}
}
My biases from other programming languages led me to think that a && a.a && a.a.size() should implicitly be converted to a boolean value. The echo reveals that the value is integer 2.
What is the Jenkins/Groovy idiomatic way to deal with this? I.e. if a stage is conditional on "dictionaries being non-null and having nonzero size", what is the idiomatically correct/preferred way to write that conditional?
Update:
Note: the echo "my_bool ${my_bool}" statement prints "my_bool 2". This is with Jenkins version 2.222.3.
expression { a?.a?.size() }
or even
expression { a?.a }

How to use Jenkins local defined variable in IF statement?

I have below code
def result = readFile('res.txt')
echo "${result}"
if ("${result}" > 5 )
{
echo "Yes"
} else {
echo "No"
}
}
It is printing NO but the answer in ${result} -> 11 which means it should print Yes. Can u pls help.
As noted in comments - you should cast the result to integer. Both result.toInteger() and Integer.parseInt(result) will work, although the first one is more straightforward and doesn't complain about extra white space (e.g. trailing endline character).
Besides, you don't need to mess with strange constructs like "${result}" because result is a normal variable, so your code may look like this:
def result = readFile('res.txt')
echo "${result}"
if (result.toInteger() > 5 ) {
echo "Yes"
} else {
echo "No"
}

Checking for null pointer exception in an array in Jenkins scripted pipeline method

I am injecting Active Choices parameter value(s) in the Jenkins scripted pipeline.
PFB sample values passed to active choice parameter block:
return['ABC','DEF','GHI',JKL']
PFB my sample script:
node(){
selectModName()
}
def selectModName(){
stage 'Multi selection'
String[] mods = "${modName}".split(',')
modsz = mods.size()
echo ''+modsz+''
for(mod in mods){
if (modsz == null || modsz == 0){
echo 'There is nothing to be printed'
} else {
echo ''+mod+' is name of the module \n'
}
}
}
The else block is executed when I pass greater than or equal to 1 value(s) (working as intended). But if block is not executing its logic when I don't pass any parameter and press build now.
Funny thing is- size() is returning 1 instead of 0 (echo ''+modsz+'') when values aren't passed.
How to make if block execute its logic when no values are passed?
Your code always jumps to the "else" block, because
"".split(',')
produces an array with a single empty string.
assert "".split(',').size() == 1
assert "".splti(',') == [""] as String[]
When you use active choice parameter with multiple values selection and you don't select anything, your variable name stores an empty string. You should check first if the modName parameter is not an empty string and only otherwise split and display values.
node(){
selectModName()
}
def selectModName(){
stage 'Multi selection'
if (modName) {
String[] mods = modName?.split(',')
for (mod in mods) {
echo " ${mod} is name of the module"
}
} else {
echo 'There is nothing to be printed'
}
}

TCL foreach to keep track of index

When using foreach in TCL to loop through a list it is desired to have a running number of the index of the current object. A way I have done this before is to maintain an extra counter variable.
set ct 0
foreach x $list {
puts "Items is $x index is $ct"
incr ct
}
One could use lsreach to retrieve the index but that's compute intensive and could be problematic with double occurrences.
Wondering if there is streamlined sleek-looking way of maintaining index information during a loop.
Pseudocode :
foreach x $list {
puts "Items is $x index is [foreach_index $x]"
}
Your feedback is appreciated.
UPDATE:
Run time tests with the provided answers:
Peter Lewerin : 86098.8 microseconds per iteration
Gert : 91057.4 microseconds per iteration
David B : 115860.0 microseconds per iteration
Loop through list with 100k random strings 80char long.
The loop proc is fastest, but hard to read.
While the control structure definitions in other languages are the law, in Tcl they're more like a set of guidelines.
proc foreachWithIndex {indexVar args} {
upvar 1 $indexVar var
set var 0
uplevel 1 [list foreach {*}[lrange $args 0 end-1] "[lindex $args end];incr $indexVar"]
}
foreachWithIndex x v {a b c} {puts "$x: $v"}
But I suggest using for instead. Radical language modifications are fun and occasionally useful, but if I had an Imperial credit for every such clever construct I ended up throwing away later I could build my own Death Star and still have money to put a grating over the exhaust port.
Your method of using incr with a counter works ok. This also works:
for { set i 0 } { $i < [llength $list] } { incr i } {
set x [lindex $list $i]
puts "Items is $x index is $i"
}
Another advantage of doing it this way is that you can modify the list while you are iterating. Let's say you want to remove all items with the value "bad" from a list.
set values { 1 2 3 bad 4 bad bad 5 }
for { set i 0 } { $i < [llength $values] } { incr i } {
if { [lindex $values $i] eq "bad" } {
set values [lreplace $values $i $i]
incr i -1
}
}
puts $values

How to return values from foreach loop in tcl

I have a list of all the files in the directory. I have stored them in a variable file_list. I want to get the tail name for each file. My approach is like this.
set file_list [list /a/b/a.txt /a/b/b.txt /a/b/c/file1.tcl /a/b/c/file2.tcl]
proc file_tail {filename} {
set x {}
set f_tail [file tail $filename]
lappend x $f_tail
return $x
}
foreach ft $file_list {
set f_tail [file_tail $ft]
}
but f_tail only contains last value stored i.e. "file2.tcl" Please guide me. I want a list of all tail values of file
I suggest either:
set f_tail {}
foreach ft $file_list {
lappend f_tail [file_tail $ft]
}
or (if you have a later version of Tcl):
set f_tail [lmap ft $file_list {file_tail $ft}]
Documentation:
foreach,
lappend,
lmap (for Tcl 8.5),
lmap
If you are making a list of all the tails, do this:
set f_tail {}
foreach ft $file_list {
lappend f_tail [file tail $ft]
}
If your helper function is going to do the lappend, you need to keep the variable holding the list outside the procedure:
proc file_tail {filename listVariable} {
upvar 1 $listVariable theList
set f_tail [file tail $filename]
lappend theList $f_tail
}
set tails {}
foreach ft $file_list {
file_tail $ft tails ; # <<< NAME, so not $tails as that would READ the variable
}
Note that we are passing in the name of the variable (tails outside) and using upvar 1 inside the procedure to make a linked local variable (theList inside) that can be updated. However, you can't do it by passing in a list value; Tcl uses copy-on-write semantics for its values. You need to be careful about the difference between the names of variables and the values they contain; they're not the same.

Resources