-
-
Notifications
You must be signed in to change notification settings - Fork 219
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
Multiple outputs? #858
Comments
In this case, are the values needed by the subject a subset of the values needed by the body? If so, I'd just use one type for the former and a second type for the latter, which includes the former in one of its fields. At work, we use a procedural macro to duplicate a type for emails that we send with both plaintext and HTML bodies. Usage: #[email(template = "contact-email")]
pub struct ContactEmail<'a> {
pub site_name: &'a str,
pub sender: &'a str,
pub message: &'a str,
pub inbox_url: &'a str,
} Macro: #[proc_macro_attribute]
pub fn email(meta: TokenStream, item: TokenStream) -> TokenStream {
let ast = parse_macro_input!(item as syn::DeriveInput);
let meta = parse_macro_input!(meta as EmailMeta);
// Introduce an `'email` lifetime for the reference to outer type.
// We set up the generics such that all lifetimes used on the item should outlive the
// `'email` lifetime, which is necessary to make some of the impls below work.
let mut email_generics = ast.generics.clone();
let email_lifetime = syn::LifetimeParam::new(syn::Lifetime::new("'email", Span::call_site()));
for lt in email_generics.lifetimes_mut() {
lt.bounds.push(email_lifetime.lifetime.clone());
}
email_generics
.params
.push(syn::GenericParam::Lifetime(email_lifetime));
// Split the generics for use in impls and type definitions, below.
let (_, inner_ty_generics, _) = ast.generics.split_for_impl();
let (impl_generics, ty_generics, where_clause) = email_generics.split_for_impl();
// Set up some bindings for use in the quote!() call below.
let visibility = &ast.vis;
let name = &ast.ident;
let text_type = Ident::new(&format!("{name}Text"), Span::call_site());
let text_template = format!("{}.txt", &meta.template);
let html_type = Ident::new(&format!("{name}Html"), Span::call_site());
let html_template = format!("{}.html", &meta.template);
quote!(
#ast
impl #impl_generics email::BodyTemplates<'email> for #name #inner_ty_generics #where_clause {
type Text = #text_type #ty_generics;
type Html = #html_type #ty_generics;
}
#[derive(askama::Template)]
#[template(path = #text_template)]
#visibility struct #text_type #ty_generics(&'email #name #inner_ty_generics) #where_clause;
impl #impl_generics From<&'email #name #inner_ty_generics> for #text_type #ty_generics #where_clause {
fn from(email: &'email #name #inner_ty_generics) -> Self {
Self(email)
}
}
impl #impl_generics std::ops::Deref for #text_type #ty_generics {
type Target = &'email #name #inner_ty_generics;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(askama::Template)]
#[template(path = #html_template)]
#visibility struct #html_type #ty_generics(&'email #name #inner_ty_generics) #where_clause;
impl #impl_generics std::ops::Deref for #html_type #ty_generics {
type Target = &'email #name #inner_ty_generics;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl #impl_generics From<&'email #name #inner_ty_generics> for #html_type #ty_generics #where_clause {
fn from(email: &'email #name #inner_ty_generics) -> Self {
Self(email)
}
}
).into()
}
struct EmailMeta {
template: String,
}
impl Parse for EmailMeta {
fn parse(input: ParseStream) -> syn::Result<Self> {
for field in Punctuated::<syn::MetaNameValue, Comma>::parse_terminated(input)? {
if field.path.is_ident("template") {
if let syn::Expr::Lit(lit) = &field.value {
if let syn::Lit::Str(lit) = &lit.lit {
return Ok(Self {
template: lit.value(),
});
}
}
}
}
panic!("require template key for email macro");
}
} |
hey is it possible you could post a gist with the whole macro and related traits extracted? I am really curious about the |
Pretty sure that is already the whole macro. pub trait BodyTemplates<'email>: 'email {
type Text: From<&'email Self> + askama::Template;
type Html: From<&'email Self> + askama::Template;
fn text(&'email self) -> Result<String, askama::Error> {
Template::render(&Self::Text::from(self))
}
fn html(&'email self) -> Result<String, askama::Error> {
Template::render(&Self::Html::from(self))
}
} It should be super straightforward to do your own from here? |
I am trying to use askama for generating emails, where both the subject and the body depends on the input variables.
How hard would it be to implement the ability to do something like:
If part is not given, then I guess a renderer could be used by default to remain backwards compatible, also in most cases only one renderer is attributed to a data template.
The text was updated successfully, but these errors were encountered: