Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move code generation into askama_derive #687

Merged
merged 1 commit into from
May 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions askama/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,12 @@
#![deny(elided_lifetimes_in_paths)]
#![deny(unreachable_pub)]

pub use askama_derive::*;
pub use askama_derive::Template;
pub use askama_escape::{Html, Text};
pub use askama_shared::{
self as shared, filters, helpers, DynTemplate, Error, MarkupDisplay, Result, Template,
};

#[deprecated(since = "0.11.1", note = "The only function in this mod is deprecated")]
pub mod mime {
#[cfg(all(feature = "mime_guess", feature = "mime"))]
#[deprecated(since = "0.11.1", note = "Use Template::MIME_TYPE instead")]
pub use crate::shared::extension_to_mime_type;
}

/// Old build script helper to rebuild crates if contained templates have changed
///
/// This function is now deprecated and does nothing.
Expand Down
37 changes: 22 additions & 15 deletions askama_derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,27 @@ edition = "2018"
proc-macro = true

[features]
config = ["askama_shared/config"]
humansize = ["askama_shared/humansize"]
markdown = ["askama_shared/markdown"]
urlencode = ["askama_shared/percent-encoding"]
serde-json = ["askama_shared/json"]
serde-yaml = ["askama_shared/yaml"]
num-traits = ["askama_shared/num-traits"]
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"]
config = ["serde", "toml"]
humansize = []
markdown = []
urlencode = []
serde-json = []
serde-yaml = []
num-traits = []
with-actix-web = []
with-axum = []
with-gotham = []
with-mendes = []
with-rocket = []
with-tide = []
with-warp = []

[dependencies]
askama_shared = { version = "0.13.0", path = "../askama_shared", default-features = false }
mime = "0.3"
mime_guess = "2"
nom = "7"
proc-macro2 = "1"
quote = "1"
serde = { version = "1.0", optional = true, features = ["derive"] }
syn = "1"
toml = { version = "0.5", optional = true }
File renamed without changes.
43 changes: 21 additions & 22 deletions askama_shared/src/generator.rs → askama_derive/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@ use crate::config::{get_template_source, read_config_file, Config, WhitespaceHan
use crate::heritage::{Context, Heritage};
use crate::input::{Print, Source, TemplateInput};
use crate::parser::{parse, Cond, CondTest, Expr, Loop, Node, Target, When, Whitespace, Ws};
use crate::{filters, CompileError};
use crate::CompileError;

use proc_macro2::TokenStream;
use proc_macro::TokenStream;
use quote::{quote, ToTokens};

use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::{cmp, hash, mem, str};

/// The actual implementation for askama_derive::Template
#[doc(hidden)]
pub fn derive_template(input: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse2(input).unwrap();
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(),
Expand Down Expand Up @@ -302,19 +301,19 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
self.impl_template(ctx, &mut buf)?;
self.impl_display(&mut buf)?;

#[cfg(feature = "actix-web")]
#[cfg(feature = "with-actix-web")]
self.impl_actix_web_responder(&mut buf)?;
#[cfg(feature = "axum")]
#[cfg(feature = "with-axum")]
self.impl_axum_into_response(&mut buf)?;
#[cfg(feature = "gotham")]
#[cfg(feature = "with-gotham")]
self.impl_gotham_into_response(&mut buf)?;
#[cfg(feature = "mendes")]
#[cfg(feature = "with-mendes")]
self.impl_mendes_responder(&mut buf)?;
#[cfg(feature = "rocket")]
#[cfg(feature = "with-rocket")]
self.impl_rocket_responder(&mut buf)?;
#[cfg(feature = "tide")]
#[cfg(feature = "with-tide")]
self.impl_tide_integrations(&mut buf)?;
#[cfg(feature = "warp")]
#[cfg(feature = "with-warp")]
self.impl_warp_reply(&mut buf)?;

Ok(buf.buf)
Expand Down Expand Up @@ -405,7 +404,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
}

// Implement Actix-web's `Responder`.
#[cfg(feature = "actix-web")]
#[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;")?;
Expand All @@ -420,7 +419,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
}

// Implement Axum's `IntoResponse`.
#[cfg(feature = "axum")]
#[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]")?;
Expand All @@ -435,7 +434,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
}

// Implement gotham's `IntoResponse`.
#[cfg(feature = "gotham")]
#[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]")?;
Expand All @@ -450,7 +449,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
}

// Implement mendes' `Responder`.
#[cfg(feature = "mendes")]
#[cfg(feature = "with-mendes")]
fn impl_mendes_responder(&mut self, buf: &mut Buffer) -> Result<(), CompileError> {
let param = syn::parse_str("A: ::mendes::Application").unwrap();

Expand Down Expand Up @@ -500,7 +499,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
}

// Implement Rocket's `Responder`.
#[cfg(feature = "rocket")]
#[cfg(feature = "with-rocket")]
fn impl_rocket_responder(&mut self, buf: &mut Buffer) -> Result<(), CompileError> {
let lifetime = syn::Lifetime::new("'askama", proc_macro2::Span::call_site());
let param = syn::GenericParam::Lifetime(syn::LifetimeDef::new(lifetime));
Expand All @@ -523,7 +522,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
Ok(())
}

