Liquid templates: strange behavior when assigning variables within for loops - template-engine

I'm learning Liquid (using grunt-liquid). I've run into some extremely strange behavior with a simple for loop. Perhaps you can tell me what I'm doing wrong.
Here is version 1 of my code, which works as expected:
{% assign isThree = 'initial value' %}
{% for i in (1..9) %}
{% if i == 3 %}
{% assign isThree = true %}
{% else %}
{% assign isThree = false %}
{% endif %}
<p>{{ i }}: {{ isThree }}</p>
{% endfor %}
Basically, it loops through the numbers 1 through 9, and displays each one, along with a boolean indicating whether or not the number is three. This produces the expected output:
1: false
2: false
3: true
4: false
5: false
6: false
7: false
8: false
9: false
And here's version 2 of my code, which exhibits the strange behavior in question. It's identical to version 1, except it now assigns a variable named someOtherVar before assigning isThree:
{% assign isThree = 'initial value' %}
{% for i in (1..9) %}
{% assign someOtherVar = 'foo' %}
{% if i == 3 %}
{% assign isThree = true %}
{% else %}
{% assign isThree = false %}
{% endif %}
<p>{{ i }}: {{ isThree }}</p>
{% endfor %}
This change shouldn't affect the output at all, but it does! In effect, it makes it so that values assigned to isThree aren't available until the next iteration of the loop:
1: initial value
2: false
3: false
4: true
5: false
6: false
7: false
8: false
9: false
If I move the someOtherVar assignment below the {% if %} block, then everything works fine again.
What the heck is going on here?

Related

How to write a Ruby "if" statement in HAML

- entries.each do |entry|
- if !isOpen
- columnClass = 'col-1'
- if entry[:variant] == 'medium'
- columnClas = 'col-2'
- if entry[:variant] == 'larg'
- columnClas = 'col-3'
%div{:class => "GridTeaserArea--column #{columnClass}"}
= component 'GridTeaser', entry
You were close, you just need more indentations with if and rearrangement.
- entries.each do |entry|
- if !isOpen
- columnClass = 'col-1'
- if entry[:variant] == 'medium'
- columnClas = 'col-2'
- if entry[:variant] == 'larg'
- columnClas = 'col-3'
%div{:class => "GridTeaserArea--column #{columnClass}"}
= component 'GridTeaser', entry
Change code as per the logic you have in mind. I tried to show how to use an if conditional inside HAML. The code output may not be what you desired, as it is not stated, I didn't attempt to assume it.
Please read HAML tutorial to get yourself comfortable with HAML.
- isOpen = false
- entries.each do |entry|
- if !isOpen
- columnClass = 'col-1'
:ruby
if (entry[:variant] == 'medium')
columnClass = 'col-2'
end
if (entry[:variant] == 'larg')
columnClass = 'col-3'
end
= "<div class='GridTeaserArea--column #{columnClass}'>"
= component 'GridTeaser', entry

How to render an Array to a YAML list with ERB?

I am trying to render an Array with ERB to a YAML file.
Input:
arr = [1,2,3]
Expected output:
---
tags:
- 1
- 2
- 3
Code:
tags:
<%- #arr.each do |tag| -%>
- <%= tag %>
<% end -%>
- extra-tag
This renders the following YAML
---
tags:
- 1 - 2 -3
Is there a way to render this properly?
The only problem I see in your example is the missing leading - from the closing <% end -%> (should be <%- end -%>).
# foo.erb:
<%- #arr = [1,2,3] -%>
tags:
<%- #arr.each do |tag| -%>
- <%= tag %>
<%- end -%>
- extra tag
Output:
$ erb -T - foo.erb
tags:
- 1
- 2
- 3
- extra tag
Without the leading - the result I get is different from yours:
tags:
- 1
- 2
- 3
- extra tag

How to change the order for two items in .each in ruby

I have a function in my .erb file and in it I have the following:
<%select id ="dropdown">
<option disabled selected value="select"> - select - </option>
<% #variable.each do |x| %>
<%= "<option value='#{x['code']}'>#{x['description']} </option>".html_safe %>
<% end %>
<%/select>
But I need to have two items (TUA and CRL) to be displayed at the top and the rest to be displayed in alpha order (which currently is) so it should be .
<%select id ="dropdown">
<option disabled selected value="select"> - select - </option>
<% #variable.each do |x| %>
<% next if x['code'] == 'TUA' or x['code'] == 'CRL' %>
<%= "<option value='#{x['code']}'>#{x['description']} </option>".html_safe %>
<% end %>
<%/select>
How can I do that ? I have hardcoded it and I don't want that.
Not sure how #variable is declared, but what if you try arranging it in the controller?
#variable = []
#variable << Variable.find_by(code: 'TUA')
#variable << Variable.find_by(code: 'CRL')
#variables = Variable.all.order('code DESC')
#variables.each do |x|
if x.code != 'TUA' && x.code!= 'CRL'
#variable << x
end
end

How can I read a string into a Ruby dictionary?

