In Jinja2, how to create auto increment numbers? - jinja2

In a page, I want to display some figures with text prompt. For example:
Figure 1
Figure 2
My code is
{% set num = 0 %}
Figure {{ ++num }}
Figure {{ ++num }}
But it shows
Figure 0
Figure 0
How can I make it fulfil my expectations?

Either those text prompt are in a list already, and you can use a for to display them, along with loop.index for the numbering:
{% set texts = ['foo', 'bar', 'baz'] %}
{%- for text in texts -%}
Figure {{ loop.index }}
{{ text }}
{% endfor -%}
Will render
Figure 1
foo
Figure 2
bar
Figure 3
baz
Or, they are in another format and you will have to use a set to increment your counter:
{% set foo = 'foo' -%}
{% set bar = 'bar' -%}
{% set baz = 'baz' -%}
{% set counter = 1 -%}
Figure {{ counter }}
{{ foo }}
{% set counter = counter + 1 -%}
Figure {{ counter }}
{{ bar }}
{% set counter = counter + 1 -%}
Figure {{ counter }}
{{ baz }}
Which will render the same.

Related

Append string to array and join back into string

I'm using Home Assistant templates, which run on Jinja2 script.
I have a group of entities (states.group.doors) that have attribute battery_level. I want to build an array of entities with battery_level < min_battery level and display as a string separated by commas.
I can't figure out what's wrong with my syntax. Two questions:
Is there just a better way overall to create a list that is filtered for battery_level < min_battery_level rather than building an array like I am?
If not, then there must be something wrong with the way I am building this array. Can someone spot it?
Thanks for the help.
The following code does successfully detect battery_level < 98 and display true if anything meets that criteria, so I'm almost there.
{% set min_battery_level = 98 -%}
{% set ns = namespace(found=false, entities=[]) -%}
{% set entities = [] -%}
{% for entity_id in states.group.doors.attributes.entity_id -%}
{% set parts = entity_id.split('.') -%}
{% if (state_attr(entity_id, 'battery_level') | replace("%","") | int) < min_battery_level -%}
{% set ns.found = true -%}
{% set entities = entities + [entity_id] -%}
{% endif -%}
{% endfor -%}
{{ ns.found }}
{{ entities | join(' ') }}
Welp... kept playing with it and got it working as follows:
{% set min_battery_level = 98 -%}
{% set ns = namespace(found=false, entities = []) -%}
{% for entity_id in states.group.doors.attributes.entity_id -%}
{% set name = state_attr(entity_id, 'friendly_name') | string -%}
{% set battery = state_attr(entity_id, 'battery_level') | replace("%","") | int -%}
{% if (battery) < min_battery_level -%}
{% set ns.found = true -%}
{% set ns.entities = ns.entities + [name+' ('+battery|string+'%)'] -%}
{% endif -%}
{% endfor -%}
{{ ns.found }}
{{ ns.entities | join(', ') }}

Jekyll equivalent of Hugo's "with"

In Hugo, one can use with to avoid repeating variable names.
{{ with .Site.Params.foo }}
<p>{{ .bar }}</p>
{{ end }}
This is almost equivalent to
<p>{{ .Site.Params.foo.bar }}</p>
What would be its equivalent in Jekyll?
My try:
{%- assign tempvar = .Site.Params.foo -%}
<p>{{ tempvar.bar }}</p>
This should work:
{%- assign tempvar = site.foo -%}
<p>{{ tempvar.bar }}</p>
Based on your follow up comment to the question, it looks like you are seeking a way to check if a variable only contains a desired key:value pair. Here is one way to check if a variable contains only the desired key.
{% assign numOfKeys = page | size %}
{% assign sizeOfGold = page.gold | size %}
<!-- Print out the page object for debugging purposes -->
{{ page | inspect }}
{% if numOfKeys == 1 %}
{% if sizeOfGold > 0 %}
{% for x in page.gold %}
<!-- Logic -->
{% endfor %}
{% endif %}
{% endif %}

How can I concatenate a string using Jinja for loop?

