Skip to content

Commit 3fb7590

Browse files
authored
Added macro support (#123)
1 parent 1f4c508 commit 3fb7590

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+1613
-160
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ All notable changes to MiniJinja are documented here.
1010
- Improve error reporting for failures in blocks and trying to
1111
`super()` when there is no parent block.
1212
- Performance improvements.
13+
- Added support for `{% import %}` / `{% from .. import .. %}`
14+
and `{% macro %}`. (#123)
15+
- Added `Value::is_kwargs` which disambiugates if an object passed
16+
to a function or filter is a normal object or if it represents
17+
keyword arguments.
18+
- Added the ability to call functions stored on objects.
1319

1420
# 0.22.1
1521

COMPATIBILITY.md

+13-4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ mapping them to filter functions is tricky. This also means that some filters i
3838
MiniJinja do not accept the parameters with keyword arguments whereas in Jinja2
3939
they do.
4040

41+
### Variadic Calls
42+
43+
MiniJinja does not support the `*args` and `**kwargs` syntax for calls.
44+
4145
### Undefined
4246

4347
The Jinja2 undefined type tracks the origin of creation, in MiniJinja the undefined
@@ -87,14 +91,19 @@ MiniJinja is different as mentioned above.
8791

8892
### `{% import %}`
8993

90-
This tag is currently not supported. If support for macros is added
91-
this would make sense to add.
94+
This tag is supported but the returned item is a map of the exported local
95+
variables. This means that the rendered content of the template is lost.
9296

9397
### `{% macro %}`
9498

95-
This tag is currently not supported.
99+
The macro tag works very similar to Jinja2 but with some differences. Most
100+
importantly the special `caller`, `varargs` and `kwargs` arguments are not
101+
supported. The external introspectable attributes `catch_kwargs`, `catch_varargs`
102+
and `caller` are not supported.
103+
104+
### `{% call %}`
96105

97-
**Tracking issue:** [#44](https://github.com/mitsuhiko/minijinja/issues/44)
106+
This tag is not supported.
98107

99108
### `{% with %}`
100109

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ build:
77
@cargo build --all
88

99
doc:
10-
@cd minijinja; RUSTC_BOOTSTRAP=1 RUSTDOCFLAGS="--cfg=docsrs --html-in-header doc-header.html" cargo doc --all --no-deps --features=$(DOC_FEATURES)
10+
@cd minijinja; RUSTC_BOOTSTRAP=1 RUSTDOCFLAGS="--cfg=docsrs --html-in-header doc-header.html" cargo doc -p minijinja -p minijinja-autoreload --no-deps --features=$(DOC_FEATURES)
1111

1212
test:
1313
@$(MAKE) run-tests FEATURES=$(TEST_FEATURES)

doc-header.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<script>
55
document.addEventListener("DOMContentLoaded", function() {
66
document.querySelectorAll("pre code").forEach(function(node) {
7-
if (node.parentNode.className.match(/jinja/)) {
7+
if (node.parentNode.className.match(/jinja|json/)) {
88
hljs.highlightElement(node.parentNode);
99
}
1010
});

examples/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ the `cargo run` command. Alternatively you can do `cargo run -p example-name`.
1616
* [hello](hello): minimal Hello World example.
1717
* [inheritance](inheritance): demonstrates how to use template inheritance.
1818
* [loader](loader): shows how to load templates dynamically at runtime.
19+
* [macros](macros): Demonstrates how to use macros and imports.
1920
* [minimal](minimal): a Hello World example without default features.
2021
* [recursive-for](recursive-for): demonstrates the recursive for loop.
2122
* [render-macro](render-macro): minimal Hello World example using the `render!` macro.

examples/macros/Cargo.toml

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "macros"
3+
version = "0.1.0"
4+
edition = "2018"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
minijinja = { path = "../../minijinja" }

examples/macros/README.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# macros
2+
3+
This example demonstrates how to use macros and imports.
4+
5+
```console
6+
$ cargo run
7+
```

examples/macros/src/macros.html

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{% macro render_input(name, value, type="text") -%}
2+
<input name="{{ name }}" value="{{ value }}" type="{{ type }}">
3+
{%- endmacro %}
4+
{% set alias = render_input %}

examples/macros/src/main.rs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use minijinja::{context, Environment};
2+
3+
fn main() {
4+
let mut env = Environment::new();
5+
env.add_template("macros.html", include_str!("macros.html"))
6+
.unwrap();
7+
env.add_template("template.html", include_str!("template.html"))
8+
.unwrap();
9+
10+
let template = env.get_template("template.html").unwrap();
11+
let context = context! {
12+
username => "John Doe"
13+
};
14+
15+
println!("{}", template.render(&context).unwrap());
16+
}

examples/macros/src/template.html

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!doctype html>
2+
<title>Demo</title>
3+
{%- import "macros.html" as all_macros %}
4+
{%- from "macros.html" import render_input %}
5+
<form action="" method="post">
6+
<dl>
7+
<dt>Username</dt>
8+
<dd>{{ render_input("username", username) }}</dd>
9+
<dt>Password</dt>
10+
<dd>{{ render_input("password", type="password") }}</dd>
11+
</dl>
12+
</form>
13+
<!--
14+
Declared macros: {{ all_macros|safe }}
15+
-->

minijinja/src/compiler/ast.rs

+57-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ use std::ops::Deref;
44
use std::fmt;
55

66
use crate::compiler::tokens::Span;
7-
use crate::value::{Value, ValueMap, ValueRepr};
7+
use crate::key::Key;
8+
use crate::value::{MapType, Value, ValueMap, ValueRepr};
89

910
/// Container for nodes with location info.
1011
///
@@ -63,6 +64,9 @@ pub enum Stmt<'a> {
6364
Include(Spanned<Include<'a>>),
6465
AutoEscape(Spanned<AutoEscape<'a>>),
6566
FilterBlock(Spanned<FilterBlock<'a>>),
67+
Macro(Spanned<Macro<'a>>),
68+
Import(Spanned<Import<'a>>),
69+
FromImport(Spanned<FromImport<'a>>),
6670
}
6771

6872
#[cfg(feature = "internal_debug")]
@@ -82,6 +86,9 @@ impl<'a> fmt::Debug for Stmt<'a> {
8286
Stmt::Include(s) => fmt::Debug::fmt(s, f),
8387
Stmt::AutoEscape(s) => fmt::Debug::fmt(s, f),
8488
Stmt::FilterBlock(s) => fmt::Debug::fmt(s, f),
89+
Stmt::Macro(s) => fmt::Debug::fmt(s, f),
90+
Stmt::Import(s) => fmt::Debug::fmt(s, f),
91+
Stmt::FromImport(s) => fmt::Debug::fmt(s, f),
8592
}
8693
}
8794
}
@@ -102,6 +109,7 @@ pub enum Expr<'a> {
102109
Call(Spanned<Call<'a>>),
103110
List(Spanned<List<'a>>),
104111
Map(Spanned<Map<'a>>),
112+
Kwargs(Spanned<Kwargs<'a>>),
105113
}
106114

107115
#[cfg(feature = "internal_debug")]
@@ -121,6 +129,7 @@ impl<'a> fmt::Debug for Expr<'a> {
121129
Expr::Call(s) => fmt::Debug::fmt(s, f),
122130
Expr::List(s) => fmt::Debug::fmt(s, f),
123131
Expr::Map(s) => fmt::Debug::fmt(s, f),
132+
Expr::Kwargs(s) => fmt::Debug::fmt(s, f),
124133
}
125134
}
126135
}
@@ -206,6 +215,29 @@ pub struct FilterBlock<'a> {
206215
pub body: Vec<Stmt<'a>>,
207216
}
208217

218+
/// Declares a macro.
219+
#[cfg_attr(feature = "internal_debug", derive(Debug))]
220+
pub struct Macro<'a> {
221+
pub name: &'a str,
222+
pub args: Vec<Expr<'a>>,
223+
pub defaults: Vec<Expr<'a>>,
224+
pub body: Vec<Stmt<'a>>,
225+
}
226+
227+
/// A "from" import
228+
#[cfg_attr(feature = "internal_debug", derive(Debug))]
229+
pub struct FromImport<'a> {
230+
pub expr: Expr<'a>,
231+
pub names: Vec<(Expr<'a>, Option<Expr<'a>>)>,
232+
}
233+
234+
/// A full module import
235+
#[cfg_attr(feature = "internal_debug", derive(Debug))]
236+
pub struct Import<'a> {
237+
pub expr: Expr<'a>,
238+
pub name: Expr<'a>,
239+
}
240+
209241
/// Outputs the expression.
210242
#[cfg_attr(feature = "internal_debug", derive(Debug))]
211243
pub struct EmitExpr<'a> {
@@ -351,6 +383,29 @@ impl<'a> List<'a> {
351383
}
352384
}
353385

386+
/// Creates a map of kwargs
387+
#[cfg_attr(feature = "internal_debug", derive(Debug))]
388+
pub struct Kwargs<'a> {
389+
pub pairs: Vec<(&'a str, Expr<'a>)>,
390+
}
391+
392+
impl<'a> Kwargs<'a> {
393+
pub fn as_const(&self) -> Option<Value> {
394+
if !self.pairs.iter().all(|x| matches!(x.1, Expr::Const(_))) {
395+
return None;
396+
}
397+
398+
let mut rv = ValueMap::new();
399+
for (key, value) in &self.pairs {
400+
if let Expr::Const(value) = value {
401+
rv.insert(Key::make_string_key(key), value.value.clone());
402+
}
403+
}
404+
405+
Some(Value(ValueRepr::Map(rv.into(), MapType::Kwargs)))
406+
}
407+
}
408+
354409
/// Creates a map of values.
355410
#[cfg_attr(feature = "internal_debug", derive(Debug))]
356411
pub struct Map<'a> {
@@ -376,7 +431,7 @@ impl<'a> Map<'a> {
376431
}
377432
}
378433

379-
Some(Value(ValueRepr::Map(rv.into())))
434+
Some(Value(ValueRepr::Map(rv.into(), MapType::Normal)))
380435
}
381436
}
382437

0 commit comments

Comments
 (0)