How to replace string with variable value in Tcl/Expect - unix

I am trying to replace a specific string (hostname), inside a main string, with a variable value using the following command,
set newVar [string map {`hostname` $theHost} 'lsnrctl status listener_`hostname`']
However, instead of replacing hostname with the variable value, it replaces it using the variable identifier, i.e. "$theHost".
The newVar reads like this,
lsnrctl status listener_$theHost
I want it to be this,
lsnrctl status listener_theHostName
where "theHostName" is the value of the variable "$theHost".
How can I achieve this?

Build the replace list with the listcommand:
set newVar [string map [list `hostname` $theHost] "lsnrctl status listener_`hostname`"
or you could just use
set newVar "lsnrctl status listener_${theHost}"
' has no special meaning in Tcl, so your snippet will throw an error.
use " if you want command, variable and backslash substitution, { if you don't want this. (\ before the end of a line will be still substituted).
So using {`hostname` $theHost} will be substituted to `hostname` $theHost, not the thing you want. So better build a list with list, "`hostname` $theHost" might work if $theHost is a valid list element (does not contain spaces, etc), but will fail if the value of $theHost is foo bar (string map will throw an error)

Can't you just do
set newVar 'lsnrctl status listener_'
append newVar $theHost

Related

xquery matches - allow non existing nodes in loop

I have a for loop and want to filter some nodes, which works fine:
matches($doc/abc/#def, $filterA)
matches($doc/qwert/#xyz, $filterB)
What also works is, when $filterA, $filterB or both are empty, to return every node. What does not work however is to return the node if node abc or qwert do not exist. For the default value i currently use "" (empty string), is there another default value or another function I can use to make it work?
You can test whether the abc and qwert elements exist with the fn:exists() function. If you want it to pass if either of those elements do not exist, you can use fn:not() to negate a test for abc and qwert existence:
fn:not(fn:exists($doc/abc) and fn:exists($doc/qwert))
If you want a condition to pass if either $filterA or $filterB is empty:
fn:not(fn:exists($filterA) and fn:exists($filterB))
You can consolidate the matches() expressions a predicate to avoid repeating $doc (not a huge savings, but something to think of more generally when writing XPath expressions.
$doc[matches(abc/#def, $filterA) and matches(qwert/#xyz, $filterB)]
Putting it all together:
let $filterA := "a"
let $filterB :="b"
let $doc := <doc><abc def="a"/><qwert xyz="b"/></doc>
return
if (fn:not(fn:exists($doc/abc) and fn:exists($doc/qwert))
or fn:not(fn:exists($filterA) and fn:exists($filterB))
or $doc[matches(abc/#def, $filterA) and matches(qwert/#xyz, $filterB)])
then "pass - copy nodes"
else "fail"

How to replace the single quote ( ' ) into double quotes ( " ) in Robot Framework?

I have a List, items are created by append by a Loop. I want to use this list as a json. The problem is that the items of that list use the single quote so it can't be the json.
Get Order Items
[Tags] Get Order Items
Set Headers ${HEADER CUSTOMER}
GET ${ORDER GET BY CODE ENDPOINT}/${ORDER CODE}
Integer response status 200
${NUMBER OF ITEMS} Output $.number_of_items
${NUMBER OF ITEMS} Evaluate ${NUMBER OF ITEMS} + 1
${ORDER ITEMS} Create List
:FOR ${i} IN RANGE 1 ${NUMBER OF ITEMS}
\ Append To List ${ORDER ITEMS} ${ORDER CODE}${i}
Set Global Variable ${ORDER ITEMS}
Actual result: ['N19072596HB1', 'N19072596HB2', 'N19072596HB3', 'N19072596HB4', 'N19072596HB5']
Expected result: ["N19072596HB1", "N19072596HB2", "N19072596HB3", "N19072596HB4", "N19072596HB5"]
This: ['N19072596HB1', 'N19072596HB2', 'N19072596HB3', 'N19072596HB4', 'N19072596HB5'] , is python's string representation of a list, and they have picked to use single quotes for it.
As your end goal is to use the double-quoted version in a json, the best bet is to use the python's json library to convert it for you, instead of replacing single with double quotes:
${list as string}= Evaluate json.dumps($ORDER_ITEMS) json
(note how the variable is not surrounded by curly brackets - thus you're passing the actual variable to the method, not its value)
Why not to use a simple string replacement?
Because you don't want to deal with cases where a list member may have a quote, single or double - like ['N19072596HB1', "N1907'2596HB2", 'N19072596HB1', 'N19072"596HB2'].
A trivial string replace will fail miserably, while the json library is designed to handle these cases as expected.

Run keyword if to append to a string ... ${query_string}= catenate ${query_string} AND

I want to append to a variable if dictionary size is greater than 1
${queryString}= startOfString
Run keyword if ${dictionary_size} > 1
... ${query_string}= catenate ${query_string} restofString
However the only if statement i can see in Robot is the above. Obviously variable assignment isnt a keyword. Is there another way of doing this so i would end up with
startOfString restofString
Set Variable If is your friend here.
${queryString}= Set Variable startOfString
&{dict}= Create Dictionary foo=bar
${dictLen}= Get Length ${dict}
${queryString}= Set Variable If ${dictLen} > 1 ${queryString} restofString ${queryString}
If the start of the query is static:
&{dict}= Create Dictionary foo=bar zaz=lop
${dictLen}= Get Length ${dict}
${queryString}= Set Variable If ${dictLen} > 1 startOfString restofString startOfString

Using Rascal MAP

I am trying to create an empty map, that will be then populated within a for loop. Not sure how to proceed in Rascal. For testing purpose, I tried:
rascal>map[int, list[int]] x;
ok
Though, when I try to populate "x" using:
rascal>x += (1, [1,2,3])
>>>>>>>;
>>>>>>>;
^ Parse error here
I got a parse error.
To start, it would be best to assign it an initial value. You don't have to do this at the console, but this is required if you declare the variable inside a script. Also, if you are going to use +=, it has to already have an assigned value.
rascal>map[int,list[int]] x = ( );
map[int, list[int]]: ()
Then, when you are adding items into the map, the key and the value are separated by a :, not by a ,, so you want something like this instead:
rascal>x += ( 1 : [1,2,3]);
map[int, list[int]]: (1:[1,2,3])
rascal>x[1];
list[int]: [1,2,3]
An easier way to do this is to use similar notation to the lookup shown just above:
rascal>x[1] = [1,2,3];
map[int, list[int]]: (1:[1,2,3])
Generally, if you are just setting the value for one key, or are assigning keys inside a loop, x[key] = value is better, += is better if you are adding two existing maps together and saving the result into one of them.
I also like this solution sometimes, where you instead of joining maps just update the value of a certain key:
m = ();
for (...whatever...) {
m[key]?[] += [1,2,3];
}
In this code, when the key is not yet present in the map, then it starts with the [] empty list and then concatenates [1,2,3] to it, or if the key is present already, let's say it's already at [1,2,3], then this will create [1,2,3,1,2,3] at the specific key in the map.

idl: pass keyword dynamically to isa function to test structure read by read_csv

I am using IDL 8.4. I want to use isa() function to determine input type read by read_csv(). I want to use /number, /integer, /float and /string as some field I want to make sure float, other to be integer and other I don't care. I can do like this, but it is not very readable to human eye.
str = read_csv(filename, header=inheader)
; TODO check header
if not isa(str.(0), /integer) then stop
if not isa(str.(1), /number) then stop
if not isa(str.(2), /float) then stop
I am hoping I can do something like
expected_header = ['id', 'x', 'val']
expected_type = ['/integer', '/number', '/float']
str = read_csv(filename, header=inheader)
if not array_equal(strlowcase(inheader), expected_header) then stop
for i=0l,n_elements(expected_type) do
if not isa(str.(i), expected_type[i]) then stop
endfor
the above doesn't work, as '/integer' is taken literally and I guess isa() is looking for named structure. How can you do something similar?
Ideally I want to pick expected type based on header read from file, so that script still works as long as header specifies expected field.
EDIT:
my tentative solution is to write a wrapper for ISA(). Not very pretty, but does what I wanted... if there is cleaner solution , please let me know.
Also, read_csv is defined to return only one of long, long64, double and string, so I could write function to test with this limitation. but I just wanted to make it to work in general so that I can reuse them for other similar cases.
function isa_generic,var,typ
; calls isa() http://www.exelisvis.com/docs/ISA.html with keyword
; if 'n', test /number
; if 'i', test /integer
; if 'f', test /float
; if 's', test /string
if typ eq 'n' then return, isa(var, /number)
if typ eq 'i' then then return, isa(var, /integer)
if typ eq 'f' then then return, isa(var, /float)
if typ eq 's' then then return, isa(var, /string)
print, 'unexpected typename: ', typ
stop
end
IDL has some limited reflection abilities, which will do exactly what you want:
expected_types = ['integer', 'number', 'float']
expected_header = ['id', 'x', 'val']
str = read_csv(filename, header=inheader)
if ~array_equal(strlowcase(inheader), expected_header) then stop
foreach type, expected_types, index do begin
if ~isa(str.(index), _extra=create_struct(type, 1)) then stop
endforeach
It's debatable if this is really "easier to read" in your case, since there are only three cases to test. If there were 500 cases, it would be a lot cleaner than writing 500 slightly different lines.
This snipped used some rather esoteric IDL features, so let me explain what's happening a bit:
expected_types is just a list of (string) keyword names in the order they should be used.
The foreach part iterates over expected_types, putting the keyword string into the type variable and the iteration count into index.
This is equivalent to using for index = 0, n_elements(expected_types) - 1 do and then using expected_types[index] instead of type, but the foreach loop is easier to read IMHO. Reference here.
_extra is a special keyword that can pass a structure as if it were a set of keywords. Each of the structure's tags is interpreted as a keyword. Reference here.
The create_struct function takes one or more pairs of (string) tag names and (any type) values, then returns a structure with those tag names and values. Reference here.
Finally, I replaced not (bitwise not) with ~ (logical not). This step, like foreach vs for, is not necessary in this instance, but can avoid headache when debugging some types of code, where the distinction matters.
--
Reflective abilities like these can do an awful lot, and come in super handy. They're work-horses in other languages, but IDL programmers don't seem to use them as much. Here's a quick list of common reflective features I use in IDL, with links to the documentation for each:
create_struct - Create a structure from (string) tag names and values.
n_tags - Get the number of tags in a structure.
_extra, _strict_extra, and _ref_extra - Pass keywords by structure or reference.
call_function - Call a function by its (string) name.
call_procedure - Call a procedure by its (string) name.
call_method - Call a method (of an object) by its (string) name.
execute - Run complete IDL commands stored in a string.
Note: Be very careful using the execute function. It will blindly execute any IDL statement you (or a user, file, web form, etc.) feed it. Never ever feed untrusted or web user input to the IDL execute function.
You can't access the keywords quite like that, but there is a typename parameter to ISA that might be useful. This is untested, but should work:
expected_header = ['id', 'x', 'val']
expected_type = ['int', 'long', 'float']
str = read_cv(filename, header=inheader)
if not array_equal(strlowcase(inheader), expected_header) then stop
for i = 0L, n_elemented(expected_type) - 1L do begin
if not isa(str.(i), expected_type[i]) then stop
endfor

Resources