I currently want to replace my Wordpress-Blog by a Jekyll-Blog. To do so, I have to find an alternative to WordPress caption tags:
[caption id="attachment_76716" align="aligncenter" width="500"]<img src="http://martin-thoma.com/wp-content/uploads/2013/11/WER-calculation.png" alt="WER calculation" width="500" height="494" class="size-full wp-image-76716" /> WER calculation[/caption]
I thought it would be nice, if I could use them like that in my posts:
{% caption align="aligncenter" width="500" alt="WER calculation" text="WER calculation" url="../images/2013/11/WER-calculation.png" %}
While it should get rendered to:
<div style="width: 510px" class="wp-caption aligncenter">
<a href="../images/2013/11/WER-calculation.png">
<img src="../images/2013/11/WER-calculation.png" alt="WER calculation" width="500" height="494" class="size-full">
</a>
<p class="wp-caption-text">WER calculation</p>
</div>
So I've written some python code that does the replacement (once) and I wanted to write a Ruby / Liquid / Jekyll plugin that does the rendering. But I don't know how to read
align="aligncenter" width="500" alt="WER calculation" text="WER calculation" url="../images/2013/11/WER-calculation.png"
into a ruby dictionary (they seem to be called "Hash"?).
Here is my plugin:
# Title: Caption tag
# Author: Martin Thoma, http://martin-thoma.com
module Jekyll
class CaptionTag < Liquid::Tag
def initialize(tag_name, text, tokens)
super
#text = text
#tokens = tokens
end
def render(context)
#hash = Hash.new
#array = #text.split(" ")
#array.each do |element|
key, value = element.split("=")
#hash[key] = value
end
#"#{#text} #{#tokens}"
"<div style=\"width: #{#hash['width']}px\" class=\"#{#hash['alignment']}\">" +
"<a href=\"../images/#{#hash['url']}\">" +
"<img src=\"../images/#{#hash['url']}\" alt=\"#{#hash['text']}\" width=\"#{#hash['width']}\" height=\"#{#hash['height']}\" class=\"#{#hash['class']}\">" +
"</a>" +
"<p class=\"wp-caption-text\">#{#hash['text']}</p>" +
"</div>"
end
end
end
Liquid::Template.register_tag('caption', Jekyll::CaptionTag)
In Python, I would use the CSV module and set delimiter to space and quotechar to ". But I'm new to Ruby.
I've just seen that Ruby also has a CSV-module. But it doesn't work, as the quoting isn't correct. So I need some html-parsing.
A Python solution
def parse(text):
splitpoints = []
# parse
isOpen = False
for i, char in enumerate(text):
if char == '"':
isOpen = not isOpen
if char == " " and not isOpen:
splitpoints.append(i)
# build data structure
dictionary = {}
last = 0
for i in splitpoints:
key, value = text[last:i].split('=')
last = i+1
dictionary[key] = value[1:-1] # remove delimiter
return dictionary
print(parse('align="aligncenter" width="500" alt="WER calculation" text="WER calculation" url="../images/2013/11/WER-calculation.png"'))
If you set row separator to space, column separator to '=' and quote char to '"', you can easily parse your string into Hash with Ruby's CSV class:
require 'csv'
def parse_attrs(input)
options = { col_sep: '=', row_sep: ' ', quote_char: '"' }
csv = CSV.new input, options
csv.each_with_object({}) do |row, attrs|
attr, value = row
value ||= true
attrs[attr] = value
end
end
Example:
irb(main):031:0> input = 'align="aligncenter" width="500" alt="WER calculation" text="WER calculation" url="../images/2013/11/WER-calculation.png" required'
=> "align=\"aligncenter\" width=\"500\" alt=\"WER calculation\" text=\"WER calculation\" url=\"../images/2013/11/WER-calculation.png\""
irb(main):032:0> parse_attrs input
=> {"align"=>"aligncenter", "width"=>"500", "alt"=>"WER calculation", "text"=>"WER calculation", "url"=>"../images/2013/11/WER-calculation.png"}

how to find all links at the same depth with a common closest ancestor with nokogiri

d=<<"EOM"
<ul>
<li><a id=t href="t">a</a></li>
<li><a id=b href="b">b</a></li>
<li>
<ul>
<li>don't want inner</li>
<li>don't want inner</li>
</ul>
</li>
<li><a id=c href="c">c</a></li>
</ul>
<ul>
<li>don't want</li>
</ul>
EOM
doc = Nokogiri.HTML(d)
t = doc.css("#t")[0]
how can i get all hrefs that have the same
outer container as "t" and are at the same
depth as "t"? in this case i'd want just the
hrefs t,b,c.
these will not always be in ul's, just using
it as an example.
To get all a tags with the same 'grandparent' as t you could do:
doc.css('a').select{|a| a.parent.parent == t.parent.parent}
To get their hrefs:
doc.css('a').select{|a| a.parent.parent == t.parent.parent}.map{|a| a[:href]}
If you know the IDs will be consistent:
puts doc.search('#t, #b, #c').map{ |n| n['href'] }
If you don't know what they would be, then XPath can get you there:
doc.search('//*[#id="t"]/../../*/*[#id]').to_html
=> "<a id=\"t\" href=\"t\">a</a><a id=\"b\" href=\"b\">b</a><a id=\"c\" href=\"c\">c</a>"
doc.search('//*[#id="t"]/../../*/*[#id]').map{ |n| n['href'] }
=> ["t", "b", "c"]
That means "find the node with an ID of 't', then back up two levels and look down finding the nodes with populated id attributes".
Thanks #pguardiario
The parent node could be at any level, so I modified your code like so:
t = doc.css("#a")[0]
r = []
p = t.parent
x = 0
while true
break if p.node_name == "body" || p.node_name == "html"
x += 1
r = doc.css('a').select{|a|
m = a
x.times { m = m.parent }
m == p
}
break if r.length > 1
p = p.parent
end
pp r.length
I'm sure there's a better way than this brute force method.

Resources