Jinja2 - Concatenate string in a recursive loop - recursion

I have an yaml ansible file like :
_app_config:
APP:
server: '"https://tst.example.com"'
uploadRefreshRate: 10
autonomous:
active: "false"
SiteId: 47
tests:
test1: 1
I would like to parse it and create a config file like :
APP.server = "https://tst.example.com"
APP.uploadRefreshRate = 10
APP.autonomous.active = false
APP.autonomous.SiteId = 47
APP.autonomous.tests.test1 = 1
So my jinja2 template looks like this :
{% if _app_config is defined %}
{% if _app_config.APP is defined %}
{% set key_chain = '' %}
{% for key,value in _app_config.APP.iteritems() recursive %}
{% if value is mapping %}
{% set key_chain = key_chain + key %}
{{ loop(value.iteritems()) }}
{% else %}
{% set param = 'APP.' + key_chain | string + '=' + value | string %}
{{ param | indent(loop.depth) }}
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
So the result is not the one expected :)
APP.="common.core"
AGL.=None
AGL.=false AGL.=47
AGL.=1
AGL.="https://tst.example.com" AGL.=10
The values are not ordered and key_chain concatenation does not work...
Thanks for your help !

this python 'filter_plugin' below does that :
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
from ansible.errors import AnsibleFilterError
def dict_path(my_dict, path=None, sort=True):
if isinstance(my_dict, dict):
def recurse(values, path):
for k, v in values.iteritems():
newpath = path + [k]
if isinstance(v, dict):
for u in recurse(v, newpath):
yield u
else:
yield newpath, v
if path is None:
path = []
g=recurse(my_dict, path)
if sort:
return sorted(sorted(g, key=lambda item: len(item[0])), key=lambda item: '.'.join(item[0]))
else:
return g
else:
raise AnsibleFilterError(my_dict + 'must be a dictionary')
# ---- Ansible filters ----
class FilterModule(object):
''' Dict path filter '''
def filters(self):
return {
'dict_path': dict_path
}
And add this code in your template :
{% for i in app_config | dict_path %}
{{ i.0 | join('.') }} = {{ i.1 }}
{% endfor %}
Pierre

Related

How to print dictionary of dictionaries in dot notation with Jinja2 recursive loop?

I have the following example of YAML data structure: (data and structure are subject to change)
settings:
subset1:
sub1: val1
sub2: val2
sub3: val3
subset2: val1
I want to use Jinja to print it as such: (ordering is not important)
subset1.sub2 = val2
subset1.sub3 = val3
subset1.sub1 = val1
subset2 = val1
This is what I have:
{% for key, value in settings.items() recursive %}
{{ key }}{% if value is mapping %}.{{ loop(value.items()) }}{% else %} = {{ value }}
{% endif %}
{% endfor %}
But it gives me this:
subset1.sub2 = val2
sub3 = val3
sub1 = val1
subset2 = val1
Only the first sub element is printed with its respected parent. How can I print all the sub elements with their respected parents properly?
(Again, ordering is not important)
Thanks!
This is how I solved my use case:
from jinja2 import Template
settings = {
'k1': {
'k1': '1.1',
'k2': {
'k1': '1.2.1',
'k2': {
'k1': '1.2.2.1'
},
'k3': {
'k1': {
'k1': '1.2.3.1.1'
},
},
},
},
'k2': {
'k1': '2.1'
},
'k3': '3'
}
template = """
{%- macro treedot(dict,string) %}
{%- for key, value in dict.items() -%}
{%- if value is mapping -%}
{%- if string -%}
{%- set str = string ~ '.' ~ key -%}
{%- else -%}
{%- set str = key -%}
{%- endif -%}
{{ treedot(value,str) }}
{%- else -%}
{%- if string %}
{{ string ~ '.' ~ key }} = {{ value }}
{%- else %}
{{ key }} = {{ value }}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{%- endmacro -%}
{{ treedot(settings,none) }}
"""
tpl = Template(template)
print tpl.render(settings=settings)
The result will be :
k3 = 3
k2.k1 = 2.1
k1.k2.k3.k1.k1 = 1.2.3.1.1
k1.k2.k2.k1 = 1.2.2.1
k1.k2.k1 = 1.2.1
k1.k1 = 1.1
Better than using the keyword "recursive", using a macro allow recursivity too and allow to pass as an argument the path to the value.
Before this I tried with "recursive" keyword too but wasn't able to achieve my needs since variables inside a loop are reset at each iteration.

Twig - Why are vars defined as false become empty?

Define a twig var as true, and it has a value of '1'.
Define another var as false, and it has no value. Why is that?
{% set myOtherVar = true %}
{{ myOtherVar }}
This outputs '1'
{% set myVar = false %}
{{ myVar }}
This outputs nada
https://twigfiddle.com/ebbwgf
This is bothersome because simple logic like this fails:
{% set myVar = false %}
{% if myVar is not empty and not myVar %}
stuff
{% endif %}
false is equivalent to '' for empty:
{% set myVar = '' %}
{% if myVar is empty %}
stuff
{% endif %}
{% set myOtherVar = false %}
{% if myVar is empty %}
other stuff
{% endif %}
https://twigfiddle.com/5g9thl
Depending on what you want to do, you can test if the variable exists with defined:
{% set myVar = false %}
{% if myVar is defined and not myVar %}
stuff
{% endif %}
https://twigfiddle.com/m1n1q0
Because you are using set to set the variable as a boolean. So you need to use it as a boolean. You can't print the value.
You might do your logic something like this:
{% set myOtherVar = true %}
{% set myVar = false %}
{% if not myVar %}
myVar is false
{% endif %}
{% if myOtherVar %}
myOtherVar is true
{% endif %}
Here is a twigfiddle showing it working properly:
https://twigfiddle.com/yxt1jd
Hope that helps!

