Repository: djc/askama
Pull request #218 (2019-03-05 22:15:05)

Documenting feature render in template
From my point of view, this hidden functionality of Askama is the most interesting. This allows me to structure a base template with a skeleton and cover the sections in it with isolated templates. This feature is not visible in the documentation or in the tests. I did not find any reference in issues.
This pull request tries to solve the absence of references to this functionality.


Patch:
diff --git a/askama/Cargo.toml b/askama/Cargo.toml
index 172ade4..2b71d19 100644
--- a/askama/Cargo.toml
+++ b/askama/Cargo.toml
@@ -24,7 +24,7 @@ serde-json = ["askama_shared/serde_json"]
 serde-yaml = ["askama_shared/serde_yaml"]
 with-iron = ["iron", "askama_derive/iron"]
 with-rocket = ["rocket", "askama_derive/rocket"]
-with-actix-web = ["actix-web", "askama_derive/actix-web", "mime_guess"]
+with-actix-web = ["actix-web", "askama_derive/actix-web", "mime_guess", "bytes"]
 with-gotham = ["gotham", "askama_derive/gotham", "hyper", "mime_guess"]
 
 [dependencies]
@@ -37,6 +37,7 @@ actix-web = { version = "0.7", optional = true }
 mime_guess = { version = "2.0.0-alpha", optional = true }
 gotham = { version = "0.3", optional = true }
 hyper = { version = "0.12", optional = true }
+bytes = { version = "0.4", optional = true }
 
 [package.metadata.docs.rs]
 features = [ "serde-json" ]
diff --git a/askama/src/lib.rs b/askama/src/lib.rs
index dea5833..d1c18c5 100644
--- a/askama/src/lib.rs
+++ b/askama/src/lib.rs
@@ -371,6 +371,36 @@
 //! recursion. This is because the `Display` implementation for that expression
 //! will in turn evaluate the expression and yield `self` again.
 //!
+//! ## Template in template
+//!
+//! Using expressions, it is possible to render a template inside another one. This functionality
+//! allows modularize template sections and inject them in another template. This
+//! facilitates testing, and reusing part of templates.
+//!
+//! ```rust
+//! use askama::Template;
+//! #[derive(Template)]
+//! #[template(source = "Section 1: {{ s1.render().unwrap() }}", ext = "txt")]
+//! struct RenderInPlace<'a> {
+//!    s1: SectionOne<'a>
+//! }
+//! #[derive(Template)]
+//! #[template(source = "A={{ a }}\nB={{ b }}", ext = "txt")]
+//! struct SectionOne<'a> {
+//!    a: &'a str,
+//!    b: &'a str,
+//! }
+//! let t = RenderInPlace{s1: SectionOne{a: "a", b: "b"}};
+//! assert_eq!(t.render().unwrap(), "Section 1: A=a\nB=b")
+//! ```
+//!
+//! See the example
+//! [render in place](https://github.com/djc/askama/blob/master/testing/tests/render_in_place.rs)
+//! using a vector of templates in a for block.
+//!
+//! See the example [using enums](https://github.com/djc/askama/blob/master/testing/tests/render_with_enums.rs)
+//! for another use case. You can use different template parts depending on the context.
+//!
 //! ## Comments
 //!
 //! Askama supports block comments delimited by `{#` and `#}`.
