最近在为 mkdocs-blogging-plugin 添加自定义的功能,因为本来就是用 Jinja 的 HTML 模版来生成 HTML,所以也想用 Jinja 给用户提供自定义的接口。

大概的要求有:

  • 提供自定义单个 post 而保留大体框架的方法
  • 提供自定义 style sheet 的方法
  • 提供整体重写的方法

这篇文章只会覆盖到部分。

继承:extends

Jinja 给我们提供了一种类似于继承的方法,可以在保留原来模版所有内容的情况下增加、重写部分内容的方法。

在“子模版”中,通过 extends 引用“父模版”:

{% extends "blog.html" %}

如果只有这一句,相当于将原来 blog.html 中所有内容原封不动地搬到新的模版中。至于增加和重写内容,稍后再谈。

函数:macro

在用户提供的覆写模版中,我们需要用户向父模版提供一个“函数”,用来生成单个博客的内容。

Jinja 中的“函数”叫做 macro,通过这样的方法定义:

{% macro render_blog(title, description, time, url) -%}
    {# content #}
{%- endmacro %}

以及,这样调用:

{% call render_blog(title, description, time, url) %}
    {# caller_content #}
{% endcall %}

call 调用 macro 后,将整个 call 块 替换为 macro 块里面的所有内容 {# content #},与 C 中的宏定义一致(这应该也是为什么叫做 macro 的原因)。

另外,macro 有一个隐含参数 caller,其作用是返回 call 块中间的内容 {# caller_content #},如:

macro

{% macro foo(name) -%}
    <div>{{ name + " says: " + caller() }}</div>
{%- endmacro %}

call

{% call foo("liang2kl") %}
    Hello, World!
{% endcall %}

call 的结果为:

<div>liang2kl says: Hello, World!</div>

值得注意的是,如果不想传递 caller 中的内容,也需要在 macro 中调用 caller(),否则会报错:

TypeError: macro ‘foo’ was invoked with two values for the special caller argument. This is most likely a bug.

现在,我们只需要用户提供一个产生博客内容的 macro 给我们的父模版就可以了。

重写:block

Jinja 的继承机制允许我们在“子模版”中选择性地重写部分内容,而保持其他部分不变。实现的机制是 block

在父模版中,我们用 block 块划出允许重写的部分。如:

{% block style %}
<style>
    ...
</style>
{% endblock %}

在子模版中,重写:

{% block style %}
    ...
{% endblock %}

即可将父模版中的 style 块替换为新的块。

另外,可以用 super() 保留原来内容:

{% block style %}
    {{ super() }}
    ...
{% endblock %}

放在一起

将上面几个功能放在一起,我们给用户提供这样的自定义方式:

{% macro render_blog(title, description, time, url) -%}
    <a href="{{ url }}"><h3>{{ title }}</h3></a>
    <div>{{ description }}</div>
    <div>{{ time }}</div>
    <hr/>
    {{ caller() }}
{%- endmacro %}

{% extends "blog.html" %}

{% block style %}
    .md-typeset .blog-post-title {
        color: blue;
    }
{% endblock %}

其作用:

  • 在引入 blog.html 之前,定义 render_blog 的 macro,以便在 blog.html 中调用
  • 引入 blog.html
  • 通过 block 修改 blog.htmlstyle