概述

ansible中很多地方都用到了jinja2,因此,在学习ansible之前,应该对jinja2有一定的了解。

jinja2是一个Python模版引擎。在jinja2模版中,可以包含变量或表达式,渲染模版时,变量和表达式会被替换成值。也可以在模版中使用标签(tags)控制模版的逻辑。


定界符

默认的jinja2定界符被配置如下:


变量

模版变量是被 渲染时传递给模版的上下文字典 定义的。在jinja2模版中,可以像Python中一样,通过"."运算符,访问变量的属性;通过"[]"运算符,获取元素。比如:

{{foo.bar}}
{{foo['bar']}}

为了便利,jinja2的foo.bar在Python层面做下面的事情:

foo['bar']做下面的事情:

变量可以被过滤器修改,变量和过滤器之间使用管道符号"|"分开。

可以将多个过滤器级联起来,此时,一个过滤器的输出,会被应用到下一个过滤器。

例如:{{ name|striptags|title }}等价于title(striptags(name)))。

还可以对变量和表达式进行测试(test)。测试的语法是:

variable/expression is test

比如,判断变量name是否被定义,可以使用:

name is defined

页面:http://jinja.pocoo.org/docs/dev/templates/#builtin-filters描述了所有内建的过滤器。

页面:http://jinja.pocoo.org/docs/dev/templates/#builtin-tests描述了所有内建的测试。


空白控制

当把减号("-")放到一个块的开始的时候,那么块之前的所有空白字符都会被删除;

当把减号("-")放到一个块的结尾的时候,那么块之后的所有空白字符都会被删除。比如,执行下面的脚本:

from jinja2 import Template
template_string = """
{%- for item in seq -%}
    {{ item }}
{%- endfor %}
"""
template = Template(template_string)
result = template.render({"seq": range(10)})
print(result)

会输出:

0123456789

注意:不能在标签和减号之间添加空格


raw语句

在jinja2中,可以通过raw语句忽略所有的模版语法。比如,执行下面的脚本:

from jinja2 import Template
template_string = """
{%- raw -%}
<ul>
{% for item in seq %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{%- endraw %}
"""
template = Template(template_string)
result = template.render({"seq": range(10)})
print(result)

会把raw语句块中的内容原样输出:

<ul>
{% for item in seq %}
<li>{{ item }}</li>
{% endfor %}
</ul>

模版继承

jinja2最强大的部分就是模版继承。在使用模版继承的时候,需要先创建一个基础的骨架模版(父模版)。父模版需要包含所有的公共元素,定义能够在子模版中重写的块。

比如(base.j2):

start
===
{% block first_section %}{% endblock %}
===
{% block second_section %}{% endblock %}
===
{% block third_section -%}
this is the third section
{%- endblock %}
===
end

在这个例子中,通过block语句定义了三个能在子模版中重写的块。需要额外说明的是:为了便于阅读,{% endblock %}标签中也可以包含块的名字。

下面是一个子模版(sub.j2):

{% extends 'base.j2' %}

{% block first_section -%}
this is the first section
{%- endblock %}


{% block second_section -%}
this is the second section
{%- endblock %}


{% block third_section -%}
{{super()}}
{%- endblock %}

extends语句用于指定父模版。模版引擎加载模版的时候,会首先定位其父模版。

extends语句应该是子模版中的第一个标签。否则,可能会导致混乱。

父模版的名字依赖于模版加载器,比如,FileSystemLoader可以通过文件名加载模版,那么,在使用FileSystemLoader的时候,可以用如下的方式指定父模版:

{% extends 'layout/default.j2' %}

在子模版的块中,可以通过调用super()函数,获取父模版中的块的内容。

不能在同一个模版中定义多个名称相同的block。

可以通过self变量和块的名字调用块,这样就可以复用块了:

start
===
{% block first_section %}{% endblock first_section %}
===
{% block second_section %}{% endblock %}
===
{% block third_section -%}
this is the third section
{%- endblock %}
===
{{self.first_section()}}
{{self.second_section()}}
{{self.third_section()}}
===
end

块可以嵌套到其他语句中,以实现更加复杂的布局。但是在块内无法访问外层作用域的变量。比如:

{% for item in seq %}
<li>{% block loop_item %}{{ item }}{% endblock %}</li>
{% endfor %}

会输出空的li元素,因为item变量在块内是未定义的。

