From 1db7e8cb89f147cce4402a35f3676f5d1039672d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Thu, 10 Mar 2022 09:08:32 +0100 Subject: [PATCH 1/6] Fix clippy warning --- askama_derive/src/lib.rs | 2 +- askama_shared/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index 2409ba48d..00936d045 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -18,7 +18,7 @@ pub 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.to_compile_error().into(), + Err(e) => e.into_compile_error().into(), } } diff --git a/askama_shared/src/lib.rs b/askama_shared/src/lib.rs index 994662c5f..f8d572968 100644 --- a/askama_shared/src/lib.rs +++ b/askama_shared/src/lib.rs @@ -307,7 +307,7 @@ impl CompileError { } } - pub fn to_compile_error(self) -> TokenStream { + pub fn into_compile_error(self) -> TokenStream { syn::Error::new(self.span, self.msg).to_compile_error() } } From f8eef40c3f0d3d9ffe7168e3242981e10f8add8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Thu, 10 Mar 2022 08:36:46 +0100 Subject: [PATCH 2/6] Move handling of integrations into askama_shared Before this PR the handling of integrations was done both by askama_shared and askama_derive. This diff lets askama_shared do the work. This will prevent problems like #629, when both packages might come out of sync. --- askama/Cargo.toml | 20 +++++------ askama_derive/Cargo.toml | 13 ------- askama_derive/src/lib.rs | 16 ++------- askama_shared/Cargo.toml | 8 +++++ askama_shared/src/generator.rs | 66 ++++++++++++++-------------------- askama_shared/src/lib.rs | 11 ------ 6 files changed, 47 insertions(+), 87 deletions(-) diff --git a/askama/Cargo.toml b/askama/Cargo.toml index 64468b23e..cdf836bda 100644 --- a/askama/Cargo.toml +++ b/askama/Cargo.toml @@ -17,20 +17,20 @@ maintenance = { status = "actively-developed" } [features] default = ["config", "humansize", "num-traits", "urlencode"] -config = ["askama_derive/config", "askama_shared/config"] +config = ["askama_shared/config"] humansize = ["askama_shared/humansize"] markdown = ["askama_shared/markdown"] urlencode = ["askama_shared/percent-encoding"] -serde-json = ["askama_derive/json", "askama_shared/json"] -serde-yaml = ["askama_derive/yaml", "askama_shared/yaml"] +serde-json = ["askama_shared/json"] +serde-yaml = ["askama_shared/yaml"] num-traits = ["askama_shared/num-traits"] -with-actix-web = ["askama_derive/actix-web"] -with-axum = ["askama_derive/axum"] -with-gotham = ["askama_derive/gotham"] -with-mendes = ["askama_derive/mendes"] -with-rocket = ["askama_derive/rocket"] -with-tide = ["askama_derive/tide"] -with-warp = ["askama_derive/warp"] +with-actix-web = ["askama_shared/actix-web"] +with-axum = ["askama_shared/axum"] +with-gotham = ["askama_shared/gotham"] +with-mendes = ["askama_shared/mendes"] +with-rocket = ["askama_shared/rocket"] +with-tide = ["askama_shared/tide"] +with-warp = ["askama_shared/warp"] # deprecated mime = [] diff --git a/askama_derive/Cargo.toml b/askama_derive/Cargo.toml index 80301dd5a..0900ac761 100644 --- a/askama_derive/Cargo.toml +++ b/askama_derive/Cargo.toml @@ -12,19 +12,6 @@ edition = "2018" [lib] proc-macro = true -[features] -config = ["askama_shared/config"] -json = ["askama_shared/json"] -yaml = ["askama_shared/yaml"] - -actix-web = [] -axum = [] -gotham = [] -mendes = [] -rocket = [] -tide = [] -warp = [] - [dependencies] askama_shared = { version = "0.12.1", path = "../askama_shared", default-features = false } proc-macro2 = "1" diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index 00936d045..d04ef72de 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -5,9 +5,7 @@ use askama_shared::heritage::{Context, Heritage}; use askama_shared::input::{Print, Source, TemplateInput}; use askama_shared::parser::{parse, Expr, Node}; -use askama_shared::{ - generator, get_template_source, read_config_file, CompileError, Config, Integrations, -}; +use askama_shared::{generator, get_template_source, read_config_file, CompileError, Config}; use proc_macro::TokenStream; use std::collections::HashMap; @@ -62,7 +60,7 @@ fn build_template(ast: &syn::DeriveInput) -> Result { eprintln!("{:?}", parsed[input.path.as_path()]); } - let code = generator::generate(&input, &contexts, heritage.as_ref(), INTEGRATIONS)?; + let code = generator::generate(&input, &contexts, heritage.as_ref())?; if input.print == Print::Code || input.print == Print::All { eprintln!("{}", code); } @@ -108,13 +106,3 @@ fn find_used_templates( } Ok(()) } - -const INTEGRATIONS: Integrations = Integrations { - actix: cfg!(feature = "actix-web"), - axum: cfg!(feature = "axum"), - gotham: cfg!(feature = "gotham"), - mendes: cfg!(feature = "mendes"), - rocket: cfg!(feature = "rocket"), - tide: cfg!(feature = "tide"), - warp: cfg!(feature = "warp"), -}; diff --git a/askama_shared/Cargo.toml b/askama_shared/Cargo.toml index 8e2be28e8..9d95b001a 100644 --- a/askama_shared/Cargo.toml +++ b/askama_shared/Cargo.toml @@ -16,6 +16,14 @@ json = ["serde", "serde_json", "askama_escape/json"] markdown = ["comrak"] yaml = ["serde", "serde_yaml"] +actix-web = [] +axum = [] +gotham = [] +mendes = [] +rocket = [] +tide = [] +warp = [] + [dependencies] askama_escape = { version = "0.10.3", path = "../askama_escape" } comrak = { version = "0.12", optional = true, default-features = false } diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs index 99971392d..def8c0840 100644 --- a/askama_shared/src/generator.rs +++ b/askama_shared/src/generator.rs @@ -1,11 +1,9 @@ -use super::{get_template_source, CompileError, Integrations}; +use super::{get_template_source, CompileError}; use crate::filters; use crate::heritage::{Context, Heritage}; use crate::input::{Source, TemplateInput}; use crate::parser::{parse, Cond, CondTest, Expr, Loop, Node, Target, When, Ws}; -use proc_macro2::Span; - use quote::{quote, ToTokens}; use std::collections::HashMap; @@ -16,9 +14,8 @@ pub fn generate( input: &TemplateInput<'_>, contexts: &HashMap<&Path, Context<'_>, S>, heritage: Option<&Heritage<'_>>, - integrations: Integrations, ) -> Result { - Generator::new(input, contexts, heritage, integrations, MapChain::new()) + Generator::new(input, contexts, heritage, MapChain::new()) .build(&contexts[input.path.as_path()]) } @@ -29,8 +26,6 @@ struct Generator<'a, S: std::hash::BuildHasher> { contexts: &'a HashMap<&'a Path, Context<'a>, S>, // The heritage contains references to blocks and their ancestry heritage: Option<&'a Heritage<'a>>, - // What integrations need to be generated - integrations: Integrations, // Variables accessible directly from the current scope (not redirected to context) locals: MapChain<'a, &'a str, LocalMeta>, // Suffix whitespace from the previous literal. Will be flushed to the @@ -53,14 +48,12 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { input: &'n TemplateInput<'_>, contexts: &'n HashMap<&'n Path, Context<'n>, S>, heritage: Option<&'n Heritage<'_>>, - integrations: Integrations, locals: MapChain<'n, &'n str, LocalMeta>, ) -> Generator<'n, S> { Generator { input, contexts, heritage, - integrations, locals, next_ws: None, skip_ws: false, @@ -72,13 +65,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { fn child(&mut self) -> Generator<'_, S> { let locals = MapChain::with_parent(&self.locals); - Self::new( - self.input, - self.contexts, - self.heritage, - self.integrations, - locals, - ) + Self::new(self.input, self.contexts, self.heritage, locals) } // Takes a Context and generates the relevant implementations. @@ -93,27 +80,21 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { self.impl_template(ctx, &mut buf)?; self.impl_display(&mut buf)?; - if self.integrations.actix { - self.impl_actix_web_responder(&mut buf)?; - } - if self.integrations.axum { - self.impl_axum_into_response(&mut buf)?; - } - if self.integrations.gotham { - self.impl_gotham_into_response(&mut buf)?; - } - if self.integrations.mendes { - self.impl_mendes_responder(&mut buf)?; - } - if self.integrations.rocket { - self.impl_rocket_responder(&mut buf)?; - } - if self.integrations.tide { - self.impl_tide_integrations(&mut buf)?; - } - if self.integrations.warp { - self.impl_warp_reply(&mut buf)?; - } + #[cfg(feature = "actix-web")] + self.impl_actix_web_responder(&mut buf)?; + #[cfg(feature = "axum")] + self.impl_axum_into_response(&mut buf)?; + #[cfg(feature = "gotham")] + self.impl_gotham_into_response(&mut buf)?; + #[cfg(feature = "mendes")] + self.impl_mendes_responder(&mut buf)?; + #[cfg(feature = "rocket")] + self.impl_rocket_responder(&mut buf)?; + #[cfg(feature = "tide")] + self.impl_tide_integrations(&mut buf)?; + #[cfg(feature = "warp")] + self.impl_warp_reply(&mut buf)?; + Ok(buf.buf) } @@ -202,6 +183,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { } // Implement Actix-web's `Responder`. + #[cfg(feature = "actix-web")] fn impl_actix_web_responder(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { self.write_header(buf, "::askama_actix::actix_web::Responder", None)?; buf.writeln("type Body = ::askama_actix::actix_web::body::BoxBody;")?; @@ -216,6 +198,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { } // Implement Axum's `IntoResponse`. + #[cfg(feature = "axum")] fn impl_axum_into_response(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { self.write_header(buf, "::askama_axum::IntoResponse", None)?; buf.writeln("#[inline]")?; @@ -230,6 +213,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { } // Implement gotham's `IntoResponse`. + #[cfg(feature = "gotham")] fn impl_gotham_into_response(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { self.write_header(buf, "::askama_gotham::IntoResponse", None)?; buf.writeln("#[inline]")?; @@ -244,6 +228,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { } // Implement mendes' `Responder`. + #[cfg(feature = "mendes")] fn impl_mendes_responder(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { let param = syn::parse_str("A: ::mendes::Application").unwrap(); @@ -255,7 +240,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { let mut where_clause = match where_clause { Some(clause) => clause.clone(), None => syn::WhereClause { - where_token: syn::Token![where](Span::call_site()), + where_token: syn::Token![where](proc_macro2::Span::call_site()), predicates: syn::punctuated::Punctuated::new(), }, }; @@ -293,8 +278,9 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { } // Implement Rocket's `Responder`. + #[cfg(feature = "rocket")] fn impl_rocket_responder(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { - let lifetime = syn::Lifetime::new("'askama", Span::call_site()); + let lifetime = syn::Lifetime::new("'askama", proc_macro2::Span::call_site()); let param = syn::GenericParam::Lifetime(syn::LifetimeDef::new(lifetime)); self.write_header( buf, @@ -315,6 +301,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { Ok(()) } + #[cfg(feature = "tide")] fn impl_tide_integrations(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { let ext = self.input.extension().unwrap_or("txt"); @@ -340,6 +327,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { buf.writeln("}\n}") } + #[cfg(feature = "warp")] fn impl_warp_reply(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { self.write_header(buf, "::askama_warp::warp::reply::Reply", None)?; buf.writeln("#[inline]")?; diff --git a/askama_shared/src/lib.rs b/askama_shared/src/lib.rs index f8d572968..8a336bbea 100644 --- a/askama_shared/src/lib.rs +++ b/askama_shared/src/lib.rs @@ -274,17 +274,6 @@ pub fn get_template_source(tpl_path: &Path) -> std::result::Result Date: Thu, 10 Mar 2022 08:44:35 +0100 Subject: [PATCH 3/6] Reduce askama_derive to a single re-export All the hard work in askama_derive was actually done in askama_shared. This PR removes the back-and-forth interaction between the two crates. Now askama_derive is a single re-export of `#[derive(Template)]` which has to be done in a proc_macro crate. This most likely means that askama_derive is "final", unless another derive template needs to be introduced in the future. --- askama_derive/Cargo.toml | 2 - askama_derive/src/lib.rs | 100 +--------------------------------- askama_shared/src/derive.rs | 105 ++++++++++++++++++++++++++++++++++++ askama_shared/src/lib.rs | 2 + 4 files changed, 108 insertions(+), 101 deletions(-) create mode 100644 askama_shared/src/derive.rs diff --git a/askama_derive/Cargo.toml b/askama_derive/Cargo.toml index 0900ac761..c1522192c 100644 --- a/askama_derive/Cargo.toml +++ b/askama_derive/Cargo.toml @@ -14,5 +14,3 @@ proc-macro = true [dependencies] askama_shared = { version = "0.12.1", path = "../askama_shared", default-features = false } -proc-macro2 = "1" -syn = "1" diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index d04ef72de..b41fa6ac5 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -2,107 +2,9 @@ #![deny(elided_lifetimes_in_paths)] #![deny(unreachable_pub)] -use askama_shared::heritage::{Context, Heritage}; -use askama_shared::input::{Print, Source, TemplateInput}; -use askama_shared::parser::{parse, Expr, Node}; -use askama_shared::{generator, get_template_source, read_config_file, CompileError, Config}; use proc_macro::TokenStream; -use std::collections::HashMap; -use std::path::PathBuf; - #[proc_macro_derive(Template, attributes(template))] pub 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().into(), - } -} - -/// 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. -fn build_template(ast: &syn::DeriveInput) -> Result { - let config_toml = read_config_file()?; - let config = Config::new(&config_toml)?; - let input = TemplateInput::new(ast, &config)?; - let source: String = match input.source { - Source::Source(ref s) => s.clone(), - Source::Path(_) => get_template_source(&input.path)?, - }; - - let mut sources = HashMap::new(); - find_used_templates(&input, &mut sources, source)?; - - let mut parsed = HashMap::new(); - for (path, src) in &sources { - parsed.insert(path.as_path(), parse(src, input.syntax)?); - } - - let mut contexts = HashMap::new(); - for (path, nodes) in &parsed { - contexts.insert(*path, Context::new(input.config, path, 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!("{:?}", parsed[input.path.as_path()]); - } - - let code = generator::generate(&input, &contexts, heritage.as_ref())?; - if input.print == Print::Code || input.print == Print::All { - eprintln!("{}", code); - } - Ok(code) -} - -fn find_used_templates( - input: &TemplateInput<'_>, - 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() { - for n in parse(&source, input.syntax)? { - match n { - Node::Extends(Expr::StrLit(extends)) => { - let extends = input.config.find_template(extends, Some(&path))?; - let dependency_path = (path.clone(), extends.clone()); - if dependency_graph.contains(&dependency_path) { - return Err(format!( - "cyclic dependecy 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, Some(&path))?; - let source = get_template_source(&import)?; - check.push((import, source)); - } - _ => {} - } - } - map.insert(path, source); - } - Ok(()) + askama_shared::derive_template(input.into()).into() } diff --git a/askama_shared/src/derive.rs b/askama_shared/src/derive.rs new file mode 100644 index 000000000..04b3ba015 --- /dev/null +++ b/askama_shared/src/derive.rs @@ -0,0 +1,105 @@ +use std::collections::HashMap; +use std::path::PathBuf; + +use proc_macro2::TokenStream; + +use crate::heritage::{Context, Heritage}; +use crate::input::{Print, Source, TemplateInput}; +use crate::parser::{parse, Expr, Node}; +use crate::{generator, get_template_source, read_config_file, CompileError, Config}; + +/// The actual implementation for askama_derive::Template +pub fn derive_template(input: TokenStream) -> TokenStream { + let ast: syn::DeriveInput = syn::parse2(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 +/// 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. +fn build_template(ast: &syn::DeriveInput) -> Result { + let config_toml = read_config_file()?; + let config = Config::new(&config_toml)?; + let input = TemplateInput::new(ast, &config)?; + let source: String = match input.source { + Source::Source(ref s) => s.clone(), + Source::Path(_) => get_template_source(&input.path)?, + }; + + let mut sources = HashMap::new(); + find_used_templates(&input, &mut sources, source)?; + + let mut parsed = HashMap::new(); + for (path, src) in &sources { + parsed.insert(path.as_path(), parse(src, input.syntax)?); + } + + let mut contexts = HashMap::new(); + for (path, nodes) in &parsed { + contexts.insert(*path, Context::new(input.config, path, 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!("{:?}", parsed[input.path.as_path()]); + } + + let code = generator::generate(&input, &contexts, heritage.as_ref())?; + if input.print == Print::Code || input.print == Print::All { + eprintln!("{}", code); + } + Ok(code) +} + +fn find_used_templates( + input: &TemplateInput<'_>, + 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() { + for n in parse(&source, input.syntax)? { + match n { + Node::Extends(Expr::StrLit(extends)) => { + let extends = input.config.find_template(extends, Some(&path))?; + let dependency_path = (path.clone(), extends.clone()); + if dependency_graph.contains(&dependency_path) { + return Err(format!( + "cyclic dependecy 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, Some(&path))?; + let source = get_template_source(&import)?; + check.push((import, source)); + } + _ => {} + } + } + map.insert(path, source); + } + Ok(()) +} diff --git a/askama_shared/src/lib.rs b/askama_shared/src/lib.rs index 8a336bbea..5a8facdea 100644 --- a/askama_shared/src/lib.rs +++ b/askama_shared/src/lib.rs @@ -13,9 +13,11 @@ use proc_macro2::{Span, TokenStream}; #[cfg(feature = "serde")] use serde::Deserialize; +pub use crate::derive::derive_template; pub use crate::input::extension_to_mime_type; pub use askama_escape::MarkupDisplay; +mod derive; mod error; pub use crate::error::{Error, Result}; pub mod filters; From 3baec95eea1620f6e849f013b39b864f5b4048d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Thu, 10 Mar 2022 09:02:10 +0100 Subject: [PATCH 4/6] Un-"pub" most of askama_shared's internals Previously askama_shared exported most of it's internals, so askama_derive could use them. This is not needed anymore. --- askama/src/lib.rs | 4 +-- askama_shared/src/derive.rs | 1 + askama_shared/src/generator.rs | 2 +- askama_shared/src/heritage.rs | 22 +++++++------- askama_shared/src/input.rs | 32 ++++++++++---------- askama_shared/src/lib.rs | 50 ++++++++++++++----------------- askama_shared/src/parser.rs | 55 ++++++++++++++++++---------------- 7 files changed, 82 insertions(+), 84 deletions(-) diff --git a/askama/src/lib.rs b/askama/src/lib.rs index ccfddc8fb..079c8f5b0 100644 --- a/askama/src/lib.rs +++ b/askama/src/lib.rs @@ -133,9 +133,7 @@ impl DynTemplate for T { } } -pub use crate::shared::filters; -pub use crate::shared::helpers; -pub use crate::shared::{read_config_file, Error, MarkupDisplay, Result}; +pub use crate::shared::{filters, helpers, Error, MarkupDisplay, Result}; pub use askama_derive::*; #[deprecated(since = "0.11.1", note = "The only function in this mod is deprecated")] diff --git a/askama_shared/src/derive.rs b/askama_shared/src/derive.rs index 04b3ba015..b8f0a2f12 100644 --- a/askama_shared/src/derive.rs +++ b/askama_shared/src/derive.rs @@ -9,6 +9,7 @@ use crate::parser::{parse, Expr, Node}; use crate::{generator, get_template_source, read_config_file, CompileError, Config}; /// The actual implementation for askama_derive::Template +#[doc(hidden)] pub fn derive_template(input: TokenStream) -> TokenStream { let ast: syn::DeriveInput = syn::parse2(input).unwrap(); match build_template(&ast) { diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs index def8c0840..f7783afc0 100644 --- a/askama_shared/src/generator.rs +++ b/askama_shared/src/generator.rs @@ -10,7 +10,7 @@ use std::collections::HashMap; use std::path::Path; use std::{cmp, hash, mem, str}; -pub fn generate( +pub(crate) fn generate( input: &TemplateInput<'_>, contexts: &HashMap<&Path, Context<'_>, S>, heritage: Option<&Heritage<'_>>, diff --git a/askama_shared/src/heritage.rs b/askama_shared/src/heritage.rs index 8dd97e2e6..49599af32 100644 --- a/askama_shared/src/heritage.rs +++ b/askama_shared/src/heritage.rs @@ -4,13 +4,13 @@ use std::path::{Path, PathBuf}; use crate::parser::{Expr, Loop, Macro, Node}; use crate::{CompileError, Config}; -pub struct Heritage<'a> { - pub root: &'a Context<'a>, - pub blocks: BlockAncestry<'a>, +pub(crate) struct Heritage<'a> { + pub(crate) root: &'a Context<'a>, + pub(crate) blocks: BlockAncestry<'a>, } impl Heritage<'_> { - pub fn new<'n, S: std::hash::BuildHasher>( + pub(crate) fn new<'n, S: std::hash::BuildHasher>( mut ctx: &'n Context<'n>, contexts: &'n HashMap<&'n Path, Context<'n>, S>, ) -> Heritage<'n> { @@ -33,16 +33,16 @@ impl Heritage<'_> { type BlockAncestry<'a> = HashMap<&'a str, Vec<(&'a Context<'a>, &'a Node<'a>)>>; -pub struct Context<'a> { - pub nodes: &'a [Node<'a>], - pub extends: Option, - pub blocks: HashMap<&'a str, &'a Node<'a>>, - pub macros: HashMap<&'a str, &'a Macro<'a>>, - pub imports: HashMap<&'a str, PathBuf>, +pub(crate) struct Context<'a> { + pub(crate) nodes: &'a [Node<'a>], + pub(crate) extends: Option, + pub(crate) blocks: HashMap<&'a str, &'a Node<'a>>, + pub(crate) macros: HashMap<&'a str, &'a Macro<'a>>, + pub(crate) imports: HashMap<&'a str, PathBuf>, } impl Context<'_> { - pub fn new<'n>( + pub(crate) fn new<'n>( config: &Config<'_>, path: &Path, nodes: &'n [Node<'n>], diff --git a/askama_shared/src/input.rs b/askama_shared/src/input.rs index f7eac2336..c70b250f8 100644 --- a/askama_shared/src/input.rs +++ b/askama_shared/src/input.rs @@ -6,17 +6,17 @@ use std::str::FromStr; use mime::Mime; use quote::ToTokens; -pub struct TemplateInput<'a> { - pub ast: &'a syn::DeriveInput, - pub config: &'a Config<'a>, - pub syntax: &'a Syntax<'a>, - pub source: Source, - pub print: Print, - pub escaper: &'a str, - pub ext: Option, - pub mime_type: String, - pub parent: Option<&'a syn::Type>, - pub path: PathBuf, +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) print: Print, + pub(crate) escaper: &'a str, + pub(crate) ext: Option, + pub(crate) mime_type: String, + pub(crate) parent: Option<&'a syn::Type>, + pub(crate) path: PathBuf, } impl TemplateInput<'_> { @@ -24,7 +24,7 @@ impl TemplateInput<'_> { /// mostly recovers the data for the `TemplateInput` fields from the /// `template()` attribute list fields; it also finds the of the `_parent` /// field, if any. - pub fn new<'n>( + pub(crate) fn new<'n>( ast: &'n syn::DeriveInput, config: &'n Config<'_>, ) -> Result, CompileError> { @@ -209,13 +209,13 @@ impl TemplateInput<'_> { } #[inline] - pub fn extension(&self) -> Option<&str> { + pub(crate) fn extension(&self) -> Option<&str> { ext_default_to_path(self.ext.as_deref(), &self.path) } } #[inline] -pub fn ext_default_to_path<'a>(ext: Option<&'a str>, path: &'a Path) -> Option<&'a str> { +fn ext_default_to_path<'a>(ext: Option<&'a str>, path: &'a Path) -> Option<&'a str> { ext.or_else(|| extension(path)) } @@ -233,13 +233,13 @@ fn extension(path: &Path) -> Option<&str> { } } -pub enum Source { +pub(crate) enum Source { Path(String), Source(String), } #[derive(PartialEq)] -pub enum Print { +pub(crate) enum Print { All, Ast, Code, diff --git a/askama_shared/src/lib.rs b/askama_shared/src/lib.rs index 5a8facdea..61ddb0084 100644 --- a/askama_shared/src/lib.rs +++ b/askama_shared/src/lib.rs @@ -21,26 +21,22 @@ mod derive; mod error; pub use crate::error::{Error, Result}; pub mod filters; -#[doc(hidden)] -pub mod generator; +mod generator; pub mod helpers; -#[doc(hidden)] -pub mod heritage; -#[doc(hidden)] -pub mod input; -#[doc(hidden)] -pub mod parser; +mod heritage; +mod input; +mod parser; #[derive(Debug)] -pub struct Config<'a> { - pub dirs: Vec, - pub syntaxes: BTreeMap>, - pub default_syntax: &'a str, - pub escapers: Vec<(HashSet, String)>, +struct Config<'a> { + dirs: Vec, + syntaxes: BTreeMap>, + default_syntax: &'a str, + escapers: Vec<(HashSet, String)>, } impl Config<'_> { - pub fn new(s: &str) -> std::result::Result, CompileError> { + fn new(s: &str) -> std::result::Result, CompileError> { let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); let default_dirs = vec![root.join("templates")]; @@ -108,7 +104,7 @@ impl Config<'_> { }) } - pub fn find_template( + fn find_template( &self, path: &str, start_at: Option<&Path>, @@ -136,13 +132,13 @@ impl Config<'_> { } #[derive(Debug)] -pub struct Syntax<'a> { - pub block_start: &'a str, - pub block_end: &'a str, - pub expr_start: &'a str, - pub expr_end: &'a str, - pub comment_start: &'a str, - pub comment_end: &'a str, +struct Syntax<'a> { + block_start: &'a str, + block_end: &'a str, + expr_start: &'a str, + expr_end: &'a str, + comment_start: &'a str, + comment_end: &'a str, } impl Default for Syntax<'_> { @@ -241,7 +237,7 @@ struct RawEscaper<'a> { extensions: Vec<&'a str>, } -pub fn read_config_file() -> std::result::Result { +fn read_config_file() -> std::result::Result { let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); let filename = root.join(CONFIG_FILE_NAME); if filename.exists() { @@ -260,7 +256,7 @@ where } #[allow(clippy::match_wild_err_arm)] -pub fn get_template_source(tpl_path: &Path) -> std::result::Result { +fn get_template_source(tpl_path: &Path) -> std::result::Result { match fs::read_to_string(tpl_path) { Err(_) => Err(format!( "unable to open template file '{}'", @@ -285,20 +281,20 @@ static DEFAULT_ESCAPERS: &[(&[&str], &str)] = &[ ]; #[derive(Debug, Clone)] -pub struct CompileError { +struct CompileError { msg: Cow<'static, str>, span: Span, } impl CompileError { - pub fn new>>(s: S, span: Span) -> Self { + fn new>>(s: S, span: Span) -> Self { Self { msg: s.into(), span, } } - pub fn into_compile_error(self) -> TokenStream { + fn into_compile_error(self) -> TokenStream { syn::Error::new(self.span, self.msg).to_compile_error() } } diff --git a/askama_shared/src/parser.rs b/askama_shared/src/parser.rs index 900e71ad7..2a7f49f64 100644 --- a/askama_shared/src/parser.rs +++ b/askama_shared/src/parser.rs @@ -13,7 +13,7 @@ use nom::{self, error_position, AsChar, IResult, InputTakeAtPosition}; use crate::{CompileError, Syntax}; #[derive(Debug, PartialEq)] -pub enum Node<'a> { +pub(crate) enum Node<'a> { Lit(&'a str, &'a str, &'a str), Comment(Ws), Expr(Ws, Expr<'a>), @@ -34,19 +34,19 @@ pub enum Node<'a> { } #[derive(Debug, PartialEq)] -pub struct Loop<'a> { - pub ws1: Ws, - pub var: Target<'a>, - pub iter: Expr<'a>, - pub cond: Option>, - pub body: Vec>, - pub ws2: Ws, - pub else_block: Vec>, - pub ws3: Ws, +pub(crate) struct Loop<'a> { + pub(crate) ws1: Ws, + pub(crate) var: Target<'a>, + pub(crate) iter: Expr<'a>, + pub(crate) cond: Option>, + pub(crate) body: Vec>, + pub(crate) ws2: Ws, + pub(crate) else_block: Vec>, + pub(crate) ws3: Ws, } #[derive(Debug, PartialEq)] -pub enum Expr<'a> { +pub(crate) enum Expr<'a> { BoolLit(&'a str), NumLit(&'a str), StrLit(&'a str), @@ -70,7 +70,7 @@ pub enum Expr<'a> { impl Expr<'_> { /// Returns `true` if enough assumptions can be made, /// to determine that `self` is copyable. - pub fn is_copyable(&self) -> bool { + pub(crate) fn is_copyable(&self) -> bool { self.is_copyable_within_op(false) } @@ -98,7 +98,7 @@ impl Expr<'_> { } /// Returns `true` if this is an `Attr` where the `obj` is `"self"`. - pub fn is_attr_self(&self) -> bool { + pub(crate) fn is_attr_self(&self) -> bool { match self { Expr::Attr(obj, _) if matches!(obj.as_ref(), Expr::Var("self")) => true, Expr::Attr(obj, _) if matches!(obj.as_ref(), Expr::Attr(..)) => obj.is_attr_self(), @@ -107,18 +107,18 @@ impl Expr<'_> { } } -pub type When<'a> = (Ws, Target<'a>, Vec>); +pub(crate) type When<'a> = (Ws, Target<'a>, Vec>); #[derive(Debug, PartialEq)] -pub struct Macro<'a> { - pub ws1: Ws, - pub args: Vec<&'a str>, - pub nodes: Vec>, - pub ws2: Ws, +pub(crate) struct Macro<'a> { + pub(crate) ws1: Ws, + pub(crate) args: Vec<&'a str>, + pub(crate) nodes: Vec>, + pub(crate) ws2: Ws, } #[derive(Debug, PartialEq)] -pub enum Target<'a> { +pub(crate) enum Target<'a> { Name(&'a str), Tuple(Vec<&'a str>, Vec>), Struct(Vec<&'a str>, Vec<(&'a str, Target<'a>)>), @@ -130,14 +130,14 @@ pub enum Target<'a> { } #[derive(Clone, Copy, Debug, PartialEq)] -pub struct Ws(pub bool, pub bool); +pub(crate) struct Ws(pub(crate) bool, pub(crate) bool); -pub type Cond<'a> = (Ws, Option>, Vec>); +pub(crate) type Cond<'a> = (Ws, Option>, Vec>); #[derive(Debug, PartialEq)] -pub struct CondTest<'a> { - pub target: Option>, - pub expr: Expr<'a>, +pub(crate) struct CondTest<'a> { + pub(crate) target: Option>, + pub(crate) expr: Expr<'a>, } fn is_ws(c: char) -> bool { @@ -1157,7 +1157,10 @@ fn tag_expr_end<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> { tag(s.syntax.expr_end)(i) } -pub fn parse<'a>(src: &'a str, syntax: &'a Syntax<'a>) -> Result>, CompileError> { +pub(crate) fn parse<'a>( + src: &'a str, + syntax: &'a Syntax<'a>, +) -> Result>, CompileError> { let state = State { syntax, loop_depth: Cell::new(0), From 774c651efa85e14cbef8ac65556a3c8f9e36dc6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Thu, 10 Mar 2022 10:07:49 +0100 Subject: [PATCH 5/6] Update version numbers --- askama/Cargo.toml | 8 ++++---- askama_actix/Cargo.toml | 5 ++--- askama_derive/Cargo.toml | 4 ++-- askama_shared/Cargo.toml | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/askama/Cargo.toml b/askama/Cargo.toml index cdf836bda..eb17c58db 100644 --- a/askama/Cargo.toml +++ b/askama/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "askama" -version = "0.11.1" +version = "0.11.2" description = "Type-safe, compiled Jinja-like templates for Rust" documentation = "https://docs.rs/askama" keywords = ["markup", "template", "jinja2", "html"] @@ -37,9 +37,9 @@ mime = [] mime_guess = [] [dependencies] -askama_derive = { version = "0.11.2", path = "../askama_derive" } -askama_escape = { version = "0.10", path = "../askama_escape" } -askama_shared = { version = "0.12.1", path = "../askama_shared", default-features = false } +askama_derive = { version = "0.12.0", path = "../askama_derive" } +askama_escape = { version = "0.10.3", path = "../askama_escape" } +askama_shared = { version = "0.13.0", path = "../askama_shared", default-features = false } [package.metadata.docs.rs] features = ["config", "humansize", "num-traits", "serde-json", "serde-yaml"] diff --git a/askama_actix/Cargo.toml b/askama_actix/Cargo.toml index ab5c9059a..3b9fc911c 100644 --- a/askama_actix/Cargo.toml +++ b/askama_actix/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "askama_actix" -version = "0.13.0" +version = "0.13.1" description = "Actix-Web integration for Askama templates" documentation = "https://docs.rs/askama" keywords = ["markup", "template", "jinja2", "html"] @@ -14,8 +14,7 @@ edition = "2018" [dependencies] actix-web = { version = "4", default-features = false } -askama = { version = "0.11.1", path = "../askama", default-features = false, features = ["with-actix-web"] } -askama_shared = { version = "0.12.2", path = "../askama_shared" } +askama = { version = "0.11.2", path = "../askama", default-features = false, features = ["with-actix-web"] } [dev-dependencies] actix-rt = { version = "2", default-features = false } diff --git a/askama_derive/Cargo.toml b/askama_derive/Cargo.toml index c1522192c..17526379b 100644 --- a/askama_derive/Cargo.toml +++ b/askama_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "askama_derive" -version = "0.11.2" +version = "0.12.0" description = "Procedural macro package for Askama" homepage = "https://github.com/djc/askama" repository = "https://github.com/djc/askama" @@ -13,4 +13,4 @@ edition = "2018" proc-macro = true [dependencies] -askama_shared = { version = "0.12.1", path = "../askama_shared", default-features = false } +askama_shared = { version = "0.13.0", path = "../askama_shared", default-features = false } diff --git a/askama_shared/Cargo.toml b/askama_shared/Cargo.toml index 9d95b001a..8affe583d 100644 --- a/askama_shared/Cargo.toml +++ b/askama_shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "askama_shared" -version = "0.12.2" +version = "0.13.0" description = "Shared code for Askama" homepage = "https://github.com/djc/askama" repository = "https://github.com/djc/askama" From c8a7ab550c877132fe8452418ebcc9217a908564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Wed, 23 Mar 2022 19:07:52 +0100 Subject: [PATCH 6/6] Merge derive.rs into generator.rs --- askama_shared/src/derive.rs | 106 ------------------------------- askama_shared/src/generator.rs | 110 +++++++++++++++++++++++++++++---- askama_shared/src/lib.rs | 3 +- 3 files changed, 100 insertions(+), 119 deletions(-) delete mode 100644 askama_shared/src/derive.rs diff --git a/askama_shared/src/derive.rs b/askama_shared/src/derive.rs deleted file mode 100644 index b8f0a2f12..000000000 --- a/askama_shared/src/derive.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::collections::HashMap; -use std::path::PathBuf; - -use proc_macro2::TokenStream; - -use crate::heritage::{Context, Heritage}; -use crate::input::{Print, Source, TemplateInput}; -use crate::parser::{parse, Expr, Node}; -use crate::{generator, get_template_source, read_config_file, CompileError, Config}; - -/// The actual implementation for askama_derive::Template -#[doc(hidden)] -pub fn derive_template(input: TokenStream) -> TokenStream { - let ast: syn::DeriveInput = syn::parse2(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 -/// 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. -fn build_template(ast: &syn::DeriveInput) -> Result { - let config_toml = read_config_file()?; - let config = Config::new(&config_toml)?; - let input = TemplateInput::new(ast, &config)?; - let source: String = match input.source { - Source::Source(ref s) => s.clone(), - Source::Path(_) => get_template_source(&input.path)?, - }; - - let mut sources = HashMap::new(); - find_used_templates(&input, &mut sources, source)?; - - let mut parsed = HashMap::new(); - for (path, src) in &sources { - parsed.insert(path.as_path(), parse(src, input.syntax)?); - } - - let mut contexts = HashMap::new(); - for (path, nodes) in &parsed { - contexts.insert(*path, Context::new(input.config, path, 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!("{:?}", parsed[input.path.as_path()]); - } - - let code = generator::generate(&input, &contexts, heritage.as_ref())?; - if input.print == Print::Code || input.print == Print::All { - eprintln!("{}", code); - } - Ok(code) -} - -fn find_used_templates( - input: &TemplateInput<'_>, - 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() { - for n in parse(&source, input.syntax)? { - match n { - Node::Extends(Expr::StrLit(extends)) => { - let extends = input.config.find_template(extends, Some(&path))?; - let dependency_path = (path.clone(), extends.clone()); - if dependency_graph.contains(&dependency_path) { - return Err(format!( - "cyclic dependecy 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, Some(&path))?; - let source = get_template_source(&import)?; - check.push((import, source)); - } - _ => {} - } - } - map.insert(path, source); - } - Ok(()) -} diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs index f7783afc0..c9e482db4 100644 --- a/askama_shared/src/generator.rs +++ b/askama_shared/src/generator.rs @@ -1,24 +1,112 @@ -use super::{get_template_source, CompileError}; -use crate::filters; use crate::heritage::{Context, Heritage}; -use crate::input::{Source, TemplateInput}; +use crate::input::{Print, Source, TemplateInput}; use crate::parser::{parse, Cond, CondTest, Expr, Loop, Node, Target, When, Ws}; +use crate::{filters, get_template_source, read_config_file, CompileError, Config}; +use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use std::collections::HashMap; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::{cmp, hash, mem, str}; -pub(crate) fn generate( - input: &TemplateInput<'_>, - contexts: &HashMap<&Path, Context<'_>, S>, - heritage: Option<&Heritage<'_>>, -) -> Result { - Generator::new(input, contexts, heritage, MapChain::new()) - .build(&contexts[input.path.as_path()]) +/// The actual implementation for askama_derive::Template +#[doc(hidden)] +pub fn derive_template(input: TokenStream) -> TokenStream { + let ast: syn::DeriveInput = syn::parse2(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 +/// 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. +fn build_template(ast: &syn::DeriveInput) -> Result { + let config_toml = read_config_file()?; + let config = Config::new(&config_toml)?; + let input = TemplateInput::new(ast, &config)?; + let source: String = match input.source { + Source::Source(ref s) => s.clone(), + Source::Path(_) => get_template_source(&input.path)?, + }; + + let mut sources = HashMap::new(); + find_used_templates(&input, &mut sources, source)?; + + let mut parsed = HashMap::new(); + for (path, src) in &sources { + parsed.insert(path.as_path(), parse(src, input.syntax)?); + } + + let mut contexts = HashMap::new(); + for (path, nodes) in &parsed { + contexts.insert(*path, Context::new(input.config, path, 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!("{:?}", parsed[input.path.as_path()]); + } + + let code = Generator::new(&input, &contexts, heritage.as_ref(), MapChain::new()) + .build(&contexts[input.path.as_path()])?; + if input.print == Print::Code || input.print == Print::All { + eprintln!("{}", code); + } + Ok(code) } +fn find_used_templates( + input: &TemplateInput<'_>, + 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() { + for n in parse(&source, input.syntax)? { + match n { + Node::Extends(Expr::StrLit(extends)) => { + let extends = input.config.find_template(extends, Some(&path))?; + let dependency_path = (path.clone(), extends.clone()); + if dependency_graph.contains(&dependency_path) { + return Err(format!( + "cyclic dependecy 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, Some(&path))?; + let source = get_template_source(&import)?; + check.push((import, source)); + } + _ => {} + } + } + map.insert(path, source); + } + Ok(()) +} struct Generator<'a, S: std::hash::BuildHasher> { // The template input state: original struct AST and attributes input: &'a TemplateInput<'a>, diff --git a/askama_shared/src/lib.rs b/askama_shared/src/lib.rs index 61ddb0084..81500e12f 100644 --- a/askama_shared/src/lib.rs +++ b/askama_shared/src/lib.rs @@ -13,11 +13,10 @@ use proc_macro2::{Span, TokenStream}; #[cfg(feature = "serde")] use serde::Deserialize; -pub use crate::derive::derive_template; +pub use crate::generator::derive_template; pub use crate::input::extension_to_mime_type; pub use askama_escape::MarkupDisplay; -mod derive; mod error; pub use crate::error::{Error, Result}; pub mod filters;