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}; 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; 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 contexts: &'a HashMap<&'a Path, Context<'a>>, // The heritage contains references to blocks and their ancestry heritage: Option<&'a Heritage<'a>>, // Cache ASTs for included templates includes: HashMap, // 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 // output buffer unless suppressed by whitespace suppression on the next // non-literal. next_ws: Option<&'a str>, // Whitespace suppression from the previous non-literal. Will be used to // determine whether to flush prefix whitespace from the next literal. skip_ws: WhitespaceHandling, // If currently in a block, this will contain the name of a potential parent block super_block: Option<(&'a str, usize)>, // buffer for writable buf_writable: Vec>, // Counter for write! hash named arguments named: usize, } impl<'a> Generator<'a> { pub(crate) fn new<'n>( input: &'n TemplateInput<'_>, contexts: &'n HashMap<&'n Path, Context<'n>>, heritage: Option<&'n Heritage<'_>>, locals: MapChain<'n, &'n str, LocalMeta>, ) -> Generator<'n> { Generator { input, contexts, heritage, includes: HashMap::default(), locals, next_ws: None, skip_ws: WhitespaceHandling::Preserve, super_block: None, buf_writable: vec![], named: 0, } } // Takes a Context and generates the relevant implementations. pub(crate) fn build(mut self, ctx: &'a Context<'_>) -> Result { let mut buf = Buffer::new(0); self.impl_template(ctx, &mut buf)?; self.impl_display(&mut buf)?; #[cfg(feature = "with-actix-web")] self.impl_actix_web_responder(&mut buf)?; #[cfg(feature = "with-axum")] self.impl_axum_into_response(&mut buf)?; #[cfg(feature = "with-gotham")] self.impl_gotham_into_response(&mut buf)?; #[cfg(feature = "with-hyper")] self.impl_hyper_into_response(&mut buf)?; #[cfg(feature = "with-mendes")] self.impl_mendes_responder(&mut buf)?; #[cfg(feature = "with-rocket")] self.impl_rocket_responder(&mut buf)?; #[cfg(feature = "with-tide")] self.impl_tide_integrations(&mut buf)?; #[cfg(feature = "with-warp")] self.impl_warp_reply(&mut buf)?; Ok(buf.buf) } // Implement `Template` for the given context struct. fn impl_template( &mut self, ctx: &'a Context<'_>, buf: &mut Buffer, ) -> Result<(), CompileError> { self.write_header(buf, "::askama::Template", None)?; buf.writeln( "fn render_into(&self, writer: &mut (impl ::std::fmt::Write + ?Sized)) -> \ ::askama::Result<()> {", )?; // Make sure the compiler understands that the generated code depends on the template files. for path in self.contexts.keys() { // Skip the fake path of templates defined in rust source. let path_is_valid = match self.input.source { Source::Path(_) => true, Source::Source(_) => path != &self.input.path, }; if path_is_valid { let path = path.to_str().unwrap(); buf.writeln( "e! { include_bytes!(#path); } .to_string(), )?; } } let size_hint = if let Some(heritage) = self.heritage { self.handle(heritage.root, heritage.root.nodes, buf, AstLevel::Top) } else { self.handle(ctx, ctx.nodes, buf, AstLevel::Top) }?; self.flush_ws(Ws(None, None)); buf.writeln("::askama::Result::Ok(())")?; buf.writeln("}")?; buf.writeln("const EXTENSION: ::std::option::Option<&'static ::std::primitive::str> = ")?; buf.writeln(&format!("{:?}", self.input.extension()))?; buf.writeln(";")?; buf.writeln("const SIZE_HINT: ::std::primitive::usize = ")?; buf.writeln(&format!("{size_hint}"))?; buf.writeln(";")?; buf.writeln("const MIME_TYPE: &'static ::std::primitive::str = ")?; buf.writeln(&format!("{:?}", &self.input.mime_type))?; buf.writeln(";")?; buf.writeln("}")?; Ok(()) } // Implement `Display` for the given context struct. fn impl_display(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { self.write_header(buf, "::std::fmt::Display", None)?; buf.writeln("#[inline]")?; buf.writeln("fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {")?; buf.writeln("::askama::Template::render_into(self, f).map_err(|_| ::std::fmt::Error {})")?; buf.writeln("}")?; buf.writeln("}") } // Implement Actix-web's `Responder`. #[cfg(feature = "with-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;")?; buf.writeln("#[inline]")?; buf.writeln( "fn respond_to(self, _req: &::askama_actix::actix_web::HttpRequest) \ -> ::askama_actix::actix_web::HttpResponse {", )?; buf.writeln("::to_response(&self)")?; buf.writeln("}")?; buf.writeln("}") } // Implement Axum's `IntoResponse`. #[cfg(feature = "with-axum")] fn impl_axum_into_response(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { self.write_header(buf, "::askama_axum::IntoResponse", None)?; buf.writeln("#[inline]")?; buf.writeln( "fn into_response(self)\ -> ::askama_axum::Response {", )?; buf.writeln("::askama_axum::into_response(&self)")?; buf.writeln("}")?; buf.writeln("}") } // Implement gotham's `IntoResponse`. #[cfg(feature = "with-gotham")] fn impl_gotham_into_response(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { self.write_header(buf, "::askama_gotham::IntoResponse", None)?; buf.writeln("#[inline]")?; buf.writeln( "fn into_response(self, _state: &::askama_gotham::State)\ -> ::askama_gotham::Response<::askama_gotham::Body> {", )?; buf.writeln("::askama_gotham::respond(&self)")?; buf.writeln("}")?; buf.writeln("}") } // Implement `From