Why I cannot get size of the string returned from function in scriban? - scriban

I am testing following code in scriban:
{{ func getString }}text{{ end }}
However when I try to get text length with:
{{ getString | string.size }}
I always get 0, whereas I expect 4. Why is this?

Function getString as defined in question prints "text" to output and returns nothing as result and thus the 0 output of string.size on its result. You may treat this instruction as a call to printf or Console.Writeline - it prints string to output but you cannot do anything more with that string. To return a string from a method to be able to further process it you need to use ret:
{{ func getString
ret 'text'
end }}
Then output of
{{ getString | string.size }}
will be 4.

Related

Adding a nilable variable to a non-nilable array

I have a string array named current_todos and am trying to add a variable of type (String | Nil) named new_task by doing the following:
current_todos << new_task if typeof(new_task) == String
I get the error Error: no overload matches 'Array(String)#<<' with type (String | Nil).
How can I add a nilable string to current_todos after doing a type check?
Edit: here is the full code:
require "option_parser"
current_todos = [
"Laundry",
"Walk dog",
"Sing in shower"
]
new_task = nil
OptionParser.parse do |parser|
parser.banner = "Welcome to my todo app!"
parser.on "-a TASK", "--add TASK", "Type a new task" do |task|
new_task = task
end
parser.on "-h", "--help" do
puts parser
exit
end
end
current_todos << new_task if typeof(new_task) == String
current_todos.each do |todo|
puts todo
end
If new_task is of type String|Nil, you can test if it is non-nil. Then the compiler will know that it is a string. That here should work:
current_todos << new_task if new_task
Another way that the compiler will understand, which is closer to your original code, is to use is_a?:
current_todos << new_task if new_task.is_a?(String)
typeof(var) gives the compile time type, not the type at runtime. For example:
def check_type(var : String?)
puts typeof(var)
puts var.class
puts var.is_a? String
puts var.is_a? Nil
puts var.nil?
end
check_type(gets)
If you give input, it will print:
(String | Nil)
String
true
false
false
If you don't give input (gets returns nil) then it will print:
(String | Nil)
Nil
false
true
true
Looks like the reason something like current_todos << new_task if new_task.is_a?(String) doesn't work is because the new assignment of new_task happens within the .on function of the parser. Since the compiler doesn't know when/if this is called as it is inside a closure, it resorts to nil.
To force it to work I would need to use .not_nil!

Scriban object.eval gives unexpected newline

I am using Scriban with a custom object that contains basically labels.
Lets say I have the object
Labels = { Label1: "L1", Label2: "L2"}
I can use {{ Labels.Label1 }} and are getting the correct value "L1"
As we want some custom labels which are not known at runtime, we have a local object
custom: [
{key: "Labels.Label1", value: "C1"}
]
Now I have a function that looks into the custom array and tries to find an entry, and if nothing is found, it evaluates the given "item" using object.eval.
When I call
"Labels.Label1" | label ":"
I get "L1:" back, note that the colon is directly behind "L1"
When I call
"Labels.Label2" | label ":"
I get "L21:" back, note that the colon is in a new line behind "L2"
Why is this happenng?
{{func byKey(label)
ret label.key == key
end}}
{{func label(item, suffix = "")
key = item
labelObject = custom | array.filter #byKey
if labelObject.size == 1
ret (labelObject[0].label) + suffix
else
ret (item | asString) + suffix
end
end
}}

How to assert that a text ends with digits in protractor

