Change variable in included jinja2 template - jinja2

Say I have two templates:
main.j2
{% include "vars.j2" %}
main: {{ var1 }}
vars.j2
{% set var1 = 123 %}
vars: {{ var1 }}
When run, only this line is output:
vars: 123
i.e. var1 is undefined in main.j2, even though it gets set to a value in the included vars.j2 template.
How can I pass variables from included template back to template that includes it? I considered chaining extends, but wondered if there's a more elegant approach.

I recently had a need to do the same thing, and found 2 solutions.
If you have Jinja version 2.10 or later, namespaces can be used:
main_ns.j2:
{% set ns = namespace() %}
{% include "vars_ns.j2" %}
main_ns: {{ ns.var1 }}
vars_ns.j2:
{% set ns.var1 = 123 %}
vars_ns: {{ ns.var1 }}
In Jinja 2.2 or later, it can be accomplished with block scoping of variables. I put the variable settings in the base template so that multiple children can extend it.
vars_block.j2:
{% set var1 = 123 %}
vars_block: {{ var1 }}
{% block content scoped %}{% endblock %}
main_block.j2:
{% extends "vars_block.j2" %}
{% block content %}
main_block: {{ var1 }}
{% endblock %}

You can try using with:
{% with var1=0 %}
{% include "vars.j2" %}
vars: {{ var1 }}
{% endwith %}

Related

How to dynamically call dbt macros using jinja?