从jinja2.2开始,可以通过在定义block时,增加scoped修饰符的方式,使block可以访问外部作用域的变量。比如:

{% for item in seq %}
<li>{% block loop_item scoped %}{{ item }}{% endblock %}</li>
{% endfor %}

当重载一个模块的时候,scoped修饰符不必提供。


流程控制

for循环用于遍历一个序列中所有元素。在for循环中,可以使用一些特殊的变量:

比如:

{% for item in seq %}
	{% if loop.first %}
		this is the first time
	{% endif %}
	{{loop.index0}}, {{item}}
	{{loop.cycle("even", "odd")}}
{% endfor %}

jinja2中的if语句,与Python中的if语句相同。比如:

{% for item in seq %}
	item is: {{item}}

	{% if item % 2 == 0 %}
		this is even number
	{% else %}
		this is odd number
	{% endif %}

	{{'this is even number' if item % 2 == 0 else 'this is odd number'}}

{% endfor %}

宏相当于函数。它把经常使用的语句封装起来,以便复用。可以像调用函数一样调用宏。比如:

{% macro macro_name(argument) %}
	this is {{argument}}
{% endmacro %}

{{ macro_name("tim") }}
{{ macro_name("zhou") }}

如果宏是在其他模版中定义的,那么在调用之前,必须先导入它。

比如:

[vagrant@mongodb-101 ~]$ tree .
.
|-- base.j2
|-- test.j2
`-- test_jinja.py

0 directories, 3 files
[vagrant@mongodb-101 ~]$ cat base.j2
{% macro macro_name(arguement) %}
	macro_name is invoked with arguement: {{arguement}}
{% endmacro %}
[vagrant@mongodb-101 ~]$cat test.j2
{% import 'base.j2' as base %}
{{ base.macro_name('test.j2') }}
[vagrant@mongodb-101 ~]$ cat test_jinja.py
from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader("."))
template = env.get_template("test.j2")
res = template.render({})
print(res)
[vagrant@mongodb-101 ~]$ python test_jinja.py


	macro_name is invoked with arguement: test.j2

如果宏的名称以下划线开头,那么它不能被导入和导出。

在宏内部,可以访问三个特殊的变量:

宏对象有如下属性:

下面是一个通过call语句调用宏的例子:

{% macro macro_name(argument) %}
	macro_name is invoked with argument: {{argument}}
	{% if caller %}
		{{ caller('macro_name') }}
	{% endif %}
{% endmacro %}

{% call(name) macro_name('caller') %}
	call is invoked by macro {{name}}
{% endcall %}

在这个例子中,使用call语句调用了宏macro_name,在宏macro_name中,又通过变量caller调用了调用宏的call语句块。call语句块本质上就是一个匿名的宏(它也可以接受参数)。


赋值

在代码块内,可以用set语句给变量赋值。并且,顶级的变量与顶级的宏一样,可以被其他模版导入。比如:

{% set navigation = [('index.html', 'Index'), ('about.html', 'About')] %}
{% set key, value = call_something() %}

从jinja2.8开始,可以使用块赋值将一个块的内容赋值给变量。比如:

{% set navigation %}
<li><a href="/">Index</a>
<li><a href="/downloads">Downloads</a>
{% endset %}

include语句

include语句用于包含一个模版。它将被包含的模块被渲染后的得到内容输出到当前模版。比如:

{% include 'header.html' %}Body{% include 'footer.html' %}

默认情况下,被包含的模版可以使用当前上下文中的变量。

从jinja2.2起,可以使用ignore missing标记include语句,在这种情况下,如果被包含的模版不存在,则忽略这条include语句。

也可以使用without context标记include语句,在这种情况下,被包含的模版无法使用当前上下文中的变量。

还可以给include语句指定一个模版列表,在这种情况下,第一个存在的模版会被包含进来。比如:

{% include ['page_detailed.html', 'page.html'] ignore missing without context %}

import语句

import语句用来导入模版。只有在导入一个模版后,才能使用该模版中定义的宏和变量。

导入模版有两种方式:

需要注意的是:

导入与包含不同,jinja2把 被导入的模版 当成 一个持有宏(或变量)的模块 对待。因此,导入会被缓存,在默认情况下,被导入的模版中无法使用当前上下文中的变量。如果想要改变这个默认行为,那么需要给import语句增加with context标记。比如:

{% from 'forms.html' import input with context %}

此时,缓存会被自动的关闭。


表达式

jinja2中可以使用python中的表达式