I would like to assert in Protractor that a link text is composed by the following way: text-1 (where text is a variable, and the number can be composed by any digits).
I tried the following:
browser.wait(
ExpectedConditions.visibilityOf(
element(by.xpath(`//a[#class = 'collapsed' and starts-with(text(), '${text}') and ends-with(text(), '-(/d+)')]`))),
5000)
and
browser.wait(
ExpectedConditions.visibilityOf(
element(by.xpath(`//a[#class = 'collapsed' and starts-with(text(), '${text}') and ends-with(text(), '/^-(/d+)$/')]`))),
5000)
Unfortunately, none of the above xpaths worked.
How can I fix this?
If you change the way to declare the variable and your second predicate you can go with :
//a[#class='collapsed'][starts-with(text(),'" + text_variable + "')][number(replace(.,'^.*-(\d+)$','$1'))*0=0]
where [number(replace(.,'^.*-(\d+)$','$1'))*0=0] test for the presence of a number at the end of a string.
Example. If you have :
<p>
<a class="collapsed">foofighters-200</a>
<a class="collapsed">foofighters</a>
<a class="collapsed">boofighters-200</a>
<a class="collapsed">boofighters-200abc</a>
</p>
The following XPath :
//a[#class='collapsed'][starts-with(text(),'foo')][number(replace(.,'^.*-(\d+)$','$1'))*0=0]
will output 1 element :
<a class="collapsed">foofighters-200</a>
So in Protractor you could have :
var text = "foo";
browser.wait(ExpectedConditions.visibilityOf(element(by.xpath("//a[#class='collapsed'][starts-with(text(),'" + text + "')][number(replace(.,'^.*-(\d+)$','$1'))*0=0]"))), 5000);
...
You can use regexp for this:
await browser.wait(async () => {
return new RegExp('^.*(\d+)').test(await $('a.collapsed').getText());
}, 20000, 'Expected link text to contain number at the end');
Tune this regex here if needed:
https://regex101.com/r/9d9yaJ/1

Crystal-Lang - Cross-Macro Macro-variables

