From 8694b0514e46fbb29edcf3353e4e3258ee440bf7 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 6 Oct 2023 11:30:37 +0200 Subject: [PATCH 01/12] Inline nested derive_template() implementation --- askama_derive/src/generator.rs | 12 +----------- askama_derive/src/lib.rs | 6 +++++- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 76b775b8b..290097cd1 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -7,7 +7,6 @@ use parser::node::{ Call, Comment, CondTest, If, Include, Let, Lit, Loop, Match, Target, Whitespace, Ws, }; use parser::{Expr, Node, Parsed}; -use proc_macro::TokenStream; use quote::{quote, ToTokens}; use syn::punctuated::Punctuated; @@ -15,15 +14,6 @@ use std::collections::hash_map::{Entry, HashMap}; use std::path::{Path, PathBuf}; use std::{cmp, hash, mem, str}; -/// The actual implementation for askama_derive::Template -pub(crate) fn derive_template(input: TokenStream) -> TokenStream { - let ast: syn::DeriveInput = syn::parse(input).unwrap(); - match build_template(&ast) { - Ok(source) => source.parse().unwrap(), - Err(e) => e.into_compile_error(), - } -} - /// Takes a `syn::DeriveInput` and generates source code for it /// /// Reads the metadata from the `template()` attribute to get the template @@ -31,7 +21,7 @@ pub(crate) fn derive_template(input: TokenStream) -> TokenStream { /// parsed, and the parse tree is fed to the code generator. Will print /// the parse tree and/or generated source according to the `print` key's /// value as passed to the `template()` attribute. -fn build_template(ast: &syn::DeriveInput) -> Result { +pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result { let template_args = TemplateArgs::new(ast)?; let config_toml = read_config_file(template_args.config_path.as_deref())?; let config = Config::new(&config_toml, template_args.whitespace.as_ref())?; diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index 8a737aa9a..3b8063559 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -16,7 +16,11 @@ mod input; #[proc_macro_derive(Template, attributes(template))] pub fn derive_template(input: TokenStream) -> TokenStream { - generator::derive_template(input) + let ast = syn::parse::(input).unwrap(); + match generator::build_template(&ast) { + Ok(source) => source.parse().unwrap(), + Err(e) => e.into_compile_error(), + } } #[derive(Debug, Clone)] From 35ca1f8c91370a95c914f04e63c2db69073b572a Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 6 Oct 2023 11:33:06 +0200 Subject: [PATCH 02/12] Move TemplateArgs into input module --- askama_derive/src/generator.rs | 131 +------------------------------ askama_derive/src/input.rs | 137 +++++++++++++++++++++++++++++++-- 2 files changed, 134 insertions(+), 134 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 290097cd1..b8760b01e 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -1,14 +1,13 @@ use crate::config::{get_template_source, read_config_file, Config, WhitespaceHandling}; use crate::heritage::{Context, Heritage}; -use crate::input::{Print, Source, TemplateInput}; +use crate::input::{Print, Source, TemplateArgs, TemplateInput}; use crate::CompileError; use parser::node::{ Call, Comment, CondTest, If, Include, Let, Lit, Loop, Match, Target, Whitespace, Ws, }; use parser::{Expr, Node, Parsed}; -use quote::{quote, ToTokens}; -use syn::punctuated::Punctuated; +use quote::quote; use std::collections::hash_map::{Entry, HashMap}; use std::path::{Path, PathBuf}; @@ -67,132 +66,6 @@ pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result, - pub(crate) print: Print, - pub(crate) escaping: Option, - pub(crate) ext: Option, - pub(crate) syntax: Option, - pub(crate) config_path: Option, - pub(crate) whitespace: Option, -} - -impl TemplateArgs { - fn new(ast: &'_ syn::DeriveInput) -> Result { - // Check that an attribute called `template()` exists once and that it is - // the proper type (list). - let mut template_args = None; - for attr in &ast.attrs { - if !attr.path().is_ident("template") { - continue; - } - - match attr.parse_args_with(Punctuated::::parse_terminated) { - Ok(args) if template_args.is_none() => template_args = Some(args), - Ok(_) => return Err("duplicated 'template' attribute".into()), - Err(e) => return Err(format!("unable to parse template arguments: {e}").into()), - }; - } - - let template_args = - template_args.ok_or_else(|| CompileError::from("no attribute 'template' found"))?; - - let mut args = Self::default(); - // Loop over the meta attributes and find everything that we - // understand. Return a CompileError if something is not right. - // `source` contains an enum that can represent `path` or `source`. - for item in template_args { - let pair = match item { - syn::Meta::NameValue(pair) => pair, - _ => { - return Err(format!( - "unsupported attribute argument {:?}", - item.to_token_stream() - ) - .into()) - } - }; - - let ident = match pair.path.get_ident() { - Some(ident) => ident, - None => unreachable!("not possible in syn::Meta::NameValue(…)"), - }; - - let value = match pair.value { - syn::Expr::Lit(lit) => lit, - syn::Expr::Group(group) => match *group.expr { - syn::Expr::Lit(lit) => lit, - _ => { - return Err(format!("unsupported argument value type for {ident:?}").into()) - } - }, - _ => return Err(format!("unsupported argument value type for {ident:?}").into()), - }; - - if ident == "path" { - if let syn::Lit::Str(s) = value.lit { - if args.source.is_some() { - return Err("must specify 'source' or 'path', not both".into()); - } - args.source = Some(Source::Path(s.value())); - } else { - return Err("template path must be string literal".into()); - } - } else if ident == "source" { - if let syn::Lit::Str(s) = value.lit { - if args.source.is_some() { - return Err("must specify 'source' or 'path', not both".into()); - } - args.source = Some(Source::Source(s.value())); - } else { - return Err("template source must be string literal".into()); - } - } else if ident == "print" { - if let syn::Lit::Str(s) = value.lit { - args.print = s.value().parse()?; - } else { - return Err("print value must be string literal".into()); - } - } else if ident == "escape" { - if let syn::Lit::Str(s) = value.lit { - args.escaping = Some(s.value()); - } else { - return Err("escape value must be string literal".into()); - } - } else if ident == "ext" { - if let syn::Lit::Str(s) = value.lit { - args.ext = Some(s.value()); - } else { - return Err("ext value must be string literal".into()); - } - } else if ident == "syntax" { - if let syn::Lit::Str(s) = value.lit { - args.syntax = Some(s.value()) - } else { - return Err("syntax value must be string literal".into()); - } - } else if ident == "config" { - if let syn::Lit::Str(s) = value.lit { - args.config_path = Some(s.value()) - } else { - return Err("config value must be string literal".into()); - } - } else if ident == "whitespace" { - if let syn::Lit::Str(s) = value.lit { - args.whitespace = Some(s.value()) - } else { - return Err("whitespace value must be string literal".into()); - } - } else { - return Err(format!("unsupported attribute key {ident:?} found").into()); - } - } - - Ok(args) - } -} - fn find_used_templates( input: &TemplateInput<'_>, map: &mut HashMap, diff --git a/askama_derive/src/input.rs b/askama_derive/src/input.rs index d4e8ad920..59770580e 100644 --- a/askama_derive/src/input.rs +++ b/askama_derive/src/input.rs @@ -1,12 +1,13 @@ -use crate::config::Config; -use crate::generator::TemplateArgs; -use crate::CompileError; -use parser::Syntax; - use std::path::{Path, PathBuf}; use std::str::FromStr; use mime::Mime; +use quote::ToTokens; +use syn::punctuated::Punctuated; + +use crate::config::Config; +use crate::CompileError; +use parser::Syntax; pub(crate) struct TemplateInput<'a> { pub(crate) ast: &'a syn::DeriveInput, @@ -105,6 +106,132 @@ impl TemplateInput<'_> { } } +#[derive(Default)] +pub(crate) struct TemplateArgs { + pub(crate) source: Option, + pub(crate) print: Print, + pub(crate) escaping: Option, + pub(crate) ext: Option, + pub(crate) syntax: Option, + pub(crate) config_path: Option, + pub(crate) whitespace: Option, +} + +impl TemplateArgs { + pub(crate) fn new(ast: &'_ syn::DeriveInput) -> Result { + // Check that an attribute called `template()` exists once and that it is + // the proper type (list). + let mut template_args = None; + for attr in &ast.attrs { + if !attr.path().is_ident("template") { + continue; + } + + match attr.parse_args_with(Punctuated::::parse_terminated) { + Ok(args) if template_args.is_none() => template_args = Some(args), + Ok(_) => return Err("duplicated 'template' attribute".into()), + Err(e) => return Err(format!("unable to parse template arguments: {e}").into()), + }; + } + + let template_args = + template_args.ok_or_else(|| CompileError::from("no attribute 'template' found"))?; + + let mut args = Self::default(); + // Loop over the meta attributes and find everything that we + // understand. Return a CompileError if something is not right. + // `source` contains an enum that can represent `path` or `source`. + for item in template_args { + let pair = match item { + syn::Meta::NameValue(pair) => pair, + _ => { + return Err(format!( + "unsupported attribute argument {:?}", + item.to_token_stream() + ) + .into()) + } + }; + + let ident = match pair.path.get_ident() { + Some(ident) => ident, + None => unreachable!("not possible in syn::Meta::NameValue(…)"), + }; + + let value = match pair.value { + syn::Expr::Lit(lit) => lit, + syn::Expr::Group(group) => match *group.expr { + syn::Expr::Lit(lit) => lit, + _ => { + return Err(format!("unsupported argument value type for {ident:?}").into()) + } + }, + _ => return Err(format!("unsupported argument value type for {ident:?}").into()), + }; + + if ident == "path" { + if let syn::Lit::Str(s) = value.lit { + if args.source.is_some() { + return Err("must specify 'source' or 'path', not both".into()); + } + args.source = Some(Source::Path(s.value())); + } else { + return Err("template path must be string literal".into()); + } + } else if ident == "source" { + if let syn::Lit::Str(s) = value.lit { + if args.source.is_some() { + return Err("must specify 'source' or 'path', not both".into()); + } + args.source = Some(Source::Source(s.value())); + } else { + return Err("template source must be string literal".into()); + } + } else if ident == "print" { + if let syn::Lit::Str(s) = value.lit { + args.print = s.value().parse()?; + } else { + return Err("print value must be string literal".into()); + } + } else if ident == "escape" { + if let syn::Lit::Str(s) = value.lit { + args.escaping = Some(s.value()); + } else { + return Err("escape value must be string literal".into()); + } + } else if ident == "ext" { + if let syn::Lit::Str(s) = value.lit { + args.ext = Some(s.value()); + } else { + return Err("ext value must be string literal".into()); + } + } else if ident == "syntax" { + if let syn::Lit::Str(s) = value.lit { + args.syntax = Some(s.value()) + } else { + return Err("syntax value must be string literal".into()); + } + } else if ident == "config" { + if let syn::Lit::Str(s) = value.lit { + args.config_path = Some(s.value()) + } else { + return Err("config value must be string literal".into()); + } + } else if ident == "whitespace" { + if let syn::Lit::Str(s) = value.lit { + args.whitespace = Some(s.value()) + } else { + return Err("whitespace value must be string literal".into()); + } + } else { + return Err(format!("unsupported attribute key {ident:?} found").into()); + } + } + + Ok(args) + } +} + #[inline] fn ext_default_to_path<'a>(ext: Option<&'a str>, path: &'a Path) -> Option<&'a str> { ext.or_else(|| extension(path)) From a6533e4be3c54c94cd28bf49ac3d9052ccda971b Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 6 Oct 2023 11:36:59 +0200 Subject: [PATCH 03/12] Attach find_used_templates() to TemplateInput --- askama_derive/src/generator.rs | 43 +------------------------------ askama_derive/src/input.rs | 46 ++++++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 44 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index b8760b01e..bf4d6c0f0 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -31,7 +31,7 @@ pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result Result, - map: &mut HashMap, - source: String, -) -> Result<(), CompileError> { - let mut dependency_graph = Vec::new(); - let mut check = vec![(input.path.clone(), source)]; - while let Some((path, source)) = check.pop() { - let parsed = Parsed::new(source, input.syntax)?; - for n in parsed.nodes() { - match n { - Node::Extends(extends) => { - let extends = input.config.find_template(extends.path, Some(&path))?; - let dependency_path = (path.clone(), extends.clone()); - if dependency_graph.contains(&dependency_path) { - return Err(format!( - "cyclic dependency in graph {:#?}", - dependency_graph - .iter() - .map(|e| format!("{:#?} --> {:#?}", e.0, e.1)) - .collect::>() - ) - .into()); - } - dependency_graph.push(dependency_path); - let source = get_template_source(&extends)?; - check.push((extends, source)); - } - Node::Import(import) => { - let import = input.config.find_template(import.path, Some(&path))?; - let source = get_template_source(&import)?; - check.push((import, source)); - } - _ => {} - } - } - map.insert(path, parsed); - } - Ok(()) -} - struct Generator<'a> { // The template input state: original struct AST and attributes input: &'a TemplateInput<'a>, diff --git a/askama_derive/src/input.rs b/askama_derive/src/input.rs index 59770580e..03817f1cf 100644 --- a/askama_derive/src/input.rs +++ b/askama_derive/src/input.rs @@ -1,3 +1,4 @@ +use std::collections::hash_map::HashMap; use std::path::{Path, PathBuf}; use std::str::FromStr; @@ -5,9 +6,9 @@ use mime::Mime; use quote::ToTokens; use syn::punctuated::Punctuated; -use crate::config::Config; +use crate::config::{get_template_source, Config}; use crate::CompileError; -use parser::Syntax; +use parser::{Node, Parsed, Syntax}; pub(crate) struct TemplateInput<'a> { pub(crate) ast: &'a syn::DeriveInput, @@ -100,6 +101,47 @@ impl TemplateInput<'_> { }) } + pub(crate) fn find_used_templates( + &self, + map: &mut HashMap, + source: String, + ) -> Result<(), CompileError> { + let mut dependency_graph = Vec::new(); + let mut check = vec![(self.path.clone(), source)]; + while let Some((path, source)) = check.pop() { + let parsed = Parsed::new(source, self.syntax)?; + for n in parsed.nodes() { + match n { + Node::Extends(extends) => { + let extends = self.config.find_template(extends.path, Some(&path))?; + let dependency_path = (path.clone(), extends.clone()); + if dependency_graph.contains(&dependency_path) { + return Err(format!( + "cyclic dependency in graph {:#?}", + dependency_graph + .iter() + .map(|e| format!("{:#?} --> {:#?}", e.0, e.1)) + .collect::>() + ) + .into()); + } + dependency_graph.push(dependency_path); + let source = get_template_source(&extends)?; + check.push((extends, source)); + } + Node::Import(import) => { + let import = self.config.find_template(import.path, Some(&path))?; + let source = get_template_source(&import)?; + check.push((import, source)); + } + _ => {} + } + } + map.insert(path, parsed); + } + Ok(()) + } + #[inline] pub(crate) fn extension(&self) -> Option<&str> { ext_default_to_path(self.ext.as_deref(), &self.path) From eacc8450a4a5ca0a8e46b1109889066b0ea9d5d5 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 6 Oct 2023 11:39:16 +0200 Subject: [PATCH 04/12] Remove unnecessary type annotation --- askama_derive/src/generator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index bf4d6c0f0..9943ffe69 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -25,7 +25,7 @@ pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result s.clone(), Source::Path(_) => get_template_source(&input.path)?, }; From d16efdb8834f33e67b014c394908e3c1bdf83bd1 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 6 Oct 2023 11:45:22 +0200 Subject: [PATCH 05/12] Move build_template() up to the crate root --- askama_derive/src/generator.rs | 69 ++++------------------------------ askama_derive/src/lib.rs | 61 +++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 63 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 9943ffe69..324fdf428 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -1,6 +1,6 @@ -use crate::config::{get_template_source, read_config_file, Config, WhitespaceHandling}; +use crate::config::{get_template_source, WhitespaceHandling}; use crate::heritage::{Context, Heritage}; -use crate::input::{Print, Source, TemplateArgs, TemplateInput}; +use crate::input::{Source, TemplateInput}; use crate::CompileError; use parser::node::{ @@ -13,60 +13,7 @@ use std::collections::hash_map::{Entry, HashMap}; use std::path::{Path, PathBuf}; use std::{cmp, hash, mem, str}; -/// Takes a `syn::DeriveInput` and generates source code for it -/// -/// Reads the metadata from the `template()` attribute to get the template -/// metadata, then fetches the source from the filesystem. The source is -/// parsed, and the parse tree is fed to the code generator. Will print -/// the parse tree and/or generated source according to the `print` key's -/// value as passed to the `template()` attribute. -pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result { - let template_args = TemplateArgs::new(ast)?; - let config_toml = read_config_file(template_args.config_path.as_deref())?; - let config = Config::new(&config_toml, template_args.whitespace.as_ref())?; - let input = TemplateInput::new(ast, &config, template_args)?; - let source = match input.source { - Source::Source(ref s) => s.clone(), - Source::Path(_) => get_template_source(&input.path)?, - }; - - let mut templates = HashMap::new(); - input.find_used_templates(&mut templates, source)?; - - let mut contexts = HashMap::new(); - for (path, parsed) in &templates { - contexts.insert( - path.as_path(), - Context::new(input.config, path, parsed.nodes())?, - ); - } - - let ctx = &contexts[input.path.as_path()]; - let heritage = if !ctx.blocks.is_empty() || ctx.extends.is_some() { - Some(Heritage::new(ctx, &contexts)) - } else { - None - }; - - if input.print == Print::Ast || input.print == Print::All { - eprintln!("{:?}", templates[input.path.as_path()].nodes()); - } - - let code = Generator::new( - &input, - &contexts, - heritage.as_ref(), - MapChain::new(), - config.whitespace, - ) - .build(&contexts[input.path.as_path()])?; - if input.print == Print::Code || input.print == Print::All { - eprintln!("{code}"); - } - Ok(code) -} - -struct Generator<'a> { +pub(crate) struct Generator<'a> { // The template input state: original struct AST and attributes input: &'a TemplateInput<'a>, // All contexts, keyed by the package-relative template path @@ -96,7 +43,7 @@ struct Generator<'a> { } impl<'a> Generator<'a> { - fn new<'n>( + pub(crate) fn new<'n>( input: &'n TemplateInput<'_>, contexts: &'n HashMap<&'n Path, Context<'n>>, heritage: Option<&'n Heritage<'_>>, @@ -119,7 +66,7 @@ impl<'a> Generator<'a> { } // Takes a Context and generates the relevant implementations. - fn build(mut self, ctx: &'a Context<'_>) -> Result { + pub(crate) fn build(mut self, ctx: &'a Context<'_>) -> Result { let mut buf = Buffer::new(0); self.impl_template(ctx, &mut buf)?; @@ -1792,7 +1739,7 @@ impl Buffer { } #[derive(Clone, Default)] -struct LocalMeta { +pub(crate) struct LocalMeta { refs: Option, initialized: bool, } @@ -1816,7 +1763,7 @@ impl LocalMeta { // type SetChain<'a, T> = MapChain<'a, T, ()>; #[derive(Debug)] -struct MapChain<'a, K, V> +pub(crate) struct MapChain<'a, K, V> where K: cmp::Eq + hash::Hash, { @@ -1828,7 +1775,7 @@ impl<'a, K: 'a, V: 'a> MapChain<'a, K, V> where K: cmp::Eq + hash::Hash, { - fn new() -> MapChain<'a, K, V> { + pub(crate) fn new() -> MapChain<'a, K, V> { MapChain { parent: None, scopes: vec![HashMap::new()], diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index 3b8063559..dc8ca7549 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -1,8 +1,8 @@ #![deny(elided_lifetimes_in_paths)] #![deny(unreachable_pub)] -use std::borrow::Cow; use std::fmt; +use std::{borrow::Cow, collections::HashMap}; use proc_macro::TokenStream; use proc_macro2::Span; @@ -10,19 +10,76 @@ use proc_macro2::Span; use parser::ParseError; mod config; +use config::{get_template_source, read_config_file, Config}; mod generator; +use generator::{Generator, MapChain}; mod heritage; +use heritage::{Context, Heritage}; mod input; +use input::{Print, Source, TemplateArgs, TemplateInput}; #[proc_macro_derive(Template, attributes(template))] pub fn derive_template(input: TokenStream) -> TokenStream { let ast = syn::parse::(input).unwrap(); - match generator::build_template(&ast) { + match build_template(&ast) { Ok(source) => source.parse().unwrap(), Err(e) => e.into_compile_error(), } } +/// Takes a `syn::DeriveInput` and generates source code for it +/// +/// Reads the metadata from the `template()` attribute to get the template +/// metadata, then fetches the source from the filesystem. The source is +/// parsed, and the parse tree is fed to the code generator. Will print +/// the parse tree and/or generated source according to the `print` key's +/// value as passed to the `template()` attribute. +pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result { + let template_args = TemplateArgs::new(ast)?; + let config_toml = read_config_file(template_args.config_path.as_deref())?; + let config = Config::new(&config_toml, template_args.whitespace.as_ref())?; + let input = TemplateInput::new(ast, &config, template_args)?; + let source = match input.source { + Source::Source(ref s) => s.clone(), + Source::Path(_) => get_template_source(&input.path)?, + }; + + let mut templates = HashMap::new(); + input.find_used_templates(&mut templates, source)?; + + let mut contexts = HashMap::new(); + for (path, parsed) in &templates { + contexts.insert( + path.as_path(), + Context::new(input.config, path, parsed.nodes())?, + ); + } + + let ctx = &contexts[input.path.as_path()]; + let heritage = if !ctx.blocks.is_empty() || ctx.extends.is_some() { + Some(Heritage::new(ctx, &contexts)) + } else { + None + }; + + if input.print == Print::Ast || input.print == Print::All { + eprintln!("{:?}", templates[input.path.as_path()].nodes()); + } + + let code = Generator::new( + &input, + &contexts, + heritage.as_ref(), + MapChain::new(), + config.whitespace, + ) + .build(&contexts[input.path.as_path()])?; + if input.print == Print::Code || input.print == Print::All { + eprintln!("{code}"); + } + Ok(code) +} + #[derive(Debug, Clone)] struct CompileError { msg: Cow<'static, str>, From 61a6ae94c1bd68b0855795791efdda1c1c1b414f Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 6 Oct 2023 11:48:22 +0200 Subject: [PATCH 06/12] Implement Default for MapChain --- askama_derive/src/generator.rs | 16 +++++++++------- askama_derive/src/lib.rs | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 324fdf428..6cb54c85d 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -1775,13 +1775,6 @@ impl<'a, K: 'a, V: 'a> MapChain<'a, K, V> where K: cmp::Eq + hash::Hash, { - pub(crate) fn new() -> MapChain<'a, K, V> { - MapChain { - parent: None, - scopes: vec![HashMap::new()], - } - } - fn with_parent<'p>(parent: &'p MapChain<'_, K, V>) -> MapChain<'p, K, V> { MapChain { parent: Some(parent), @@ -1844,6 +1837,15 @@ impl MapChain<'_, &str, LocalMeta> { } } +impl<'a, K: Eq + hash::Hash, V> Default for MapChain<'a, K, V> { + fn default() -> Self { + Self { + parent: None, + scopes: vec![HashMap::new()], + } + } +} + /// Returns `true` if enough assumptions can be made, /// to determine that `self` is copyable. fn is_copyable(expr: &Expr<'_>) -> bool { diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index dc8ca7549..37e8a3bb6 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -70,7 +70,7 @@ pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result Date: Fri, 6 Oct 2023 11:50:56 +0200 Subject: [PATCH 07/12] Cleanup imports a bit --- askama_derive/src/config.rs | 1 - askama_derive/src/generator.rs | 8 ++++---- askama_derive/src/heritage.rs | 1 - 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/askama_derive/src/config.rs b/askama_derive/src/config.rs index b2a13aa82..47801cdcb 100644 --- a/askama_derive/src/config.rs +++ b/askama_derive/src/config.rs @@ -6,7 +6,6 @@ use std::{env, fs}; use serde::Deserialize; use crate::CompileError; - use parser::node::Whitespace; use parser::Syntax; diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 6cb54c85d..6fee5a62a 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -1,3 +1,7 @@ +use std::collections::hash_map::{Entry, HashMap}; +use std::path::{Path, PathBuf}; +use std::{cmp, hash, mem, str}; + use crate::config::{get_template_source, WhitespaceHandling}; use crate::heritage::{Context, Heritage}; use crate::input::{Source, TemplateInput}; @@ -9,10 +13,6 @@ use parser::node::{ use parser::{Expr, Node, Parsed}; use quote::quote; -use std::collections::hash_map::{Entry, HashMap}; -use std::path::{Path, PathBuf}; -use std::{cmp, hash, mem, str}; - pub(crate) struct Generator<'a> { // The template input state: original struct AST and attributes input: &'a TemplateInput<'a>, diff --git a/askama_derive/src/heritage.rs b/askama_derive/src/heritage.rs index ce85ac769..2f9cb4569 100644 --- a/askama_derive/src/heritage.rs +++ b/askama_derive/src/heritage.rs @@ -3,7 +3,6 @@ use std::path::{Path, PathBuf}; use crate::config::Config; use crate::CompileError; - use parser::node::{BlockDef, Macro, Match}; use parser::Node; From 5fc350b468c228618024a148bb0033c0b74db136 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 6 Oct 2023 11:53:05 +0200 Subject: [PATCH 08/12] Inline some logic into find_used_templates() --- askama_derive/src/input.rs | 6 +++++- askama_derive/src/lib.rs | 10 +++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/askama_derive/src/input.rs b/askama_derive/src/input.rs index 03817f1cf..891dec7fc 100644 --- a/askama_derive/src/input.rs +++ b/askama_derive/src/input.rs @@ -104,8 +104,12 @@ impl TemplateInput<'_> { pub(crate) fn find_used_templates( &self, map: &mut HashMap, - source: String, ) -> Result<(), CompileError> { + let source = match &self.source { + Source::Source(s) => s.clone(), + Source::Path(_) => get_template_source(&self.path)?, + }; + let mut dependency_graph = Vec::new(); let mut check = vec![(self.path.clone(), source)]; while let Some((path, source)) = check.pop() { diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index 37e8a3bb6..3046f9dbe 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -10,13 +10,13 @@ use proc_macro2::Span; use parser::ParseError; mod config; -use config::{get_template_source, read_config_file, Config}; +use config::{read_config_file, Config}; mod generator; use generator::{Generator, MapChain}; mod heritage; use heritage::{Context, Heritage}; mod input; -use input::{Print, Source, TemplateArgs, TemplateInput}; +use input::{Print, TemplateArgs, TemplateInput}; #[proc_macro_derive(Template, attributes(template))] pub fn derive_template(input: TokenStream) -> TokenStream { @@ -39,13 +39,9 @@ pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result s.clone(), - Source::Path(_) => get_template_source(&input.path)?, - }; let mut templates = HashMap::new(); - input.find_used_templates(&mut templates, source)?; + input.find_used_templates(&mut templates)?; let mut contexts = HashMap::new(); for (path, parsed) in &templates { From ef0e54fd4268e3542dca7e6998116313ab388e7d Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 6 Oct 2023 12:06:17 +0200 Subject: [PATCH 09/12] Reorganize TemplateArgs and TemplateInput structure --- askama_derive/src/input.rs | 42 +++++++++++++++++++------------------- askama_derive/src/lib.rs | 7 +++---- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/askama_derive/src/input.rs b/askama_derive/src/input.rs index 891dec7fc..45da7f025 100644 --- a/askama_derive/src/input.rs +++ b/askama_derive/src/input.rs @@ -6,7 +6,7 @@ use mime::Mime; use quote::ToTokens; use syn::punctuated::Punctuated; -use crate::config::{get_template_source, Config}; +use crate::config::{get_template_source, read_config_file, Config}; use crate::CompileError; use parser::{Node, Parsed, Syntax}; @@ -14,10 +14,10 @@ pub(crate) struct TemplateInput<'a> { pub(crate) ast: &'a syn::DeriveInput, pub(crate) config: &'a Config<'a>, pub(crate) syntax: &'a Syntax<'a>, - pub(crate) source: Source, + pub(crate) source: &'a Source, pub(crate) print: Print, pub(crate) escaper: &'a str, - pub(crate) ext: Option, + pub(crate) ext: Option<&'a str>, pub(crate) mime_type: String, pub(crate) path: PathBuf, } @@ -29,7 +29,7 @@ impl TemplateInput<'_> { pub(crate) fn new<'n>( ast: &'n syn::DeriveInput, config: &'n Config<'_>, - args: TemplateArgs, + args: &'n TemplateArgs, ) -> Result, CompileError> { let TemplateArgs { source, @@ -43,7 +43,9 @@ impl TemplateInput<'_> { // Validate the `source` and `ext` value together, since they are // related. In case `source` was used instead of `path`, the value // of `ext` is merged into a synthetic `path` value here. - let source = source.expect("template path or source not found in attributes"); + let source = source + .as_ref() + .expect("template path or source not found in attributes"); let path = match (&source, &ext) { (Source::Path(path), _) => config.find_template(path, None)?, (&Source::Source(_), Some(ext)) => PathBuf::from(format!("{}.{}", ast.ident, ext)), @@ -53,28 +55,25 @@ impl TemplateInput<'_> { }; // Validate syntax - let syntax = syntax.map_or_else( + let syntax = syntax.as_deref().map_or_else( || Ok(config.syntaxes.get(config.default_syntax).unwrap()), |s| { config .syntaxes - .get(&s) + .get(s) .ok_or_else(|| CompileError::from(format!("attribute syntax {s} not exist"))) }, )?; // Match extension against defined output formats - let escaping = escaping.unwrap_or_else(|| { - path.extension() - .map(|s| s.to_str().unwrap()) - .unwrap_or("") - .to_string() - }); + let escaping = escaping + .as_deref() + .unwrap_or_else(|| path.extension().map(|s| s.to_str().unwrap()).unwrap_or("")); let mut escaper = None; for (extensions, path) in &config.escapers { - if extensions.contains(&escaping) { + if extensions.contains(escaping) { escaper = Some(path); break; } @@ -93,9 +92,9 @@ impl TemplateInput<'_> { config, syntax, source, - print, + print: *print, escaper, - ext, + ext: ext.as_deref(), mime_type, path, }) @@ -148,18 +147,18 @@ impl TemplateInput<'_> { #[inline] pub(crate) fn extension(&self) -> Option<&str> { - ext_default_to_path(self.ext.as_deref(), &self.path) + ext_default_to_path(self.ext, &self.path) } } -#[derive(Default)] +#[derive(Debug, Default)] pub(crate) struct TemplateArgs { pub(crate) source: Option, pub(crate) print: Print, pub(crate) escaping: Option, pub(crate) ext: Option, pub(crate) syntax: Option, - pub(crate) config_path: Option, + pub(crate) config: String, pub(crate) whitespace: Option, } @@ -259,7 +258,7 @@ impl TemplateArgs { } } else if ident == "config" { if let syn::Lit::Str(s) = value.lit { - args.config_path = Some(s.value()) + args.config = read_config_file(Some(&s.value()))?; } else { return Err("config value must be string literal".into()); } @@ -297,12 +296,13 @@ fn extension(path: &Path) -> Option<&str> { } } +#[derive(Debug)] pub(crate) enum Source { Path(String), Source(String), } -#[derive(PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub(crate) enum Print { All, Ast, diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index 3046f9dbe..a133813e3 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -10,7 +10,7 @@ use proc_macro2::Span; use parser::ParseError; mod config; -use config::{read_config_file, Config}; +use config::Config; mod generator; use generator::{Generator, MapChain}; mod heritage; @@ -36,9 +36,8 @@ pub fn derive_template(input: TokenStream) -> TokenStream { /// value as passed to the `template()` attribute. pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result { let template_args = TemplateArgs::new(ast)?; - let config_toml = read_config_file(template_args.config_path.as_deref())?; - let config = Config::new(&config_toml, template_args.whitespace.as_ref())?; - let input = TemplateInput::new(ast, &config, template_args)?; + let config = Config::new(&template_args.config, template_args.whitespace.as_ref())?; + let input = TemplateInput::new(ast, &config, &template_args)?; let mut templates = HashMap::new(); input.find_used_templates(&mut templates)?; From 7d0c413880341c17b6cbcb0c56acbb51d7e7470f Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 10 Oct 2023 15:10:12 +0200 Subject: [PATCH 10/12] Build Config from TemplateArgs --- askama_derive/src/input.rs | 18 +++++++++++------- askama_derive/src/lib.rs | 3 +-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/askama_derive/src/input.rs b/askama_derive/src/input.rs index 45da7f025..ee9b94233 100644 --- a/askama_derive/src/input.rs +++ b/askama_derive/src/input.rs @@ -153,13 +153,13 @@ impl TemplateInput<'_> { #[derive(Debug, Default)] pub(crate) struct TemplateArgs { - pub(crate) source: Option, - pub(crate) print: Print, - pub(crate) escaping: Option, - pub(crate) ext: Option, - pub(crate) syntax: Option, - pub(crate) config: String, - pub(crate) whitespace: Option, + source: Option, + print: Print, + escaping: Option, + ext: Option, + syntax: Option, + config: String, + whitespace: Option, } impl TemplateArgs { @@ -275,6 +275,10 @@ impl TemplateArgs { Ok(args) } + + pub(crate) fn config(&self) -> Result, CompileError> { + Config::new(&self.config, self.whitespace.as_ref()) + } } #[inline] diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index a133813e3..a5eb67a30 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -10,7 +10,6 @@ use proc_macro2::Span; use parser::ParseError; mod config; -use config::Config; mod generator; use generator::{Generator, MapChain}; mod heritage; @@ -36,7 +35,7 @@ pub fn derive_template(input: TokenStream) -> TokenStream { /// value as passed to the `template()` attribute. pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result { let template_args = TemplateArgs::new(ast)?; - let config = Config::new(&template_args.config, template_args.whitespace.as_ref())?; + let config = template_args.config()?; let input = TemplateInput::new(ast, &config, &template_args)?; let mut templates = HashMap::new(); From 252601184d1d90d6d494a3cc3a54f7e77eb489e0 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 10 Oct 2023 15:17:36 +0200 Subject: [PATCH 11/12] Avoid passing around duplicate data --- askama_derive/src/generator.rs | 15 ++------------- askama_derive/src/lib.rs | 10 ++-------- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 6fee5a62a..7aa6d296d 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -37,9 +37,6 @@ pub(crate) struct Generator<'a> { buf_writable: Vec>, // Counter for write! hash named arguments named: usize, - // If set to `suppress`, the whitespace characters will be removed by default unless `+` is - // used. - whitespace: WhitespaceHandling, } impl<'a> Generator<'a> { @@ -48,7 +45,6 @@ impl<'a> Generator<'a> { contexts: &'n HashMap<&'n Path, Context<'n>>, heritage: Option<&'n Heritage<'_>>, locals: MapChain<'n, &'n str, LocalMeta>, - whitespace: WhitespaceHandling, ) -> Generator<'n> { Generator { input, @@ -61,7 +57,6 @@ impl<'a> Generator<'a> { super_block: None, buf_writable: vec![], named: 0, - whitespace, } } @@ -796,13 +791,7 @@ impl<'a> Generator<'a> { // handle the include's nodes. Unfortunately we can't easily share the `includes` cache. let locals = MapChain::with_parent(&self.locals); - let mut child = Self::new( - self.input, - self.contexts, - self.heritage, - locals, - self.whitespace, - ); + let mut child = Self::new(self.input, self.contexts, self.heritage, locals); let nodes = match self.contexts.get(path.as_path()) { Some(ctx) => ctx.nodes, @@ -1638,7 +1627,7 @@ impl<'a> Generator<'a> { Some(Whitespace::Suppress) => WhitespaceHandling::Suppress, Some(Whitespace::Preserve) => WhitespaceHandling::Preserve, Some(Whitespace::Minimize) => WhitespaceHandling::Minimize, - None => self.whitespace, + None => self.input.config.whitespace, } } diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index a5eb67a30..547a449f3 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -60,14 +60,8 @@ pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result Date: Tue, 10 Oct 2023 15:21:02 +0200 Subject: [PATCH 12/12] Apply clippy suggestions --- askama_derive/src/heritage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/askama_derive/src/heritage.rs b/askama_derive/src/heritage.rs index 2f9cb4569..d75d0a597 100644 --- a/askama_derive/src/heritage.rs +++ b/askama_derive/src/heritage.rs @@ -25,7 +25,7 @@ impl Heritage<'_> { while let Some(ref path) = ctx.extends { ctx = &contexts[path.as_path()]; for (name, def) in &ctx.blocks { - blocks.entry(name).or_insert_with(Vec::new).push((ctx, def)); + blocks.entry(name).or_default().push((ctx, def)); } }