#[cfg(feature = "tide")]
#[cfg(feature = "with-tide")]
fn impl_tide_integrations(&mut self, buf: &mut Buffer) -> Result<(), CompileError> {
let ext = self.input.extension().unwrap_or("txt");

Expand All @@ -549,7 +548,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
buf.writeln("}\n}")
}

#[cfg(feature = "warp")]
#[cfg(feature = "with-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]")?;
Expand Down Expand Up @@ -1381,11 +1380,11 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
name = "json";
}

#[cfg(not(feature = "json"))]
#[cfg(not(feature = "serde-json"))]
if name == "json" {
return Err("the `json` filter requires the `serde-json` feature to be enabled".into());
}
#[cfg(not(feature = "yaml"))]
#[cfg(not(feature = "serde-yaml"))]
if name == "yaml" {
return Err("the `yaml` filter requires the `serde-yaml` feature to be enabled".into());
}
Expand All @@ -1396,7 +1395,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
"::askama::filters::{}({}, ",
name, self.input.escaper
));
} else if filters::BUILT_IN_FILTERS.contains(&name) {
} else if crate::BUILT_IN_FILTERS.contains(&name) {
buf.write(&format!("::askama::filters::{}(", name));
} else {
buf.write(&format!("filters::{}(", name));
Expand Down
File renamed without changes.
3 changes: 1 addition & 2 deletions askama_shared/src/input.rs → askama_derive/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,7 @@ impl Default for Print {
}
}

#[doc(hidden)]
pub fn extension_to_mime_type(ext: &str) -> Mime {
pub(crate) fn extension_to_mime_type(ext: &str) -> Mime {
let basic_type = mime_guess::from_ext(ext).first_or_octet_stream();
for (simple, utf_8) in &TEXT_TYPES {
if &basic_type == simple {
Expand Down
92 changes: 91 additions & 1 deletion askama_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,99 @@
#![deny(elided_lifetimes_in_paths)]
#![deny(unreachable_pub)]

use std::borrow::Cow;
use std::fmt;

use proc_macro::TokenStream;
use proc_macro2::Span;

mod config;
mod generator;
mod heritage;
mod input;
mod parser;

#[proc_macro_derive(Template, attributes(template))]
pub fn derive_template(input: TokenStream) -> TokenStream {
askama_shared::derive_template(input.into()).into()
generator::derive_template(input)
}

#[derive(Debug, Clone)]
struct CompileError {
msg: Cow<'static, str>,
span: Span,
}

impl CompileError {
fn new<S: Into<Cow<'static, str>>>(s: S, span: Span) -> Self {
Self {
msg: s.into(),
span,
}
}

fn into_compile_error(self) -> TokenStream {
syn::Error::new(self.span, self.msg)
.to_compile_error()
.into()
}
}

impl std::error::Error for CompileError {}

impl fmt::Display for CompileError {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.write_str(&self.msg)
}
}

impl From<&'static str> for CompileError {
#[inline]
fn from(s: &'static str) -> Self {
Self::new(s, Span::call_site())
}
}

impl From<String> for CompileError {
#[inline]
fn from(s: String) -> Self {
Self::new(s, Span::call_site())
}
}

// This is used by the code generator to decide whether a named filter is part of
// Askama or should refer to a local `filters` module. It should contain all the
// filters shipped with Askama, even the optional ones (since optional inclusion
// in the const vector based on features seems impossible right now).
const BUILT_IN_FILTERS: &[&str] = &[
"abs",
"capitalize",
"center",
"e",
"escape",
"filesizeformat",
"fmt",
"format",
"indent",
"into_f64",
"into_isize",
"join",
"linebreaks",
"linebreaksbr",
"paragraphbreaks",
"lower",
"lowercase",
"safe",
"trim",
"truncate",
"upper",
"uppercase",
"urlencode",
"urlencode_strict",
"wordcount",
// optional features, reserve the names anyway:
"json",
"markdown",
"yaml",
];
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
36 changes: 0 additions & 36 deletions askama_shared/src/filters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,42 +43,6 @@ const URLENCODE_STRICT_SET: &AsciiSet = &NON_ALPHANUMERIC
// Same as URLENCODE_STRICT_SET, but preserves forward slashes for encoding paths
const URLENCODE_SET: &AsciiSet = &URLENCODE_STRICT_SET.remove(b'/');

// This is used by the code generator to decide whether a named filter is part of
// Askama or should refer to a local `filters` module. It should contain all the
// filters shipped with Askama, even the optional ones (since optional inclusion
// in the const vector based on features seems impossible right now).
pub const BUILT_IN_FILTERS: &[&str] = &[
"abs",
"capitalize",
"center",
"e",
"escape",
"filesizeformat",
"fmt",
"format",
"indent",
"into_f64",
"into_isize",
"join",
"linebreaks",
"linebreaksbr",
"paragraphbreaks",
"lower",
"lowercase",
"safe",
"trim",
"truncate",
"upper",
"uppercase",
"urlencode",
"urlencode_strict",
"wordcount",
// optional features, reserve the names anyway:
"json",
"markdown",
"yaml",
];

/// Marks a string (or other `Display` type) as safe
///
/// Use this is you want to allow markup in an expression, or if you know
Expand Down
Loading