diff options
Diffstat (limited to 'askama_derive/src')
-rw-r--r-- | askama_derive/src/generator.rs | 236 | ||||
-rw-r--r-- | askama_derive/src/lib.rs | 31 |
2 files changed, 127 insertions, 140 deletions
diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 6c6e1e7..86fd9f5 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -4,10 +4,9 @@ use parser::{self, Cond, Expr, MatchParameter, MatchVariant, Node, Target, When, use shared::{filters, path}; use proc_macro2::Span; -use quote::ToTokens; use std::collections::{HashMap, HashSet}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::{cmp, hash, str}; use syn; @@ -26,7 +25,7 @@ struct Generator<'a> { next_ws: Option<&'a str>, skip_ws: bool, vars: usize, - impl_blocks: bool, + inherited: usize, } impl<'a> Generator<'a> { @@ -46,7 +45,7 @@ impl<'a> Generator<'a> { next_ws: None, skip_ws: false, vars: 0, - impl_blocks: false, + inherited: 0, } } @@ -57,26 +56,18 @@ impl<'a> Generator<'a> { // Takes a Context and generates the relevant implementations. fn build(mut self, ctx: &'a Context) -> String { - if !ctx.blocks.is_empty() { - if ctx.extends.is_none() { - self.define_trait(ctx); - } else { - match self.input.parent { - Some(ty) => self.deref_to_parent(ty), - None => panic!("expected field '_parent' in extending template struct"), - } + let heritage = if !ctx.blocks.is_empty() { + if ctx.extends.is_some() && self.input.parent.is_none() { + panic!("expected field '_parent' in extending template struct"); } - - let trait_nodes = if ctx.extends.is_none() { - Some(&ctx.nodes[..]) - } else { - None - }; - self.impl_trait(ctx, trait_nodes); - self.impl_template_for_trait(ctx); + let heritage = Heritage::new(ctx, self.contexts); + self.trait_blocks(&heritage); + Some(heritage) } else { - self.impl_template(ctx); - } + None + }; + + self.impl_template(ctx, &heritage); self.impl_display(); if cfg!(feature = "iron") { self.impl_modifier_response(); @@ -87,14 +78,62 @@ impl<'a> Generator<'a> { self.buf } + fn trait_blocks(&mut self, heritage: &Heritage<'a>) { + let trait_name = trait_name_for_path(&self.input.path); + self.writeln(&format!("pub trait {} {{", trait_name)); + for name in heritage.blocks.keys() { + self.writeln(&format!( + "fn render_block_{}_into(&self, writer: &mut ::std::fmt::Write) \ + -> ::askama::Result<()>;", + name + )); + } + self.writeln("}"); + + self.write_header(&trait_name, None); + for (level, ctx, def) in heritage.blocks.values() { + if let Node::BlockDef(ws1, name, nodes, ws2) = def { + self.writeln("#[allow(unused_variables)]"); + self.writeln(&format!( + "fn render_block_{}_into(&self, writer: &mut ::std::fmt::Write) \ + -> ::askama::Result<()> {{", + name + )); + self.prepare_ws(*ws1); + + self.inherited = heritage.levels - *level; + self.locals.push(); + self.handle(ctx, nodes, AstLevel::Block); + self.locals.pop(); + + self.flush_ws(*ws2); + self.writeln("Ok(())"); + self.writeln("}"); + } else { + panic!("only block definitions allowed here"); + } + } + self.writeln("}"); + + self.inherited = 0; + } + // Implement `Template` for the given context struct. - fn impl_template(&mut self, ctx: &'a Context) { + fn impl_template(&mut self, ctx: &'a Context, heritage: &Option<Heritage<'a>>) { self.write_header("::askama::Template", None); self.writeln( "fn render_into(&self, writer: &mut ::std::fmt::Write) -> \ ::askama::Result<()> {", ); - self.handle(ctx, &ctx.nodes, AstLevel::Top); + + if let Some(heritage) = heritage { + self.inherited = heritage.levels; + self.handle(heritage.root, heritage.root.nodes, AstLevel::Top); + self.inherited = 0; + } else { + self.handle(ctx, &ctx.nodes, AstLevel::Top); + } + self.flush_ws(WS(false, false)); self.writeln("Ok(())"); self.writeln("}"); @@ -110,74 +149,6 @@ impl<'a> Generator<'a> { self.writeln("}"); } - // Implement `Deref<Parent>` for an inheriting context struct. - fn deref_to_parent(&mut self, parent_type: &syn::Type) { - self.write_header("::std::ops::Deref", None); - self.writeln(&format!( - "type Target = {};", - parent_type.into_token_stream() - )); - self.writeln("fn deref(&self) -> &Self::Target {"); - self.writeln("&self._parent"); - self.writeln("}"); - self.writeln("}"); - } - - // Implement `TraitFromPathName` for the given context struct. - fn impl_trait(&mut self, ctx: &'a Context, nodes: Option<&'a [Node]>) { - self.write_header(&ctx.trait_name, None); - self.write_block_defs(ctx); - - self.writeln("#[allow(unused_variables)]"); - self.writeln(&format!( - "fn render_trait_into(&self, timpl: &{}, writer: &mut ::std::fmt::Write) \ - -> ::askama::Result<()> {{", - ctx.trait_name - )); - - if let Some(nodes) = nodes { - self.impl_blocks = true; - self.handle(ctx, nodes, AstLevel::Top); - self.flush_ws(WS(false, false)); - self.impl_blocks = false; - self.writeln("Ok(())"); - } else { - self.writeln("self._parent.render_trait_into(self, writer)"); - } - - self.writeln("}"); - self.flush_ws(WS(false, false)); - self.writeln("}"); - } - - // Implement `Template` for templates that implement a template trait. - fn impl_template_for_trait(&mut self, ctx: &'a Context) { - self.write_header("::askama::Template", None); - self.writeln( - "fn render_into(&self, writer: &mut ::std::fmt::Write) \ - -> ::askama::Result<()> {", - ); - if ctx.extends.is_some() { - self.writeln("self._parent.render_trait_into(self, writer)"); - } else { - self.writeln("self.render_trait_into(self, writer)"); - } - self.writeln("}"); - self.writeln("}"); - } - - // Defines the `TraitFromPathName` trait. - fn define_trait(&mut self, ctx: &'a Context) { - self.writeln(&format!("pub trait {} {{", ctx.trait_name)); - self.write_block_defs(ctx); - self.writeln(&format!( - "fn render_trait_into(&self, timpl: &{}, writer: &mut ::std::fmt::Write) \ - -> ::askama::Result<()>;", - ctx.trait_name - )); - self.writeln("}"); - } - // Implement iron's Modifier<Response> if enabled fn impl_modifier_response(&mut self) { self.write_header("::askama::iron::Modifier<::askama::iron::Response>", None); @@ -310,30 +281,6 @@ impl<'a> Generator<'a> { } } - fn write_block_defs(&mut self, ctx: &'a Context) { - for b in &ctx.blocks { - if let Node::BlockDef(ws1, name, nodes, ws2) = b { - self.writeln("#[allow(unused_variables)]"); - self.writeln(&format!( - "fn render_block_{}_into(&self, writer: &mut ::std::fmt::Write) \ - -> ::askama::Result<()> {{", - name - )); - self.prepare_ws(*ws1); - - self.locals.push(); - self.handle(ctx, nodes, AstLevel::Block); - self.locals.pop(); - - self.flush_ws(*ws2); - self.writeln("Ok(())"); - self.writeln("}"); - } else { - panic!("only block definitions allowed here"); - } - } - } - fn write_cond(&mut self, ctx: &'a Context, conds: &'a [Cond], ws: WS) { for (i, &(cws, ref cond, ref nodes)) in conds.iter().enumerate() { self.handle_ws(cws); @@ -530,8 +477,7 @@ impl<'a> Generator<'a> { fn write_block(&mut self, ws1: WS, name: &str, ws2: WS) { self.flush_ws(ws1); - let ctx = if self.impl_blocks { "timpl" } else { "self" }; - self.writeln(&format!("{}.render_block_{}_into(writer)?;", ctx, name)); + self.writeln(&format!("self.render_block_{}_into(writer)?;", name)); self.prepare_ws(ws2); } @@ -796,7 +742,11 @@ impl<'a> Generator<'a> { if self.locals.contains(s) { code.push_str(s); } else { - code.push_str(&format!("self.{}", s)); + code.push_str("self."); + for _ in 0..self.inherited { + code.push_str("_parent."); + } + code.push_str(s); } DisplayWrap::Unwrapped } @@ -931,6 +881,54 @@ where } } +struct Heritage<'a> { + root: &'a Context<'a>, + levels: usize, + blocks: HashMap<&'a str, (usize, &'a Context<'a>, &'a Node<'a>)>, +} + +impl<'a> Heritage<'a> { + fn new<'n>( + mut ctx: &'n Context<'n>, + contexts: &'n HashMap<&'n PathBuf, Context<'n>>, + ) -> Heritage<'n> { + let mut levels = 0; + let mut blocks: HashMap<_, (usize, _, _)> = ctx.blocks + .iter() + .map(|(name, def)| (*name, (levels, ctx, *def))) + .collect(); + + while let Some(ref path) = ctx.extends { + ctx = &contexts[&path]; + levels += 1; + for (name, def) in &ctx.blocks { + if !blocks.contains_key(name) { + blocks.insert(name, (levels, ctx, def)); + } + } + } + + Heritage { + root: ctx, + levels, + blocks + } + } +} + +fn trait_name_for_path(path: &Path) -> String { + let mut res = String::new(); + res.push_str("TraitFrom"); + for c in path.to_string_lossy().chars() { + if c.is_alphanumeric() { + res.push(c); + } else { + res.push_str(&format!("{:x}", c as u32)); + } + } + res +} + #[derive(Clone, PartialEq)] enum AstLevel { Top, diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index 07ba0a6..ad1c123 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -17,7 +17,7 @@ use proc_macro::TokenStream; use shared::path; use std::collections::HashMap; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; #[proc_macro_derive(Template, attributes(template))] pub fn derive_template(input: TokenStream) -> TokenStream { @@ -92,10 +92,9 @@ fn find_used_templates(map: &mut HashMap<PathBuf, String>, path: PathBuf, source pub(crate) struct Context<'a> { nodes: &'a [Node<'a>], extends: Option<PathBuf>, - blocks: Vec<&'a Node<'a>>, + blocks: HashMap<&'a str, &'a Node<'a>>, macros: HashMap<&'a str, &'a Macro<'a>>, imports: HashMap<&'a str, PathBuf>, - trait_name: String, } impl<'a> Context<'a> { @@ -143,10 +142,14 @@ impl<'a> Context<'a> { check_nested += 1; } - let trait_name = match extends { - Some(ref path) => trait_name_for_path(path), - None => trait_name_for_path(&input.path), - }; + let blocks: HashMap<_, _> = blocks + .iter() + .map(|def| if let Node::BlockDef(_, name, _, _) = def { + (*name, *def) + } else { + unreachable!() + }) + .collect(); Context { nodes, @@ -154,20 +157,6 @@ impl<'a> Context<'a> { blocks, macros, imports, - trait_name, - } - } -} - -fn trait_name_for_path(path: &Path) -> String { - let mut res = String::new(); - res.push_str("TraitFrom"); - for c in path.to_string_lossy().chars() { - if c.is_alphanumeric() { - res.push(c); - } else { - res.push_str(&format!("{:x}", c as u32)); } } - res } |