Skip to content

Commit

Permalink
Added macro support (#123)
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed Sep 23, 2022
1 parent 1f4c508 commit 3fb7590
Show file tree
Hide file tree
Showing 65 changed files with 1,613 additions and 160 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Expand Up @@ -10,6 +10,12 @@ All notable changes to MiniJinja are documented here.
- Improve error reporting for failures in blocks and trying to
`super()` when there is no parent block.
- Performance improvements.
- Added support for `{% import %}` / `{% from .. import .. %}`
and `{% macro %}`. (#123)
- Added `Value::is_kwargs` which disambiugates if an object passed
to a function or filter is a normal object or if it represents
keyword arguments.
- Added the ability to call functions stored on objects.

# 0.22.1

Expand Down
17 changes: 13 additions & 4 deletions COMPATIBILITY.md
Expand Up @@ -38,6 +38,10 @@ mapping them to filter functions is tricky. This also means that some filters i
MiniJinja do not accept the parameters with keyword arguments whereas in Jinja2
they do.

### Variadic Calls

MiniJinja does not support the `*args` and `**kwargs` syntax for calls.

### Undefined

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

### `{% import %}`

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

### `{% macro %}`

This tag is currently not supported.
The macro tag works very similar to Jinja2 but with some differences. Most
importantly the special `caller`, `varargs` and `kwargs` arguments are not
supported. The external introspectable attributes `catch_kwargs`, `catch_varargs`
and `caller` are not supported.

### `{% call %}`

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

### `{% with %}`

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Expand Up @@ -7,7 +7,7 @@ build:
@cargo build --all

doc:
@cd minijinja; RUSTC_BOOTSTRAP=1 RUSTDOCFLAGS="--cfg=docsrs --html-in-header doc-header.html" cargo doc --all --no-deps --features=$(DOC_FEATURES)
@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)

test:
@$(MAKE) run-tests FEATURES=$(TEST_FEATURES)
Expand Down
2 changes: 1 addition & 1 deletion doc-header.html
Expand Up @@ -4,7 +4,7 @@
<script>
document.addEventListener("DOMContentLoaded", function() {
document.querySelectorAll("pre code").forEach(function(node) {
if (node.parentNode.className.match(/jinja/)) {
if (node.parentNode.className.match(/jinja|json/)) {
hljs.highlightElement(node.parentNode);
}
});
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Expand Up @@ -16,6 +16,7 @@ the `cargo run` command. Alternatively you can do `cargo run -p example-name`.
* [hello](hello): minimal Hello World example.
* [inheritance](inheritance): demonstrates how to use template inheritance.
* [loader](loader): shows how to load templates dynamically at runtime.
* [macros](macros): Demonstrates how to use macros and imports.
* [minimal](minimal): a Hello World example without default features.
* [recursive-for](recursive-for): demonstrates the recursive for loop.
* [render-macro](render-macro): minimal Hello World example using the `render!` macro.
Expand Down
9 changes: 9 additions & 0 deletions examples/macros/Cargo.toml
@@ -0,0 +1,9 @@
[package]
name = "macros"
version = "0.1.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
minijinja = { path = "../../minijinja" }
7 changes: 7 additions & 0 deletions examples/macros/README.md
@@ -0,0 +1,7 @@
# macros

This example demonstrates how to use macros and imports.

```console
$ cargo run
```
4 changes: 4 additions & 0 deletions examples/macros/src/macros.html
@@ -0,0 +1,4 @@
{% macro render_input(name, value, type="text") -%}
<input name="{{ name }}" value="{{ value }}" type="{{ type }}">
{%- endmacro %}
{% set alias = render_input %}
16 changes: 16 additions & 0 deletions examples/macros/src/main.rs
@@ -0,0 +1,16 @@
use minijinja::{context, Environment};

fn main() {
let mut env = Environment::new();
env.add_template("macros.html", include_str!("macros.html"))
.unwrap();
env.add_template("template.html", include_str!("template.html"))
.unwrap();

let template = env.get_template("template.html").unwrap();
let context = context! {
username => "John Doe"
};

println!("{}", template.render(&context).unwrap());
}
15 changes: 15 additions & 0 deletions examples/macros/src/template.html
@@ -0,0 +1,15 @@
<!doctype html>
<title>Demo</title>
{%- import "macros.html" as all_macros %}
{%- from "macros.html" import render_input %}
<form action="" method="post">
<dl>
<dt>Username</dt>
<dd>{{ render_input("username", username) }}</dd>
<dt>Password</dt>
<dd>{{ render_input("password", type="password") }}</dd>
</dl>
</form>
<!--
Declared macros: {{ all_macros|safe }}
-->
59 changes: 57 additions & 2 deletions minijinja/src/compiler/ast.rs
Expand Up @@ -4,7 +4,8 @@ use std::ops::Deref;
use std::fmt;

use crate::compiler::tokens::Span;
use crate::value::{Value, ValueMap, ValueRepr};
use crate::key::Key;
use crate::value::{MapType, Value, ValueMap, ValueRepr};

/// Container for nodes with location info.
///
Expand Down Expand Up @@ -63,6 +64,9 @@ pub enum Stmt<'a> {
Include(Spanned<Include<'a>>),
AutoEscape(Spanned<AutoEscape<'a>>),
FilterBlock(Spanned<FilterBlock<'a>>),
Macro(Spanned<Macro<'a>>),
Import(Spanned<Import<'a>>),
FromImport(Spanned<FromImport<'a>>),
}

#[cfg(feature = "internal_debug")]
Expand All @@ -82,6 +86,9 @@ impl<'a> fmt::Debug for Stmt<'a> {
Stmt::Include(s) => fmt::Debug::fmt(s, f),
Stmt::AutoEscape(s) => fmt::Debug::fmt(s, f),
Stmt::FilterBlock(s) => fmt::Debug::fmt(s, f),
Stmt::Macro(s) => fmt::Debug::fmt(s, f),
Stmt::Import(s) => fmt::Debug::fmt(s, f),
Stmt::FromImport(s) => fmt::Debug::fmt(s, f),
}
}
}
Expand All @@ -102,6 +109,7 @@ pub enum Expr<'a> {
Call(Spanned<Call<'a>>),
List(Spanned<List<'a>>),
Map(Spanned<Map<'a>>),
Kwargs(Spanned<Kwargs<'a>>),
}

#[cfg(feature = "internal_debug")]
Expand All @@ -121,6 +129,7 @@ impl<'a> fmt::Debug for Expr<'a> {
Expr::Call(s) => fmt::Debug::fmt(s, f),
Expr::List(s) => fmt::Debug::fmt(s, f),
Expr::Map(s) => fmt::Debug::fmt(s, f),
Expr::Kwargs(s) => fmt::Debug::fmt(s, f),
}
}
}
Expand Down Expand Up @@ -206,6 +215,29 @@ pub struct FilterBlock<'a> {
pub body: Vec<Stmt<'a>>,
}

/// Declares a macro.
#[cfg_attr(feature = "internal_debug", derive(Debug))]
pub struct Macro<'a> {
pub name: &'a str,
pub args: Vec<Expr<'a>>,
pub defaults: Vec<Expr<'a>>,
pub body: Vec<Stmt<'a>>,
}

/// A "from" import
#[cfg_attr(feature = "internal_debug", derive(Debug))]
pub struct FromImport<'a> {
pub expr: Expr<'a>,
pub names: Vec<(Expr<'a>, Option<Expr<'a>>)>,
}

/// A full module import
#[cfg_attr(feature = "internal_debug", derive(Debug))]
pub struct Import<'a> {
pub expr: Expr<'a>,
pub name: Expr<'a>,
}

/// Outputs the expression.
#[cfg_attr(feature = "internal_debug", derive(Debug))]
pub struct EmitExpr<'a> {
Expand Down Expand Up @@ -351,6 +383,29 @@ impl<'a> List<'a> {
}
}

/// Creates a map of kwargs
#[cfg_attr(feature = "internal_debug", derive(Debug))]
pub struct Kwargs<'a> {
pub pairs: Vec<(&'a str, Expr<'a>)>,
}

impl<'a> Kwargs<'a> {
pub fn as_const(&self) -> Option<Value> {
if !self.pairs.iter().all(|x| matches!(x.1, Expr::Const(_))) {
return None;
}

let mut rv = ValueMap::new();
for (key, value) in &self.pairs {
if let Expr::Const(value) = value {
rv.insert(Key::make_string_key(key), value.value.clone());
}
}

Some(Value(ValueRepr::Map(rv.into(), MapType::Kwargs)))
}
}

/// Creates a map of values.
#[cfg_attr(feature = "internal_debug", derive(Debug))]
pub struct Map<'a> {
Expand All @@ -376,7 +431,7 @@ impl<'a> Map<'a> {
}
}

Some(Value(ValueRepr::Map(rv.into())))
Some(Value(ValueRepr::Map(rv.into(), MapType::Normal)))
}
}

Expand Down

0 comments on commit 3fb7590

Please sign in to comment.