@@ -489,8 +519,37 @@ pub mod rocket {
 #[cfg(feature = "with-actix-web")]
 pub mod actix_web {
     extern crate actix_web;
+    extern crate bytes;
     extern crate mime_guess;
 
+    use std::fmt;
+
+    struct BytesWriter {
+        buf: bytes::BytesMut,
+    }
+
+    impl BytesWriter {
+        #[inline]
+        pub fn with_capacity(size: usize) -> Self {
+            Self {
+                buf: bytes::BytesMut::with_capacity(size),
+            }
+        }
+
+        #[inline]
+        pub fn freeze(self) -> bytes::Bytes {
+            self.buf.freeze()
+        }
+    }
+
+    impl fmt::Write for BytesWriter {
+        #[inline]
+        fn write_str(&mut self, buf: &str) -> fmt::Result {
+            self.buf.extend_from_slice(buf.as_bytes());
+            Ok(())
+        }
+    }
+
     // actix_web technically has this as a pub fn in later versions, fs::file_extension_to_mime.
     // Older versions that don't have it exposed are easier this way. If ext is empty or no
     // associated type was found, then this returns `application/octet-stream`, in line with how
@@ -506,11 +565,14 @@ pub mod actix_web {
 
     impl<T: super::Template> TemplateIntoResponse for T {
         fn into_response(&self) -> Result<HttpResponse, Error> {
-            let rsp = self
-                .render()
+            let mut buffer = BytesWriter::with_capacity(T::size_hint());
+            self.render_into(&mut buffer)
                 .map_err(|_| ErrorInternalServerError("Template parsing error"))?;
+
             let ctype = get_mime_type(T::extension().unwrap_or("txt")).to_string();
-            Ok(HttpResponse::Ok().content_type(ctype.as_str()).body(rsp))
+            Ok(HttpResponse::Ok()
+                .content_type(ctype.as_str())
+                .body(buffer.freeze()))
         }
     }
 }
diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs
index cf5ead9..2c02cb5 100644
--- a/askama_derive/src/lib.rs
+++ b/askama_derive/src/lib.rs
@@ -54,7 +54,7 @@ fn build_template(ast: &syn::DeriveInput) -> String {
     }
 
     let ctx = &contexts[&input.path];
-    let heritage = if !ctx.blocks.is_empty() {
+    let heritage = if !ctx.blocks.is_empty() || ctx.extends.is_some() {
         Some(Heritage::new(ctx, &contexts))
     } else {
         None
diff --git a/askama_derive/src/parser.rs b/askama_derive/src/parser.rs
index c4cb52d..2a342fc 100644
--- a/askama_derive/src/parser.rs
+++ b/askama_derive/src/parser.rs
@@ -454,6 +454,7 @@ named!(expr_single<Input, Expr>, alt!(
     expr_num_lit |
     expr_str_lit |
     expr_path |
+    expr_rust_macro |
     expr_array_lit |
     expr_var |
     expr_group
@@ -554,7 +555,7 @@ named!(expr_unary<Input, Expr>, do_parse!(
     })
 ));
 
-named!(rust_macro<Input, Expr>, do_parse!(
+named!(expr_rust_macro<Input, Expr>, do_parse!(
     mname: identifier >>
     tag!("!") >>
     args: macro_arguments >>
@@ -595,7 +596,6 @@ named!(range_right<Input, Expr>, do_parse!(
 
 named!(expr_any<Input, Expr>, alt!(
     range_right |
-    rust_macro |
     do_parse!(
         left: expr_or >>
         rest: range_right >> (match rest {
diff --git a/askama_shared/Cargo.toml b/askama_shared/Cargo.toml
index 5ba1111..79a5a31 100644
--- a/askama_shared/Cargo.toml
+++ b/askama_shared/Cargo.toml
@@ -17,4 +17,4 @@ serde = "1.0"
 serde_derive = "1.0"
 serde_json = { version = "1.0", optional = true }
 serde_yaml = { version = "0.8", optional = true }
-toml = "0.4"
+toml = "0.5"


Test patch:
diff --git a/testing/templates/render_in_place.html b/testing/templates/render_in_place.html
new file mode 100644
index 0000000..3dff36c
--- /dev/null
+++ b/testing/templates/render_in_place.html
@@ -0,0 +1,6 @@
+Section 1: {{ s1.render().unwrap() }}
+Section 2: {{ s2.render().unwrap()|safe }}
+Section 3 for:
+{% for s in s3.as_slice() -%}
+* {{ s.render().unwrap() }}
+{% endfor %}
diff --git a/testing/templates/render_in_place_sec1.html b/testing/templates/render_in_place_sec1.html
new file mode 100644
index 0000000..75f9fd6
--- /dev/null
+++ b/testing/templates/render_in_place_sec1.html
@@ -0,0 +1,2 @@
+A={{ a }}
+B={{ b }}
diff --git a/testing/templates/render_in_place_sec2.html b/testing/templates/render_in_place_sec2.html
new file mode 100644
index 0000000..2a0ae21
--- /dev/null
+++ b/testing/templates/render_in_place_sec2.html
@@ -0,0 +1,2 @@
+C={{ c }}
+D={{ d }}
diff --git a/testing/tests/inheritance.rs b/testing/tests/inheritance.rs
index 35fee80..32028c4 100644
--- a/testing/tests/inheritance.rs
+++ b/testing/tests/inheritance.rs
@@ -29,6 +29,18 @@ fn test_simple_extends() {
     );
 }
 
+#[derive(Template)]
+#[template(source = "{% extends \"base.html\" %}", ext = "html")]
+struct EmptyChild<'a> {
+    title: &'a str,
+}
+
+#[test]
+fn test_empty_child() {
+    let t = EmptyChild { title: "baz" };
+    assert_eq!(t.render().unwrap(), "baz\n\nFoo\nCopyright 2017");
+}
+
 pub mod parent {
     use askama::Template;
     #[derive(Template)]
diff --git a/testing/tests/render_in_place.rs b/testing/tests/render_in_place.rs
new file mode 100644
index 0000000..3d1b91e
--- /dev/null
+++ b/testing/tests/render_in_place.rs
@@ -0,0 +1,39 @@
+use askama::Template;
+#[derive(Template)]
+#[template(path = "render_in_place.html")]
+struct RenderInPlace<'a> {
+    s1: SectionOne<'a>,
+    s2: SectionTwo<'a>,
+    s3: &'a Vec<SectionOne<'a>>,
+}
+
+#[derive(Template)]
+#[template(path = "render_in_place_sec1.html")]
+struct SectionOne<'a> {
+    a: &'a str,
+    b: &'a str,
+}
+
+#[derive(Template)]
+#[template(path = "render_in_place_sec2.html")]
+struct SectionTwo<'a> {
+    c: &'a str,
+    d: &'a str,
+}
+
+#[test]
+fn test_render_in_place() {
+    let t = RenderInPlace {
+        s1: SectionOne { a: "A", b: "B" },
+        s2: SectionTwo { c: "C", d: "D" },
+        s3: &vec![
+            SectionOne { a: "1", b: "2" },
+            SectionOne { a: "A", b: "B" },
+            SectionOne { a: "a", b: "b" },
+        ],
+    };
+    assert_eq!(
+        t.render().unwrap(),
+       "Section 1: A=A\nB=B\nSection 2: C=C\nD=D\nSection 3 for:\n* A=1\nB=2\n* A=A\nB=B\n* A=a\nB=b\n"
+    );
+}
diff --git a/testing/tests/render_with_enums.rs b/testing/tests/render_with_enums.rs
new file mode 100644
index 0000000..cb83c39
--- /dev/null
+++ b/testing/tests/render_with_enums.rs
@@ -0,0 +1,93 @@
+use askama::Template;
+
+enum SectionOneType<'a> {
+    Empty(TEmpty),
+    Simple(TSecOneSimple<'a>),
+    Full(TSecOneFull<'a>),
+}
+impl<'a> SectionOneType<'a> {
+    pub fn render(&self) -> askama::Result<String> {
+        match self {
+            SectionOneType::Empty(v) => v.render(),
+            SectionOneType::Simple(v) => v.render(),
+            SectionOneType::Full(v) => v.render(),
+        }
+    }
+}
+enum SectionTwoFormat<'a> {
+    Html(TSecTwoHtml<'a>),
+    Text(TSecTwoText<'a>),
+}
+impl<'a> SectionTwoFormat<'a> {
+    pub fn render(&self) -> askama::Result<String> {
+        match self {
+            SectionTwoFormat::Html(v) => v.render(),
+            SectionTwoFormat::Text(v) => v.render(),
+        }
+    }
+}
+#[derive(Template)]
+#[template(path = "render_in_place.html")]
+struct RenderInPlace<'a> {
+    s1: SectionOneType<'a>,
+    s2: SectionTwoFormat<'a>,
+    s3: &'a Vec<SectionOneType<'a>>,
+}
+
+#[derive(Template)]
+#[template(source = "", ext = "txt")]
+struct TEmpty {}
+#[derive(Template)]
+#[template(source = "{{ a }}", ext = "txt")]
+struct TSecOneSimple<'a> {
+    a: &'a str,
+}
+#[derive(Template)]
+#[template(source = "{{ a }}, {{ b }}", ext = "txt")]
+struct TSecOneFull<'a> {
+    a: &'a str,
+    b: &'a str,
+}
+
+#[derive(Template)]
+#[template(source = "<span>{{ c }}</span><p>{{ d }}</p>", ext = "html")]
+struct TSecTwoHtml<'a> {
+    c: &'a str,
+    d: &'a str,
+}
+#[derive(Template)]
+#[template(source = "{{ c }}, {{ d }}", ext = "txt")]
+struct TSecTwoText<'a> {
+    c: &'a str,
+    d: &'a str,
+}
+
+#[test]
+fn test_render_with_enums() {
+    let t = RenderInPlace {
+        s1: SectionOneType::Empty(TEmpty {}),
+        s2: SectionTwoFormat::Html(TSecTwoHtml { c: "C", d: "D" }),
+        s3: &vec![
+            SectionOneType::Empty(TEmpty {}),
+            SectionOneType::Simple(TSecOneSimple { a: "A" }),
+            SectionOneType::Full(TSecOneFull { a: "A", b: "B" }),
+        ],
+    };
+    assert_eq!(
+        t.render().unwrap(),
+        "Section 1: \nSection 2: <span>C</span><p>D</p>\nSection 3 for:\n* \n* A\n* A, B\n"
+    );
+    let t = RenderInPlace {
+        s1: SectionOneType::Empty(TEmpty {}),
+        s2: SectionTwoFormat::Text(TSecTwoText { c: "C", d: "D" }),
+        s3: &vec![
+            SectionOneType::Empty(TEmpty {}),
+            SectionOneType::Simple(TSecOneSimple { a: "A" }),
+            SectionOneType::Full(TSecOneFull { a: "A", b: "B" }),
+        ],
+    };
+    assert_eq!(
+        t.render().unwrap(),
+        "Section 1: \nSection 2: C, D\nSection 3 for:\n* \n* A\n* A, B\n"
+    );
+}
diff --git a/testing/tests/vars.rs b/testing/tests/vars.rs
index da8aef6..0e1e367 100644
--- a/testing/tests/vars.rs
+++ b/testing/tests/vars.rs
@@ -45,11 +45,7 @@ fn test_let_decl() {
 }
 
 #[derive(Template)]
-#[template(
-    source = "{% for v in self.0 %}{{ v }}{% endfor %}",
-    ext = "txt",
-    print = "code"
-)]
+#[template(source = "{% for v in self.0 %}{{ v }}{% endfor %}", ext = "txt")]
 struct SelfIterTemplate(Vec<usize>);
 
 #[test]