swig-template testing condition with subdocument - json

I have a json:
var json = [{
a: "asdf",
b: "a",
c: {1:{z:30,x:20,y:50},2:{z:30,x:50,y:30}}
},
{
a: "fdsa",
b: "o",
c: {1:{z:10,x:20,y:50},2:{z:0,x:20,y:30}}
}
]
I want to have a condition to check:
if any item z, x, or y in the c object is greater than 30, show the value for a
Is this possible? I did some research but couldn't find any answers.
Please help! Thanks!
I tried
{% for c,b in json.c %}

Your use case is incredibly complex and probably better done server-side, but here's a way you can do it in swig...
{% for item in json %}
{% set show = false %}
{% for set in item.c %}
{% for k in set %}
{% if k > 30 %}
{% set show = true %}
{% endif %}
{% endfor %}
{% endfor %}
{% if show %}
{{ item.a }}
{% endif %}
{% endfor %}

Related

Nested loops in jinja2 template

I am templating JSON with jinja2 and trying to iterate through list of lists, using 4 nested loops, and it is failing with message:
"AnsibleError: template error while templating string: expected token ':', got '}'"
As original JSON template is a bit bigger, I miss to paste here simple JSON key:value pairs.
Can you please support me on this. Thanx
This is path of template where main logic is:
{ ....
"panels": [
{% set frame_meter_servers= ["frame-meter10-246-44-20", "frame-meter10-246-45-92", "frame-meter10-246-46-234"] %}
{% set frame_procstat_meter_services= ["frame-meter-clock.service", "frame-meter-web.service"] %}
{% set frame_cpanel_backend_config_cluster_nodes= ["10.246.45.189", "10.246.46.102", "10.246.44.163"] %}
{% set frame_procstat_cpanel_services= ["frame-cpanel-backend"] %}
{% set frame_services_monitoring_hosts= ["frame_cpanel_backend_config_cluster_nodes", "frame_meter_servers"] %}
{% set frame_platform_services= ["frame_procstat_cpanel_services", "frame_procstat_meter_services"] %}
{% set id = namespace(total=1) %}
{% set z = namespace(total=0) %}
{% for node in frame_services_monitoring_hosts %}
{% set nodeloop = loop %}
{% for service in frame_platform_services %}
{% if nodeloop.index == loop.index %}
{
...
"x": 0,
"y": {{ z.total }}
},
"id": {{ id.total }},
"panels": [],
...
},
{% set x = namespace(total=0) %}
{% set y = namespace(total=1) %}
{% for hosts in node %}
{% set x = namespace(total=0) %}
{% for services in service %}
{
...
"x": {{ x.total }},
"y": {{ y.total }}
},
"id": {{ id.total }},
...
},
{% set x.total = 4 + x.total %}
{% set id.total = 1 + id.total %}
{% endfor %}
{% set y.total = 6 + y.total %}
{% endfor %}
{% set y = namespace(total=4) %}
{% for hosts in node %}
{% set x = namespace(total=0) %}
{% for services in service %}
{
...
],
"valueName": "current"
} {% set x.total = 4 + x.total %} {% set id.total = 1 + id.total %} {% if not loop.last %},{% endif %}
{% endfor %}
{% set y.total = 6 + y.total %} {% if not loop.last %},{% endif %}
{% endfor %}
{% if node|length == 2 %} {% set z.total = 14 + z.total %} {% else %} {% set z.total = 20 + z.total %}{% endif %}{% if not loop.last %},{% endif %}{% set id.total = 1 + id.total %}
{% endif %}
{% endfor %}
{% set id.total = 1 + id.total %}{% if not loop.last %},{% endif %}
{% endfor %}
],
...
{% if {{ nodeloop.index }} == {{ loop.index }} %}
That is incorrect syntax; everything inside the {% and %} is conceptually Python; what you want to say is:
{% if nodeloop.index == loop.index %}
I didn't check the rest of that wall of text, but I'm super positive that if was wrong, so let's start there

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 }}

jekyll: Is it possible to use a page.variable, as an operator inside a conditional if statement?

