Merge dicts of arbitrary depth in Jinja2 - 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 #}

Related

Argument into {% for loop %} in jinja2

There is given a list of array results in format:
result_a, result_b, result_c etc.
A foo and bar are some variables, not strings.
There is need to put macro argument into for loop.
{% macro test(foo, bar) %}
{% for item in results_{{foo}} %}
{% if item[0].name == result_{{foo}}[bar] %}
{{ item }} pass test
{% endif %}
{% endfor %}
{% endmacro %}
During calling macro test(a, names) there is error. How to implement argument into {% for block %}.

Listing all the key-value properites in site.pages

On my blog, I have a debug page which lists several things, including all the properties for each page in site.pages, like so:
{%- for page in site.pages -%}
<strong>{{ page.path }}:</strong><br>
<table>
{% for key_value in page %}
{% if key_value[0] == "content" %}
<tr><td>content</td><td>[{{ key_value[1] | size }} characters]</td></tr>
{% else %}
<tr><td>{{ key_value[0] }}</td><td><span>{{ key_value[1] }}</span></td></tr>
{% endif %}
{% endfor %}
</table>
{%- endfor -%}
This produces output like this:
So far, so good.
Now, I want to do the exact same thing with site.posts, but it doesn't work.
In particular, key_value[0] and key_value[1] don't have any value. The iteration does produce something, but it is not a key an value.
For example, the following produces a list of key names:
{%- for pp in site.posts -%}
<strong>{{ pp.path }}:</strong><br>
{% for kv in pp %}
{% capture cap %}{{ kv }}{% endcapture %}
<div>{{ cap }}</div>
{% endfor %}
<br>
{%- endfor -%}
like so:
I can't seem to get the content of the properties in a generic way, however. I guess the type of the things in the site.posts is not the same as sites.pages, in particular not a simple liquid dictionary.
I've tried messing around with to_liquid mentioned here without luck.

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

Output Markup Inside of Tag Markup

My question is how can I rewrite this output markup (which does work)
{{ forloop.index0 | modulo:4 }}
so that it can be used inside of a tag markup (which doesn't work).
{% if forloop.index0 | modulo:4 == 0 %}
This is what ended up working (David helped me get started).
{% assign mod = forloop.index0 | modulo:4 %}
{% if mod == 0 %}
<!-- Do stuff -->
{% endif %}
Edited answer :
For this example I use a custom array, but the array can be site.pages or site.posts or site.data.somedatas.
{% assign words = "zero,one,two,three,four,five" | split: ',' %}
Then process the words array :
<ol>
{% for word in words %}
{% comment %} Here we assign the filtered array to myTest {% endcomment %}
{% assign myTest = forloop.index0 | modulo:4 %}
{% comment %} then we process the filtered array {% endcomment %}
{% if myTest == 0 %}
<li>
<h4>Test passing (index = {{ forloop.index0 }} >> modulo = {{ myTest }})</h4>
</li>
{% else %}
<li>
<h4>test NOT passing (index = {{ forloop.index0 }} >> modulo = {{ myTest }})</h4>
</li>
{% endif %}
{% endfor %}
</ol>
This works on Jekyll 2.2.0, the current Github pages version.

swig-template testing condition with subdocument

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