I am trying to iteratively concatenate a string to build url params with a 'for' loop, but I believe I am having scoping issues.
The output should be: url_param = "&query_param=hello&query_param=world"
array_of_objects = [{'id':'hello'},{'id':'world'}]
{% set url_param = "" %}
{% set array_of_ids = array_of_objects|map(attribute='id')|list%} // correctly returns [1,2]
{% for id in array_of_ids %}
{% set param = '&query_param='~id %}
{% set url_param = url_param~param %}
{% endfor %}
//url_param is still an empty string
I also tried namespace(), but to no avail:
{% set ns = namespace() %}
{% set ns.output = '' %}
{% set array_of_ids = array_of_objects|map(attribute='id')|list%} // correctly returns [1,2]
{% for id in array_of_ids %}
{% set param = '&industries='~id%}
{% set ns.output = ns.output~param %}
{% endfor %}
//ns.output returns namespace
That is indeed a scope issue. One "hacky" way of dealing with this is using a list that you append to like so:
{% set array_of_objects = [{'id':'hello'},{'id':'world'}] %}
{% set array_of_ids = array_of_objects|map(attribute='id')|list%}
{{ array_of_ids|pprint }} {# output: ['hello', 'world'] #}
{% set ids = [] %} {# Temporary list #}
{% for id in array_of_ids %}
{% set param = '&query_param='~id %}
{% set url_param = url_param~param %}
{{ ids.append(url_param) }}
{% endfor %}
{{ ids|pprint }} {# output: [u'&query_param=hello', u'&query_param=world'] #}
{{ ids|join|pprint }} {# output: "&query_param=hello&query_param=world" #}
The above gets you what you need, but for this specific example I would take a look at using jinja's join filter. It's more declarative and feels a little less hacky.
{% set array_of_objects = [{'id':'hello'},{'id':'world'}] %}
{# set to a variable #}
{% set query_string = "&query_param=" ~ array_of_objects|join("&query_param=", attribute="id") %}
{{ query_string|pprint }}
{# output: u'&query_param=hello&query_param=world' #}
{# or just use it inline #}
{{ "&query_param=" ~ array_of_objects|join("&query_param=", attribute="id") }}
You should change the initialization of your namespace.
Here is an example from the docs that will help you out:
{% set ns = namespace(found=false) %}
{% for item in items %}
{% if item.check_something() %}
{% set ns.found = true %}
{% endif %}
* {{ item.title }}
{% endfor %}
Found item having something: {{ ns.found }}

Compare forloop.index|modulo:4 with 0 in Liquid (Jekyll)

I'm trying to get my home.html layout to return a row for every forth column. But this keeps up giving me true everytime.
{%- if forloop.index0|modulo:4 == 0 -%}{%- endif -%}
Any ideas?
The problem with the above code is that Jekyll expects an end_of_string but found pipe in forloop.index0|modulo:4 == 0.
You have to assign the modulo calculation to a variable and then use it in the if clause, inside the for loop:
{% assign loopindex = forloop.index0 | modulo: 4 %}
{%- if loopindex == 0 -%}
<p style="background-color:red">Hey look! I'm a fourth element</p>
{%- endif -%}

Nesting variables in Jekyll/Liquid

Assumption - From what I understand, Liquid works in a way that the variable page.my_key can be compared to a PHP array with name page and key my_key: $page['my_key']. Following this same logic we could compare {{ page.my_key }} with echo $page['my_key'].
And we could compare this front matter:
----
my_key: my_value
----
to this PHP code:
$page['my_key'] = "my_value";
Question - I would like to do something like this:
$page['my_key'] = "my_value";
$page['my_key2'] = "my_value2";
$key = "my_key";
echo $page[$key];
All I can think of is:
----
my_key: my_value
my_key2: my_value2
----
{% assign key = 'my_key' %}
{{ page.{{ key }} }}
However, that does not work... Is something like this possible, though?
Beware : array and hash are two different animals.
Just create a array-hash.md (note I wrote it in markdown for brevity) page in your jekyll. Paste this code. And you will understand how they are different and how to access their items.
---
layout: default
title: array-hash
myArray:
- item 1
- item 2
- one more
# or
myArray2: [ item 1, item 2, one more item ]
myHash:
item1: toto
"item 2": titi
item 3: yoyo
---
{% comment %} +++ Shortcuts
a = page.myArray
h = page.MyHash
h2 = page.myArray2
{% endcomment %}
{% assign a = page.myArray %}
{% assign a2 = page.myArray2 %}
{% assign h = page.myHash %}
## Arrays
page.myArray : {{ a }}
page.myArray with inspect : {{ a | inspect }}
page.myArray with join : {{ a | join:", " }}
page.myArray2 : {{ a2 | inspect }}
### Looping the array
<ul>
{% for item in a %}
<li>{{ item | inspect }}</li>
{% endfor %}
</ul>
### Targeting a specific item in the array
{% comment %} arrays start at zero {% endcomment %}
second element in the array = {{ a[1] }}
Note that {% raw %}{{ a["1"] }}{% endraw %} will not work. You need to pass
an integer and not a string.
Test (not working) : { a["1"] }
## Hashes
page.myHash : {{ h }}
#### looping the hash
{% for item in h %}
{{ item | inspect }}
{% endfor %}
You note that in the loop we get arrays that returns **key as item[0]**
and **value as item[1]**
The loop can then look like :
<ul>
{% for item in h %}
<li>{{ item[0] }} : {{ item[1] }}</li>
{% endfor %}
</ul>
### Targeting a specific item in the hash
**Item1** due to the absence of space in the key name, can both me accessed
by dot notation (h.item1) or bracket notation (h["item1"]).
hash.item1 : {{ h.item1 }}
hash["item1"] : {{ h.["item1"] }}
Item 2 and 3, containing a space in their key string can only be accessed with
bracket notation :
hash.item 2 (not working) : {{ h.item 2 }}
hash["item 2"] : {{ h.["item 2"] }}
hash.item 3 (not working) : {{ h.item 3 }}
hash["item 3"] : {{ h.["item 3"] }}
I think I found the solution:
----
my_key: my_value
my_key2: my_value2
----
{% assign key = 'my_key' %}
{{ page[key] }}
Found it here.