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
注意:不能在标签和减号之间添加空格。
在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 '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 'forms.html' as forms %}
把一个模版中特定的名字导入到当前命名空间中(名称以下划线开头的宏和变量是私有的,不能被导入),比如:
{% from 'forms.html' import input as input_field, textarea %}
需要注意的是:
导入与包含不同,jinja2把 被导入的模版 当成 一个持有宏(或变量)的模块 对待。因此,导入会被缓存,在默认情况下,被导入的模版中无法使用当前上下文中的变量。如果想要改变这个默认行为,那么需要给import语句增加with context标记。比如:
{% from 'forms.html' import input with context %}
此时,缓存会被自动的关闭。
jinja2中可以使用python中的表达式