Setting conditional value in twig do something block

I am trying to setting a conditional value using if and else if inside a twig template, so that is what I have to do
{% set x = '1' %}
{% if a and b %}
{% set x = '2' %}
{% elseif a or b %}
{% set x = '3' %}
{% else %}
{% set x = '4' %}
{% endif %}
The above works fine for me. But my question is, is there a way I can achieve the same using only one {% %} (do something) block.
Something like:
{%
set x = '1'
if a and b
...
elseif a or b
...
else
%}

Trying to locate some objects in a space

I'm trying to locate some objects in a field using Twig given some conditions (yes, I know it s simple, but I'm having too much troubles to have results). It's all right, but I'm having troubles giving some distance between objects.
This is my code:
{% for key, positions in teams %}
{% for key1, position in positions %}
{% for key2, player in position %}
{% set x = 100 %}
{% set counter = player.positionId|length + 1%}
{% set d = x/counter %}
{% if player.positionId == 1 %}
{% set top = 0.4 %}
{% set xpos = 42 %} //That value is correct because it's a goalkeeper
{% elseif player.positionId == 2%}
{% set top = 9.5 %}
{% set xpos = %}
{% elseif player.positionId == 3%}
{% set top = 20.5 %}
{% set xpos = %}
{% elseif player.positionId == 4%}
{% set top = 32.5 %}
{% set xpos = %}
{% endif %}
In summary, I have some players that are located on different y-coodinates given their positions, but also I need each one to have some distance between them if they have the same position, being xpos its position in the x-axis.
If you don't know Twig, you can help me using another lannguage too. Thanks in advance
Ah, and well, I want to know how to separate the teams (because it renders all players at this moment)
There are multiple ways to pass values to twig, one way is to do this through routing,
# app/config/routing.yml
blog_show:
path: /blog/{YOUR_VARIABLE_OR_VALUE_HERE}
defaults: { _controller: AcmeBlogBundle:Blog:show }
see this reference: http://symfony.com/doc/current/book/routing.html
you can also set global variables: http://symfony.com/doc/current/cookbook/templating/global_variables.html
or create an array in controller using repository or on your on, then send this to the view through the params
ie)
$repository2 = $this->getDoctrine()->getRepository('YOUR_BUNDLE_NAME_Bundle:YOUR_REPOSITORY');
$YOUR_ARRAY_NAME = $repository2->REPOSITORY_METHOD_OR_GENERIC_METHOD($IDENTIFIER);
$params = array('form' => $form->createView(), 'YOUR_ARRAY_NAME' => $YOUR_ARRAY_NAME');
return $this->render('SystemCheckoutBundle:Checkout:checkout.html.twig', $params);
these params can then be accessed in the view via:
{% for YOUR_ARRAY_NAME in YOUR_ARRAY_NAME %}
Client ID: {{ YOUR_ARRAY_NAME.contactId }}<br>
First Name: {{ YOUR_ARRAY_NAME.firstName }}<br>
Last Name:{{ YOUR_ARRAY_NAME.lastName }}<br>
Email: {{ YOUR_ARRAY_NAME.email }}<br>
{% endfor %}

Set variable in loop

I'm trying to create a json object on twig, so I need to set a variable inside a loop.
After many attempts I found this way, but it's fine when I only have two records, if I have any more you generate the problem:
{% set data = [] %}
{% for artist in artists %}
{% if loop.first %}
{%
set data = {
id : artist.id,
text : artist.name|capitalize() ~' '~ artist.surname|capitalize()
}
%}
{% else %}
{%
set data = [data,{
id : artist.id,
text : artist.name|capitalize() ~' '~ artist.surname|capitalize()
}]
%}
{% endif %}
{% endfor %}
{% set data = {results: data} %}
{{ data|json_encode|raw }}
What I want to achieve is:
{results: [{id: 1, text: "bla"},{id: 2, text: "blabla"},{id: 3, text: "blablabla"}]}
Instead I get:
{results:[[{id:1,text:"bla"},{id:2,text:"blabla"}],{id:3,text:"blablabla"}]}
Is there a way to build a json object inside twig without going crazy?
I've already tried this way .. but rewrites the object and saves in the variable only the last element:
{% set data = [] %}
{% for artist in artists %}
{%
set data = {
id : artist.id,
text : artist.name|capitalize() ~' '~ artist.surname|capitalize()
}
%}
{% endfor %}
{% set data = {results: data} %}
{{ data|json_encode|raw }}
Use merge.
{% set data = [] %}
{% for artist in artists %}
{%
set data = data|merge ([{
id : artist.id,
text : artist.name|capitalize() ~' '~ artist.surname|capitalize()
}])
%}
{% endfor %}
{% set data = {results: data} %}
{{ data|json_encode|raw }}

Resources