So I'm building a data type where, I would like, optional auto-casting. The last question I asked is related to this also.
The code I currently have can be found below:
class Test(T)
##auto_cast = false
def initialize(var : T)
#var = var
end
def self.auto_cast
##auto_cast
end
def self.auto_cast=(val)
##auto_cast = val
end
def self.auto_cast(forced_value=true,&block)
#Force value, but store initial value:
ac = ##auto_cast
##auto_cast = forced_value
block.call
##auto_cast = ac
end
def +(val)
var = #var
if ##auto_cast
if var.is_a? String
casted_arg = val.to_s
return var + casted_arg
else
casted_arg = typeof(var).new(val)
return var + casted_arg
end
else
if typeof(var) != typeof(val)
{{raise "Error: Type of <<var>> is not equal to type of <<val>> while auto_cast is false."}}
else
return var + val
end
end
end
end
When I try to test the data type however:
Test.auto_cast do
puts Test.auto_cast
puts Test.new(1) + "1"
puts Test.new("1") + 1
end
It throws an error at return var + val:
if typeof(var) != typeof(val)
{{raise "Error: Type of <<var>> is not equal to type of <<val>> while auto_cast is false."}}
else
ERROR! --> return var + val
end
At first I was confused why, but now it makes sense.
I believe the Crystal compiler cannot be certain that ##auto_cast will be true whenever I intend to auto_cast (and to be fair, when auto-casting is disabled, I want the syntax error).
A compile error occurs because the value of ##auto_cast is unknown at compile time.
Due to the contradictory nature of the bodies:
.
if var.is_a? String
casted_arg = val.to_s
return var + casted_arg
else
casted_arg = typeof(var).new(val)
return var + casted_arg
end
and
if typeof(var) != typeof(val)
{{raise "Error: Type of <<var>> is not equal to type of <<val>> while auto_cast is false."}}
else
return var + val
end
Each definition should only be used when the user explicitly declares it. Thus this is more suited to a macro.
Given these reasons I started trying to build the functionality into a macro instead:
def +(val)
var = #var
{%if ##auto_cast%}
if var.is_a? String
casted_arg = val.to_s
return var + casted_arg
else
casted_arg = typeof(var).new(val)
return var + casted_arg
end
{%else%}
if typeof(var) != typeof(val)
{{raise "Error: Type of <<var>> is not equal to type of <<val>> while auto_cast is false."}}
else
return var + val
end
{%end%}
end
I thought this would work because, this way code is only ever generated if ##auto_cast is set. However what I forgot was premise #2. I.E. the value of ##auto_cast is unknown at compile time. Ultimately, in order to make this work I would need a variable which can be:
Set at compile time.
Used globally within macros at compile time.
Ultimately I figured I could do something along the lines of this:
SET_AUTOCAST_VARIABLE true
puts Test.auto_cast
puts Test.new(1) + "1"
puts Test.new("1") + 1
SET_AUTOCAST_VARIABLE false
and then in the +() definition:
{%if autocast_variable %}
...
{%else%}
...
{%end%}
The problem is, I do not think such a macro global variable exists... I was thinking about ways to get around this issue and so far the only solution I can come up with is using some external file at compile time:
{{File.write("/tmp/cct","1")}}
puts Test.auto_cast
puts Test.new(1) + "1"
puts Test.new("1") + 1
{{File.write("/tmp/cct","")}}
and in the method definition:
{%if File.read("/tmp/cct")=="1" %}
...
{%else%}
...
{%end%}
This feels really hacky though... I was wondering whether there were any other alternatives, (or, even, if this just won't work at all)?
This can't work. Methods are only instantiated once, it is not possible to have two implementations of the same method with the same argument types. In the following example both + methods will inevitably have the same implementation.
Test.auto_cast do
Test.new(1) + "1"
end
Test.new(1) + "1"
You can't have different implementations of the same method depending on lexical scope. The method is exactly instantiated once and so is the macro inside it.
I don't understand your overall use case, but maybe there are other ways to achieve what you need.
For completeness: You can utilize a constant as a macro global variable. Constants can't be redefined, but altered through macro expressions. That can be used to store state between macros. For example:
FOO = [true]
{{ FOO[0] }} # => true
{% FOO.clear; FOO << false %}
{{ FOO[0] }} # => false
That's pretty hacky, though ;)

Write regex to match markup element like attribute/values even ones not wrapped in quotes

Say I have
<div class="doublequotes"></div>
<div class='simplequotes'></div>
<customElement data-attr-1=no quotes data-attr-2 = again no quotes/>
I would like to see a nice regex to grab all attribute/vale pairs above as follows:
class, doublequotes
class, simplequotes
data-attr-1, no quotes
data-attr-2, again no quotes
Please note in the setup the following
presence of both single/double quotes to wrap values
possible absence of any quote
possible absence of any quote + multiple-word value
Here is a solution, written in Javascript so you can try it out right here, that separates into tags and then attributes, which allows retaining the parent tag (if you don't want that, don't use tag[1]).
A main reason this extracts tags and then attributes is so we don't find false "attributes" outside the tags. Note how the look="a distraction" part is not included in the parsed output.
<textarea id="test" style="width:100%;height:11ex">
<div class="doublequotes"> look="a distraction" </div><div class='simplequotes'></div>
<customElement data-attr-1=no quotes data-attr-2 = again no quotes/>
<t key1="value1" key2='value2' key3 = value3 key4 = v a l u e 4 key5 = v a l u e 5 />
Poorly nested 1 (staggered tags): <a1 b1=c1>foo<d1 e1=f1>bar</a1>baz</d1>
Poorly nested 2 (nested tags): <a2 b2=c2 <d2 e2=f2>>
</textarea>
<script type="text/javascript">
function parse() {
var xml = document.getElementById("test").value; // grab the above text
var out = ""; // assemble the output
tag_re = /<([^\s>]+)(\s[^>]*\s*\/?>)/g; // each tag as (name) and (attrs)
// each attribute, leaving room for future attributes
attr_re = /([^\s=]+)\s*=\s*("[^"]*"|'[^']*'|[^'"=\/>]*?[^\s\/>](?=\s+\S+\s*=|\s*\/?>))/g;
while(tag = tag_re.exec(xml)) { // for each tag
while (attr = attr_re.exec(tag[2])) { // for each attribute in each tag
out += "\n" + tag[1] + " -> " + attr[1] + " -> "
+ attr[2].replace(/^(['"])(.*)\1$/,"$2"); // remove quotes
}
};
document.getElementById("output").innerHTML = out.replace(/</g,"<");
}
</script>
<button onclick="parse()" style="float:right;margin:0">Parse</button>
<pre id="output" style="display:table"></pre>
I am not sure how complete this is since you haven't explicitly stated what is and is not valid. The comments to the question already establish that this is neither HTML nor XML.
Update: I added to nesting tests, both of which are invalid in XHTML, as an attempt to answer the comment about imbricated elements. This code does not recognize <d2 as a new element because it is inside another element and therefore assumed to be a part of the value of the b2 attribute. Because this included < and > characters, I had to HTML-escape the <s before rendering it to the <pre> tag (this is the final replace() call).
After more than a few tweaks, I have managed to build something
([0-9a-zA-z-]+)\s*=\s*(("([^">]*)")|('([^'>]*)')|(([^'"=>\s]+\s)\s*(?![ˆ\s]*=))*)?
This should deal reasonably even with something like
<t key1="value1" key2='value2' key3 = value3 key4 = v a l u e 4 key5 = v a l u e 5 />