I have a use case where I would like to define the name of a macro and then apply it to one column.
A simplified example could be as follows. I have two macros defined that I want to call dynamically in my model (both take one column as an input):
cast_to_string
convert_empty_string_to_null_value
Now, I want to call them dynamically. See the example below
{%- set macro_names = ["cast_to_string", "convert_empty_string_to_null_value"] -%}
select
{% for macro_name in macro_names %}
-- this should dynamically be evaluated to `{{ cast_to_string(my_column) }}`
-- and `{{ convert_empty_string_to_null_value(my_column) }}`
{{ macro_name(my_column) }}
{% endfor %}
from my_model
However, this will throw an error saying that a string is not callable.
I also tried using {% raw %} {{ {% endraw %} to escape brackets, but that didn’t work either.
So, my question is, if there is a way to dynamically call macros in jinja/dbt?
I think it should work if you remove the quotes :
{%- set macro_names = [cast_to_string, convert_empty_string_to_null_value] -%}
So that jinja doesn't interpret it as string and you can use it as a Callable
I achieve it using this example :
{%- set macro_names = [print_lower, print_upper] -%}
{% for macro_name in macro_names %}
{{ macro_name("test") }}
{% endfor %}
and
{% macro print_lower(string) %}
{{ print(string|lower) }}
{% endmacro %}
{% macro print_upper(string) %}
{{ print(string|upper) }}
{% endmacro %}

How to use a Jinja variable inside of sphinx ref directive or head tag?

I'm using sphinx and auto-summary to create my documentation. However, for each module a I want to generate customized list of references. However, to do that, I need to pass the objname to ref directive. How can I achieve that?
{{ fullname | escape | underline}}
.. automodule:: {{ fullname }}
Items
-----
{% block classestab %}
{% if classes %}
{% for objname in classes %}
- :ref:` {{objname}} <{{objname}}>`_ // doesn't work
{%- endfor %}
{% endif %}
{% endblock %}
I've also trying to use a jinja variable to create headings for my documentation. However I don't know to achieve this. The code below will not work of course
{% block table %}
{% if classes %}
{% for objname in classes %}
{{objname}}
===========
{%- endfor %}
{% endif %}
{% endblock %}

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

Kramdown table of content doesn't display inside HTML block

I was following this question, but it doesn't seem to take effect for me. Any help would be greatly appreciated.
_includes/layout.html
<main>
<div>
<!-- Sidebar -->
<aside markdown="1">
<h4>Table of Contents</h4>
* ToC
{:toc}
</aside>
<!-- END Sidebar -->
<!-- Main content -->
<article>
{{ content }}
</article>
<!-- END Main content -->
</div>
</main>
_config.yml
markdown: kramdown
Result:
Update
_layouts/site.html
<aside markdown="1">
mark**down**
</aside>
It just renders as above. Kramdown is turned on in config.
Solved by using pure liquid ToC by allejo.
% capture tocWorkspace %}
{% comment %}
"...like all things liquid - where there's a will, and ~36 hours to spare, there's usually a/some way" ~jaybe
Usage:
{% include toc_pure_liquid.html html=content sanitize=true class="inline_toc" id="my_toc" h_min=2 h_max=3 %}
Parameters:
* html (string) - the HTML of compiled markdown generated by kramdown in Jekyll
Optional Parameters:
* sanitize (bool) : false - when set to true, the headers will be stripped of any HTML in the TOC
* class (string) : '' - a CSS class assigned to the TOC
* id (string) : '' - an ID to assigned to the TOC
* h_min (int) : 1 - the minimum TOC header level to use; any header lower than this value will be ignored
* h_max (int) : 6 - the maximum TOC header level to use; any header greater than this value will be ignored
Output:
An unordered list representing the table of contents of a markdown block. This snippet will only generate the table of contents and will NOT output the markdown given to it
{% endcomment %}
{% capture my_toc %}{% endcapture %}
{% assign minHeader = include.h_min | default: 1 %}
{% assign maxHeader = include.h_max | default: 6 %}
{% assign nodes = include.html | split: '<h' %}
{% for node in nodes %}
{% if node == "" %}
{% continue %}
{% endif %}
{% assign headerLevel = node | replace: '"', '' | slice: 0, 1 %}
{% assign headerLevel = headerLevel | times: 1 %}
{% assign indentAmount = headerLevel | minus: minHeader | add: 1 %}
{% assign _workspace = node | split: '</h' %}
{% unless headerLevel >= minHeader %}
{% continue %}
{% endunless %}
{% if headerLevel > maxHeader %}
{% continue %}
{% endif %}
{% assign _idWorkspace = _workspace[0] | split: '"' %}
{% assign html_id = _idWorkspace[1] %}
{% capture _hAttrToStrip %}{{ headerLevel }} id="{{ html_id }}">{% endcapture %}
{% assign header = _workspace[0] | replace: _hAttrToStrip, '' %}
{% assign space = '' %}
{% for i in (1..indentAmount) %}
{% assign space = space | prepend: ' ' %}
{% endfor %}
{% capture my_toc %}{{ my_toc }}
{{ space }}- [{% if include.sanitize %}{{ header | strip_html }}{% else %}{{ header }}{% endif %}](#{{ html_id }}){% endcapture %}
{% endfor %}
{% if include.class %}
{% capture my_toc %}{:.{{ include.class }}}
{{ my_toc | lstrip }}{% endcapture %}
{% endif %}
{% if include.id %}
{% capture my_toc %}{: #{{ include.id }}}
{{ my_toc | lstrip }}{% endcapture %}
{% endif %}
{% endcapture %}{% assign tocWorkspace = '' %}
{{ my_toc | markdownify }}
In theory it should work that way, (it is not working for me either) but you can force to process the code inside a block with `markdown="1" like this:
<aside markdown="1">
<h4>Table of Contents</h4>
* ToC
{:toc}
</aside>
Make sure you don't indent the code inside the aside tag or it will be parsed as kramdown code.
By default, kramdown parses all block HTML tags and all XML tags as
raw HTML blocks. However, this can be configured with the
parse_block_html. If this is set to true, then syntax parsing in HTML
blocks is globally enabled. It is also possible to enable/disable
syntax parsing on a tag per tag basis using the markdown attribute:
If an HTML tag has an attribute markdown="0", then the tag is parsed as raw HTML block.
If an HTML tag has an attribute markdown="1", then the default mechanism for parsing syntax in this tag is used.
Update
I've checked your repo, you need to rename index.html to index.md so kramdown will parse it and then you can also add the line to _config.yml to parse markdown inside html blocks.
Jekyll only parse .md or .mardown files.
.html files are not proccessed by markdown parser.
If you rename a file from .html to .md, it will be processed as kramdown.
But there you will have problems with indentation.
Mixing html and markdown is not so easy ;-)

Pass variables from child template to parent in Jinja2

I want to have one parent template and many children templates with their own variables that they pass to the parent, like so:
parent.html:
{% block variables %}
{% endblock %}
{% if bool_var %}
{{ option_a }}
{% else %}
{{ option_b }}
{% endif %}
child.html:
{% extends "parent.html" %}
{% block variables %}
{% set bool_var = True %}
{% set option_a = 'Text specific to this child template' %}
{% set option_b = 'More text specific to this child template' %}
{% endblock %}
But the variables end up undefined in the parent.
Ah. Apparently they won't be defined when they are passed through blocks. The solution is to just remove the block tags and set it up like so:
parent.html:
{% if bool_var %}
{{ option_a }}
{% else %}
{{ option_b }}
{% endif %}
child.html:
{% extends "parent.html" %}
{% set bool_var = True %}
{% set option_a = 'Text specific to this child template' %}
{% set option_b = 'More text specific to this child template' %}
If Nathron's solution does not fix your problem, you can use a function in combination with a global python variable to pass a variable value.
Advantage: The variable's value will available in all templates. You can set the variable inside a block.
Disadvantage: More overhead.
This is what I did:
child.j2:
{{ set_my_var('new var value') }}
base.j2
{% set my_var = get_my_var() %}
python code
my_var = ''
def set_my_var(value):
global my_var
my_var = value
return '' # a function returning nothing will print a "none"
def get_my_var():
global my_var
return my_var
# make functions available inside jinja2
config = { 'set_my_var': set_my_var,
'get_my_var': get_my_var,
...
}
template = env.get_template('base.j2')
generated_code = template.render(config)
In some cases, you can avoid this 'parameter-passing' by creating another variant of the parent that adds/removes some block and extends it instead.
{% extends [condition]|yesno:'parent_1.html,parent_2.html' %}