I want to append new key/value item to a hash inside a jinja2 for loop. Until now i have only been able to append a static entry, but i would like to add a test depending on the current element.
My data set looks like:
people:
- forename: John
name: Doe
gender: M
- forename: Jane
name: Doe
gender: F
I want to dump something like:
Hello Mr John Doe
Hello Ms Jane Doe
I can do something like :
{% for person in people -%}
Hello {{ 'Ms' if person.gender=='F' else 'Mr' }} {{ person.forename }} {{ person.name }}
{% endfor -%}
but i find this syntax awkward, especially if i have to repeat the test many times.
My idea is to add a title key to my data and i am trying to do it inside the for statement.
Jinja accept the combine filter for that:
{% for person in people | map ('combine', { "title": 'M.'} ) -%}
Hello {{ person.title ~ ' ' ~ person.forename ~ ' ' ~ person.name }}
{% endfor -%}
And i can also add a test to choose the correct title:
{% for person in people | map ('combine', { "title": 'Mr'} if 1==1 else { "title": 'Ms'}) -%}
Hello {{ person.title ~ ' ' ~ person.forename ~ ' ' ~ person.name }}
{% endfor -%}
But i have been unable to acces to the current person, i have tried thinks like person.gender=='M', self.gender=='M', this.gender=='M', but without success.
Is there any way to do it ?
Thanks !
I am answering my question since i finally have found a way to do it.
The key is to filter the people array with selectattr, then to apply the combine and do it again for the rest of the array.
{% for person in people | selectattr("gender", "equalto", "F") | map('combine', { 'title': 'Ms' }) +
people | selectattr("gender", "equalto", "M") | map('combine', { 'title': 'Mr' }) -%}
Hello {{ person.title }} {{ person.forename }} {{ person.name }}
{% endfor -%}
The for statement is of course more complex, but the loop body is straightforward.
Related
I am looking for a way to create a template in ansible with this dictionary.
data= {
"_dictionary": {
"keyone": "abc",
"rtv 4": "data2",
"longtexthere": "1",
"keythree": "data3",
"keyfour": "data1234",
}
}
The template output should have this format:
keyone abc
keytwo data2
longtexthere 1
keythree data3
keyfour data1234
With python I can create it with:
w = max([len(x) for x in data['_dictionary'].keys()])
for k,v in data['_dictionary'].items():
print(' ', k.ljust(w), ' ', v)
But I can't a way to create it in a jinja2 template in ansible. I have not found a replacement for ljust.
Currently my template is this, but I got a output without format.
{% for key, value in data['_dictionary'].items() %}
{{ "%s\t%s" | format( key, value ) }}
{% endfor %}
Any ideas, sugestion?
For example
- debug:
msg: |
{% for k,v in data['_dictionary'].items() %}
{{ "{:<15} {}".format(k, v) }}
{% endfor %}
gives
msg: |-
keyone abc
rtv 4 data2
longtexthere 1
keythree data3
keyfour data1234
See format and Format String Syntax.
Q: "Create the format dynamicaly."
A: For example, find the longest key in the dictionary. Add 1 more space to the length of the first column. In the same way, calculate the length of the second column and create the format string in a separate variable
- debug:
msg: |
{% for k,v in data['_dictionary'].items() %}
{{ fmt.format(k, v) }} # comment
{% endfor %}
vars:
col1: "{{ data._dictionary.keys()|map('length')|max + 1 }}"
col2: "{{ data._dictionary.values()|map('length')|max + 1 }}"
fmt: "{:<{{ col1 }}} {:<{{ col2 }}}"
gives
msg: |-
keyone abc # comment
rtv 4 data2 # comment
longtexthere 1 # comment
keythree data3 # comment
keyfour data1234 # comment
Is working, at the end my j2 file is:
{% set col1 = data._dictionary.keys()|map('length')|max %}
{% set fmt = "{:<" + col1|string + "} {}" %}
{% for key, value in data._dictionary.items() %}
{{ fmt.format(key, value) }}
{% endfor %}
Thank you.
The data.yml structure :
routingConfig:
accessKeyId: AKIAVL34FWX5KFFSVSCND
secretAccessKey: IglNnjk/iaR++DnQuNObAnrXsvsd9ZO+gJW5nZDd
hostedZoneId: Z03431513GGUF3XEAQE5U
recordSet:
type: A
ttl: 60
resourceIp: 52.41.8.70
jinja2 template snippet to regenerate the yml structure :
routingConfig:
{% for key,value in routingConfig.items() if value.recordSet is not defined %}
{{ key|e }}: {{ value|e }}
{% endfor %}
recordSet:
{% for key,value in routingConfig.items() if value.recordSet is defined %}
{{ key|e }}: {{ value|e }}
{% endfor %}
The actual output :
routingConfig:
recordSet: {u'type': u'A', u'resourceIp': u'52.71.3.72',
accessKeyId: AKIAVL34FWX5KFSTDFDFNCND
secretAccessKey: IglNnjk/iaR++DnQuNObAnrXRrbfvdfvd9ZO+gJW5nZDd
hostedZoneId: Z03431513GGUF3XEFBDVAQE5U
Expected output format :
routingConfig:
accessKeyId: AKIAVL34FWX5KFFSVSCND
secretAccessKey: IglNnjk/iaR++DnQuNObAnrXsvsd9ZO+gJW5nZDd
hostedZoneId: Z03431513GGUF3XEAQE5U
recordSet:
type: A
ttl: 60
resourceIp: 52.41.8.70
Any suggestion to get the output in the same structure as in expected format?
{{ routingConfig | to_yaml(default_flow_style=False) | indent(2) }}
this gives the below yml format. But seems the indentation is not quite right
routingConfig:
accessKeyId: AKIAVL34FWX5KFDFBSTNCND
recordSet:
type: A
ttl: 60
resourceIp: 52.41.8.70
secretAccessKey: IglNnjk/iaR++DnQuNObDFBAnrXsvsd9ZO+gJW5nZDd
hostedZoneId: Z03431513GGUF3XEADGBDQE5U
First of all, it does not make sense to use |e in a YAML template since that does HTML escaping, not YAML escaping. Use |to_yaml instead. Depending on your data (which you do not show), this may already suffice:
routingConfig:
{{ routingConfig | to_yaml(default_flow_style=False) | indent(2) }}
Mind that if you want to force a certain order of keys, you will need to output them separately:
routingConfig:
{% set c = routingConfig -%}
accessKeyId: {{ c.accessKeyId | to_yaml }}
secretAccessKey: {{ c.secretAccessKey | to_yaml }}
hostedZoneId: {{ c.hostedZoneId | to_yaml }}
recordSet:
{% set r = c.recordSet -%}
type: {{ r.type | to_yaml }}
ttl: {{ r.ttl | to_yaml }}
resourceIp: {{ r.resourceIp | to_yaml }}
Assuming routingConfig is already a dict have you considered using the to_yaml filter in your template
routingConfig:
{{ routingConfig | to_yaml | indent }}
I have a list in yaml file
users:
name:
- abc
- pqr
age:
- 10
- 12
I want to iterate over above values in jinja2 template.
Member in name is associated with member in age of same index. So I want to iterate over both in single line only.
{% for n in users['name'] and for a in users['age'] %}
{{ n }}
{{ a }}
{% endfor %}
For loop in this code isn't working.
I checked official documentation but I could not find any example like this.
Can anyone please help me with this?
The template below
{% for item in users.name|zip(users.age)|list %}
{{ item.0 }}
{{ item.1 }}
{% endfor %}
gives:
$ cat test.txt
abc
10
pqr
12
i'm using this line of code
<img data-animate="zoomIn" srcset="{{ 'device1.png' | asset_path | magick:resize:549x395 magick:quality:100 }} 1024w, {{ 'device1.png' | asset_path | magick:resize:280x201magick:quality:100 }} 640w" src="{{ 'device1.png' | asset_path | magick:resize:549x395 magick:quality:100 }}" alt="Mac" style="width: 100%; top: 0; left: 0;">
but i'm getting a liquid error like this
Liquid Warning: Liquid syntax error: Expected end_of_string but found
id in "{{ 'device1.png' | asset_path | magick:resize:549x395
magick:quality
Can you help me with the right syntax of this ?
Thanks in advance.
Carlos Vieira
I ran into the same issue. It seems a newer version of Liquid doesn't expect the pipe. I was able to fix it by removing the pipe altogether. Here was my issue:
Error: {% for post in site.posts | limit: 5 %}
Fixed: {% for post in site.posts limit: 5 %}
This page may help with proper liquid syntax http://jekyll.tips/jekyll-cheat-sheet/
I ran into a similar issue today with the following code:
{%- if title_case contains ' ' -%}
{%- assign all_strings = title_case | split: ' ' -%}
{%- assign the_string = '' -%}
{%- for str in all_strings -%}
{% assign new_string = str | capitalize %}
{% assign the_string = the_string | append: new_string | append: ' ' %}
{%- endfor -%}
{%- assign title_case = the_string | strip-%}
{%- endif -%}
{{ title _case }}
The problem was an extra space in the word 'title_case' - because it was a space followed by an underscore, Shopify interpreted it as an id!
According to this:
Sorting a for loop directly doesn’t generally work in my experience.
Workaround: use assign & apply sort to that array first, then use the sorted array in the for loop as below:
{% assign sortedPosts = site.posts | sort: 'last_modified_at' %}
{% for post in sortedPosts %}
...
{% endfor %}
The right answer is:
First use this plugin:
require "jekyll-assets"
class Jekyll::ImagePath < Jekyll::Assets::Liquid::Tag
def initialize(tag, args, tokens)
super("img", args, tokens)
end
private
def build_html(args, sprockets, asset, path = get_path(sprockets, asset))
path
end
end
Liquid::Template.register_tag('image_path', Jekyll::ImagePath)
then in image use
src="{% image_path 'customize-template-image.png' magick:resize: 549x375 magick:quality:100 %}"
This would fix for sure
This question is really close to Shopify Sort cart.items array using Liquid script
I have unsorted sessions that I want to sort by date and by time (expected result: 1,2,3,4).
{
"date"=>"2014-06-24",
"time"=>"09:00",
"name"=>"Session 2",
}
{
"date"=>"2014-06-25",
"time"=>"08:45",
"name"=>"Session 3",
}
{
"date"=>"2014-06-24",
"time"=>"08:00",
"name"=>"Session 1",
}
{
"date"=>"2014-06-25",
"time"=>"09:45",
"name"=>"Session 4",
}
Here's the code:
{% assign time_sorted_instances = instances | sort: "time" %}
{% assign day_sorted_instances = time_sorted_instances | sort: "date" %}
{% for instance in instances %}
{{ instance.date | date: "%A, %B %e, %Y" }} {{ instance.time }} {{ instance.session.name }} <br>
{% endfor %}
{% for instance in time_sorted_instances %}
{{ instance.date | date: "%A, %B %e, %Y" }} {{ instance.time }} {{ instance.session.name }} <br>
{% endfor %}
{% for instance in day_sorted_instances %}
{{ instance.date | date: "%A, %B %e, %Y" }} {{ instance.time }} {{ instance.session.name }} <br>
{% endfor %}
I'm able to get either instances sorted by date (2,1,3,4), or by time (1,3,2,4), but not sorted one after another (1,2,3,4). The matching function in ruby would be:
sorted_instances = instances.sort{|i| [i.date,i.time]}
Here's the code for the sort option in Liquid: https://github.com/Shopify/liquid/blob/master/lib/liquid/standardfilters.rb#L112-L123
And obviously it's not possible. If someone has a work around, let me know!
If using Javascript is an option, that could be an answer https://github.com/Shopify/liquid/issues/143#issuecomment-8718129
But I haven't found any Liquid only answer...