JSON file in path: _data/integers.json which looks like this:
{
"100": [
{
"value": "true"
}
]
}
In Jekyll page:
---
integers:
- 100
- 200
---
What I'm trying to do:
{% assign json = site.data.integers %}
{% for integer in page.integers %} // loop
{% if json.{{ integer }}[0].value == "true" %} ... {% endif %}
{% endfor %}
e.g. use the {{ integer }} (aka page.integer[0]) as an operator inside the conditional statement.
Is there a method? ... asking for a friend.
If we keep your json and page.integers as is :
{% assign json = site.data.integers %}
{{ json | inspect }}
{% for integer in page.integers %}
{% comment %} Here we cast our integer to a string,
as json keys are strings
(100 | append:"" => "100")
{% endcomment %}
{% assign intAsStr = integer | append:"" %}
{% comment %} as a possible json[intAsStr] returns an array,
we retrieve the first and only element in it
{% endcomment %}
{% assign data = json[intAsStr].first %}
{% if data["value"] == "true" %}
<h1>We have a match on {{ intAsStr }}</h1>
{% endif %}
{% endfor %}
We can simplify a little with some refactoring
data/integers.json
{
"100": { "value": true }
}
jekyll page
---
integers:
- "100"
- "200"
---
{% assign json = site.data.integers %}
{{ json | inspect }}
{% for integer in page.integers %}
{% assign data = json[integer] %}
{% if data["value"] == true %}
<h1>We have a match on {{ integer }}</h1>
{% endif %}
{% endfor %}

One variable for different collections in Jekyll to use in forloop

I have several collections on my Jekyll site. I've added post navigation to one of the collections displaying a counter on each post page:
{% assign testimonials = site.testimonials %}
{% assign page_order = 1 %}
{% for node in testimonials reversed %}
{% if node.url == page.url %}
{{ page_order }} from {{ forloop.length }}
{% else %}
{% assign page_order = page_order | plus: 1 %}
{% endif %}
{% endfor %}
I would like to make this code work not only for site.testimonials, but for other collections as well. I tried to pass a variable for collections like this:
{% capture label %}{{ page.collection }}{% endcapture %}
{% assign collection = site.collections | where: "label",label | first %}
{% for node in collection reversed %}
{% if node.url == page.url %}
{{ page_order }} from {{ forloop.length }}
{% else %}
{% assign page_order = page_order | plus: 1 %}
{% endif %}
{% endfor %}
But it doesn't work. Is there any way to pass a variable for all collections in Jekyll to use in forloop in post navigation?
When you access collection with site.testimonials, you get collection's documents array.
{{ site.testimonials | inspect }}
# output >> [#<Jekyll::Document ...>, #<Jekyll::Document ...>, ...]
When you access a collection while looping over site.collection, you receive the collection's object :
{% assign collection = site.collections | where: "label",page.collection | first %}
{{ collection | inspect }}
# output >> { "output": true, "label": "collectionLabel",
"docs": [ doc1, docs2, ... ], "files": [],
"directory": "/path/to/collection",
"relative_directory": "_colectionDirectory" }
In your case, you just have to replace :
{% for node in collection reversed %}
By :
{% for node in collection.docs reversed %}

Merge dicts of arbitrary depth in Jinja2

How can I recursively merge dicts in Jinja2 without having to define a custom filter?
So
{ a: { b: 'ab', d: 'ad' } }
+ { a: { b: 'aX', e: 'ae' } }
becomes
{ a: { b: 'aX', d: 'ad', e: 'ae' } }
I'm migrating some Mako-templates to Jinja2. With Mako I could simply use a small python-code snippet doing this for me... The reason I cannot use a custom filter is, that I simply cannot register custom filters in the template engine as I cannot directly access it.
I finally found a solution to my problem:
{%- macro deep_merge(a, b): %}
{%- for k,v in b.items(): %}
{%- if v is not defined: %}
{%- do a.pop(k) %}
{%- else: %}
{%- if v is mapping: %}
{%- if a[k] is not mapping: %}
{%- do a.update({ k: { } }) %}
{%- endif %}
{%- do deep_merge(a[k], v) %}
{%- else: %}
{%- do a.update({ k: v }) %}
{%- endif %}
{% endif %}
{%- endfor %}
{%- endmacro %}
{%- do deep_merge(a, b) %}
This snippet of Jinja2-Code recursively merges b into a, overriding duplicate keys (b takes precedence) and removing keys from a that are set to null in b.
It's ugly, but it sounds like your situation is a bit Abby Normal:
{# Untested code - may not work #}
{% macro merge(destination) %}
{% for provider in varargs %}
{% for key, value in provider.items() %}
{% if value is mapping %}
{% set intermediate = {} %}
{% do merge(intermediate, destination.get(key, {}), value) %}
{% do destination[key] = intermediate %}
{% else %}
{% do destination[key] = value %}
{% endif %}
{% endfor %}
{% endfor %}
{% endmacro %}
And you can use it like this:
{% set result = {} %}
{% do merge(result, first_values, second_values) %}
{# result is now the merger of first_values and second_values #}