mirror of
https://github.com/askama-rs/askama.git
synced 2025-10-02 23:35:07 +00:00
Merge pull request #253 from Kijewski/pr-refactor-generator
derive: refactor generator for greater re-usability
This commit is contained in:
commit
f86feddbb4
@ -1,6 +1,5 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::hash_map::{Entry, HashMap};
|
||||
use std::fmt::{Arguments, Display, Write};
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
@ -14,23 +13,51 @@ use parser::{
|
||||
CharLit, CharPrefix, Expr, Filter, FloatKind, IntKind, Node, Num, StrLit, StrPrefix, Target,
|
||||
WithSpan,
|
||||
};
|
||||
use quote::quote;
|
||||
use rustc_hash::FxBuildHasher;
|
||||
|
||||
use crate::config::WhitespaceHandling;
|
||||
use crate::heritage::{Context, Heritage};
|
||||
use crate::html::write_escaped_str;
|
||||
use crate::input::{Source, TemplateInput};
|
||||
use crate::integration::{Buffer, impl_everything, write_header};
|
||||
use crate::{BUILT_IN_FILTERS, CRATE, CompileError, FileInfo, MsgValidEscapers};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
enum EvaluatedResult {
|
||||
AlwaysTrue,
|
||||
AlwaysFalse,
|
||||
Unknown,
|
||||
pub(crate) fn template_to_string(
|
||||
input: &TemplateInput<'_>,
|
||||
contexts: &HashMap<&Arc<Path>, Context<'_>, FxBuildHasher>,
|
||||
heritage: Option<&Heritage<'_>>,
|
||||
) -> Result<String, CompileError> {
|
||||
let mut buf = Buffer::new();
|
||||
template_into_buffer(input, contexts, heritage, &mut buf, false)?;
|
||||
Ok(buf.into_string())
|
||||
}
|
||||
|
||||
pub(crate) struct Generator<'a> {
|
||||
pub(crate) fn template_into_buffer(
|
||||
input: &TemplateInput<'_>,
|
||||
contexts: &HashMap<&Arc<Path>, Context<'_>, FxBuildHasher>,
|
||||
heritage: Option<&Heritage<'_>>,
|
||||
buf: &mut Buffer,
|
||||
only_template: bool,
|
||||
) -> Result<(), CompileError> {
|
||||
let ctx = &contexts[&input.path];
|
||||
let generator = Generator::new(
|
||||
input,
|
||||
contexts,
|
||||
heritage,
|
||||
MapChain::default(),
|
||||
input.block.is_some(),
|
||||
0,
|
||||
);
|
||||
let mut result = generator.build(ctx, buf, only_template);
|
||||
if let Err(err) = &mut result {
|
||||
if err.span.is_none() {
|
||||
err.span = input.source_span;
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
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
|
||||
@ -55,7 +82,7 @@ pub(crate) struct Generator<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Generator<'a> {
|
||||
pub(crate) fn new<'n>(
|
||||
fn new<'n>(
|
||||
input: &'n TemplateInput<'_>,
|
||||
contexts: &'n HashMap<&'n Arc<Path>, Context<'n>, FxBuildHasher>,
|
||||
heritage: Option<&'n Heritage<'_>>,
|
||||
@ -80,37 +107,28 @@ impl<'a> Generator<'a> {
|
||||
}
|
||||
|
||||
// Takes a Context and generates the relevant implementations.
|
||||
pub(crate) fn build(mut self, ctx: &Context<'a>) -> Result<String, CompileError> {
|
||||
let mut buf = Buffer::new();
|
||||
buf.write(format_args!(
|
||||
"\
|
||||
const _: () = {{\
|
||||
extern crate {CRATE} as rinja;\
|
||||
"
|
||||
));
|
||||
|
||||
if let Err(mut err) = self.impl_template(ctx, &mut buf) {
|
||||
if err.span.is_none() {
|
||||
err.span = self.input.source_span;
|
||||
}
|
||||
return Err(err);
|
||||
fn build(
|
||||
mut self,
|
||||
ctx: &Context<'a>,
|
||||
buf: &mut Buffer,
|
||||
only_template: bool,
|
||||
) -> Result<(), CompileError> {
|
||||
if !only_template {
|
||||
buf.write(format_args!(
|
||||
"\
|
||||
const _: () = {{\
|
||||
extern crate {CRATE} as rinja;\
|
||||
"
|
||||
));
|
||||
}
|
||||
|
||||
self.impl_display(&mut buf);
|
||||
self.impl_fast_writable(&mut buf);
|
||||
self.impl_template(ctx, buf)?;
|
||||
impl_everything(self.input.ast, buf, only_template);
|
||||
|
||||
#[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-rocket")]
|
||||
self.impl_rocket_responder(&mut buf);
|
||||
#[cfg(feature = "with-warp")]
|
||||
self.impl_warp_reply(&mut buf);
|
||||
|
||||
buf.write("};");
|
||||
|
||||
Ok(buf.buf)
|
||||
if !only_template {
|
||||
buf.write("};");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn push_locals<T, F: FnOnce(&mut Self) -> Result<T, CompileError>>(
|
||||
@ -124,8 +142,12 @@ impl<'a> Generator<'a> {
|
||||
}
|
||||
|
||||
// Implement `Template` for the given context struct.
|
||||
fn impl_template(&mut self, ctx: &Context<'a>, buf: &mut Buffer) -> Result<(), CompileError> {
|
||||
self.write_header(buf, "rinja::Template", None);
|
||||
fn impl_template(
|
||||
&mut self,
|
||||
ctx: &Context<'a>,
|
||||
buf: &mut Buffer,
|
||||
) -> Result<usize, CompileError> {
|
||||
write_header(self.input.ast, buf, "rinja::Template", None);
|
||||
buf.write(
|
||||
"fn render_into<RinjaW>(&self, writer: &mut RinjaW) -> rinja::Result<()>\
|
||||
where \
|
||||
@ -177,147 +199,7 @@ impl<'a> Generator<'a> {
|
||||
));
|
||||
|
||||
buf.write('}');
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Implement `Display` for the given context struct.
|
||||
fn impl_display(&mut self, buf: &mut Buffer) {
|
||||
let ident = &self.input.ast.ident;
|
||||
buf.write(format_args!(
|
||||
"\
|
||||
/// Implement the [`format!()`][rinja::helpers::std::format] trait for [`{}`]\n\
|
||||
///\n\
|
||||
/// Please be aware of the rendering performance notice in the \
|
||||
[`Template`][rinja::Template] trait.\n\
|
||||
",
|
||||
quote!(#ident),
|
||||
));
|
||||
self.write_header(buf, "rinja::helpers::core::fmt::Display", None);
|
||||
buf.write(
|
||||
"\
|
||||
#[inline]\
|
||||
fn fmt(\
|
||||
&self,\
|
||||
f: &mut rinja::helpers::core::fmt::Formatter<'_>\
|
||||
) -> rinja::helpers::core::fmt::Result {\
|
||||
rinja::Template::render_into(self, f)\
|
||||
.map_err(|_| rinja::helpers::core::fmt::Error)\
|
||||
}\
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
// Implement `FastWritable` for the given context struct.
|
||||
fn impl_fast_writable(&mut self, buf: &mut Buffer) {
|
||||
self.write_header(buf, "rinja::filters::FastWritable", None);
|
||||
buf.write(
|
||||
"\
|
||||
#[inline]\
|
||||
fn write_into<RinjaW>(&self, dest: &mut RinjaW) -> rinja::Result<()> \
|
||||
where \
|
||||
RinjaW: rinja::helpers::core::fmt::Write + ?rinja::helpers::core::marker::Sized,\
|
||||
{\
|
||||
rinja::Template::render_into(self, dest)\
|
||||
}\
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
// Implement Actix-web's `Responder`.
|
||||
#[cfg(feature = "with-actix-web")]
|
||||
fn impl_actix_web_responder(&mut self, buf: &mut Buffer) {
|
||||
self.write_header(buf, "::rinja_actix::actix_web::Responder", None);
|
||||
buf.write(
|
||||
"\
|
||||
type Body = ::rinja_actix::actix_web::body::BoxBody;\
|
||||
#[inline]\
|
||||
fn respond_to(self, _req: &::rinja_actix::actix_web::HttpRequest)\
|
||||
-> ::rinja_actix::actix_web::HttpResponse<Self::Body> {\
|
||||
::rinja_actix::into_response(&self)\
|
||||
}\
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
// Implement Axum's `IntoResponse`.
|
||||
#[cfg(feature = "with-axum")]
|
||||
fn impl_axum_into_response(&mut self, buf: &mut Buffer) {
|
||||
self.write_header(buf, "::rinja_axum::axum_core::response::IntoResponse", None);
|
||||
buf.write(
|
||||
"\
|
||||
#[inline]\
|
||||
fn into_response(self) -> ::rinja_axum::axum_core::response::Response {\
|
||||
::rinja_axum::into_response(&self)\
|
||||
}\
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
// Implement Rocket's `Responder`.
|
||||
#[cfg(feature = "with-rocket")]
|
||||
fn impl_rocket_responder(&mut self, buf: &mut Buffer) {
|
||||
let lifetime1 = syn::Lifetime::new("'rinja1", proc_macro2::Span::call_site());
|
||||
let param1 = syn::GenericParam::Lifetime(syn::LifetimeParam::new(lifetime1));
|
||||
|
||||
self.write_header(
|
||||
buf,
|
||||
"::rinja_rocket::rocket::response::Responder<'rinja1, 'static>",
|
||||
Some(vec![param1]),
|
||||
);
|
||||
buf.write(
|
||||
"\
|
||||
#[inline]\
|
||||
fn respond_to(self, _: &'rinja1 ::rinja_rocket::rocket::request::Request<'_>)\
|
||||
-> ::rinja_rocket::rocket::response::Result<'static>\
|
||||
{\
|
||||
::rinja_rocket::respond(&self)\
|
||||
}\
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "with-warp")]
|
||||
fn impl_warp_reply(&mut self, buf: &mut Buffer) {
|
||||
self.write_header(buf, "::rinja_warp::warp::reply::Reply", None);
|
||||
buf.write(
|
||||
"\
|
||||
#[inline]\
|
||||
fn into_response(self) -> ::rinja_warp::warp::reply::Response {\
|
||||
::rinja_warp::into_response(&self)\
|
||||
}\
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
// Writes header for the `impl` for `TraitFromPathName` or `Template`
|
||||
// for the given context struct.
|
||||
fn write_header(
|
||||
&mut self,
|
||||
buf: &mut Buffer,
|
||||
target: impl Display,
|
||||
params: Option<Vec<syn::GenericParam>>,
|
||||
) {
|
||||
let mut generics;
|
||||
let (impl_generics, orig_ty_generics, where_clause) = if let Some(params) = params {
|
||||
generics = self.input.ast.generics.clone();
|
||||
for param in params {
|
||||
generics.params.push(param);
|
||||
}
|
||||
|
||||
let (_, orig_ty_generics, _) = self.input.ast.generics.split_for_impl();
|
||||
let (impl_generics, _, where_clause) = generics.split_for_impl();
|
||||
(impl_generics, orig_ty_generics, where_clause)
|
||||
} else {
|
||||
self.input.ast.generics.split_for_impl()
|
||||
};
|
||||
|
||||
let ident = &self.input.ast.ident;
|
||||
buf.write(format_args!(
|
||||
"impl {} {} for {} {{",
|
||||
quote!(#impl_generics),
|
||||
target,
|
||||
quote!(#ident #orig_ty_generics #where_clause),
|
||||
));
|
||||
Ok(size_hint)
|
||||
}
|
||||
|
||||
// Helper methods for handling node types
|
||||
@ -640,7 +522,7 @@ impl<'a> Generator<'a> {
|
||||
this.visit_target(buf, true, true, target);
|
||||
}
|
||||
}
|
||||
buf.write(format_args!("= &{} {{", expr_buf.buf));
|
||||
buf.write(format_args!("= &{expr_buf} {{"));
|
||||
} else if cond_info.generate_condition {
|
||||
this.visit_condition(ctx, buf, expr)?;
|
||||
buf.write('{');
|
||||
@ -949,7 +831,8 @@ impl<'a> Generator<'a> {
|
||||
let mut attr_buf = Buffer::new();
|
||||
this.visit_attr(ctx, &mut attr_buf, obj, attr)?;
|
||||
|
||||
let var = this.locals.resolve(&attr_buf.buf).unwrap_or(attr_buf.buf);
|
||||
let attr = attr_buf.into_string();
|
||||
let var = this.locals.resolve(&attr).unwrap_or(attr);
|
||||
this.locals
|
||||
.insert(Cow::Borrowed(arg), LocalMeta::with_ref(var));
|
||||
}
|
||||
@ -965,7 +848,7 @@ impl<'a> Generator<'a> {
|
||||
("", "")
|
||||
};
|
||||
value.write(this.visit_expr_root(ctx, expr)?);
|
||||
buf.write(format_args!("let {arg} = {before}{}{after};", value.buf));
|
||||
buf.write(format_args!("let {arg} = {before}{value}{after};"));
|
||||
this.locals.insert_with_default(Cow::Borrowed(arg));
|
||||
}
|
||||
}
|
||||
@ -1022,10 +905,10 @@ impl<'a> Generator<'a> {
|
||||
filter,
|
||||
)?;
|
||||
let filter_buf = match display_wrap {
|
||||
DisplayWrap::Wrapped => filter_buf.buf,
|
||||
DisplayWrap::Wrapped => filter_buf.into_string(),
|
||||
DisplayWrap::Unwrapped => format!(
|
||||
"(&&rinja::filters::AutoEscaper::new(&({}), {})).rinja_auto_escape()?",
|
||||
filter_buf.buf, self.input.escaper,
|
||||
"(&&rinja::filters::AutoEscaper::new(&({filter_buf}), {})).rinja_auto_escape()?",
|
||||
self.input.escaper,
|
||||
),
|
||||
};
|
||||
buf.write(format_args!(
|
||||
@ -1177,7 +1060,7 @@ impl<'a> Generator<'a> {
|
||||
} else {
|
||||
("", "")
|
||||
};
|
||||
buf.write(format_args!(" = {before}{}{after};", &expr_buf.buf));
|
||||
buf.write(format_args!(" = {before}{expr_buf}{after};"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1361,11 +1244,11 @@ impl<'a> Generator<'a> {
|
||||
|
||||
let mut expr_buf = Buffer::new();
|
||||
let expr = match self.visit_expr(ctx, &mut expr_buf, s)? {
|
||||
DisplayWrap::Wrapped => expr_buf.buf,
|
||||
DisplayWrap::Wrapped => expr_buf.into_string(),
|
||||
DisplayWrap::Unwrapped => format!(
|
||||
"(&&rinja::filters::AutoEscaper::new(&({}), {})).\
|
||||
"(&&rinja::filters::AutoEscaper::new(&({expr_buf}), {})).\
|
||||
rinja_auto_escape()?",
|
||||
expr_buf.buf, self.input.escaper,
|
||||
self.input.escaper,
|
||||
),
|
||||
};
|
||||
let idx = if is_cacheable(s) {
|
||||
@ -1391,11 +1274,10 @@ impl<'a> Generator<'a> {
|
||||
}
|
||||
buf.write(format_args!(
|
||||
") {{\
|
||||
({}) => {{\
|
||||
{}\
|
||||
({targets}) => {{\
|
||||
{lines}\
|
||||
}}\
|
||||
}}",
|
||||
targets.buf, lines.buf,
|
||||
}}"
|
||||
));
|
||||
|
||||
for s in trailing_simple_lines {
|
||||
@ -1452,7 +1334,7 @@ impl<'a> Generator<'a> {
|
||||
) -> Result<String, CompileError> {
|
||||
let mut buf = Buffer::new();
|
||||
self.visit_expr(ctx, &mut buf, expr)?;
|
||||
Ok(buf.buf)
|
||||
Ok(buf.into_string())
|
||||
}
|
||||
|
||||
fn visit_expr(
|
||||
@ -2355,7 +2237,7 @@ impl<'a> Generator<'a> {
|
||||
}
|
||||
},
|
||||
Target::Tuple(path, targets) => {
|
||||
buf.write(SeparatedPath(path));
|
||||
buf.write_separated_path(path);
|
||||
buf.write('(');
|
||||
for target in targets {
|
||||
self.visit_target(buf, initialized, false, target);
|
||||
@ -2364,7 +2246,7 @@ impl<'a> Generator<'a> {
|
||||
buf.write(')');
|
||||
}
|
||||
Target::Array(path, targets) => {
|
||||
buf.write(SeparatedPath(path));
|
||||
buf.write_separated_path(path);
|
||||
buf.write('[');
|
||||
for target in targets {
|
||||
self.visit_target(buf, initialized, false, target);
|
||||
@ -2373,7 +2255,7 @@ impl<'a> Generator<'a> {
|
||||
buf.write(']');
|
||||
}
|
||||
Target::Struct(path, targets) => {
|
||||
buf.write(SeparatedPath(path));
|
||||
buf.write_separated_path(path);
|
||||
buf.write('{');
|
||||
for (name, target) in targets {
|
||||
if let Target::Rest(_) = target {
|
||||
@ -2566,7 +2448,7 @@ fn expr_is_int_lit_plus_minus_one(expr: &WithSpan<'_, Expr<'_>>) -> Option<bool>
|
||||
Some(IntKind::$svar) => is_signed_singular($sty::from_str_radix, $value, 1, -1),
|
||||
)*
|
||||
$(
|
||||
Some(IntKind::$uvar) => is_unsigned_singular($sty::from_str_radix, $value, 1),
|
||||
Some(IntKind::$uvar) => is_unsigned_singular($uty::from_str_radix, $value, 1),
|
||||
)*
|
||||
None => match $value.starts_with('-') {
|
||||
true => is_signed_singular(i128::from_str_radix, $value, 1, -1),
|
||||
@ -2714,105 +2596,6 @@ fn compile_time_escape<'a>(expr: &Expr<'a>, escaper: &str) -> Option<Writable<'a
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Buffer {
|
||||
// The buffer to generate the code into
|
||||
buf: String,
|
||||
discard: bool,
|
||||
last_was_write_str: bool,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
buf: String::new(),
|
||||
discard: false,
|
||||
last_was_write_str: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_discard(&self) -> bool {
|
||||
self.discard
|
||||
}
|
||||
|
||||
fn set_discard(&mut self, discard: bool) {
|
||||
self.discard = discard;
|
||||
self.last_was_write_str = false;
|
||||
}
|
||||
|
||||
fn write(&mut self, src: impl BufferFmt) {
|
||||
if !self.discard {
|
||||
src.append_to(&mut self.buf);
|
||||
self.last_was_write_str = false;
|
||||
}
|
||||
}
|
||||
|
||||
fn write_escaped_str(&mut self, s: &str) {
|
||||
if !self.discard {
|
||||
self.buf.push('"');
|
||||
string_escape(&mut self.buf, s);
|
||||
self.buf.push('"');
|
||||
}
|
||||
}
|
||||
|
||||
fn write_writer(&mut self, s: &str) -> usize {
|
||||
const OPEN: &str = r#"writer.write_str(""#;
|
||||
const CLOSE: &str = r#"")?;"#;
|
||||
|
||||
if !s.is_empty() && !self.discard {
|
||||
if !self.last_was_write_str {
|
||||
self.last_was_write_str = true;
|
||||
self.buf.push_str(OPEN);
|
||||
} else {
|
||||
// strip trailing `")?;`, leaving an unterminated string
|
||||
self.buf.truncate(self.buf.len() - CLOSE.len());
|
||||
}
|
||||
string_escape(&mut self.buf, s);
|
||||
self.buf.push_str(CLOSE);
|
||||
}
|
||||
s.len()
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.buf.clear();
|
||||
self.last_was_write_str = false;
|
||||
}
|
||||
}
|
||||
|
||||
trait BufferFmt {
|
||||
fn append_to(&self, buf: &mut String);
|
||||
}
|
||||
|
||||
impl<T: BufferFmt + ?Sized> BufferFmt for &T {
|
||||
fn append_to(&self, buf: &mut String) {
|
||||
T::append_to(self, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferFmt for char {
|
||||
fn append_to(&self, buf: &mut String) {
|
||||
buf.push(*self);
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferFmt for str {
|
||||
fn append_to(&self, buf: &mut String) {
|
||||
buf.push_str(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferFmt for String {
|
||||
fn append_to(&self, buf: &mut String) {
|
||||
buf.push_str(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferFmt for Arguments<'_> {
|
||||
fn append_to(&self, buf: &mut String) {
|
||||
buf.write_fmt(*self).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
struct CondInfo<'a> {
|
||||
cond: &'a WithSpan<'a, Cond<'a>>,
|
||||
cond_expr: Option<WithSpan<'a, Expr<'a>>>,
|
||||
@ -2827,6 +2610,13 @@ struct Conds<'a> {
|
||||
nb_conds: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
enum EvaluatedResult {
|
||||
AlwaysTrue,
|
||||
AlwaysFalse,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl<'a> Conds<'a> {
|
||||
fn compute_branches(generator: &Generator<'a>, i: &'a If<'a>) -> Self {
|
||||
let mut conds = Vec::with_capacity(i.branches.len());
|
||||
@ -2929,21 +2719,8 @@ impl<'a> Conds<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
struct SeparatedPath<I>(I);
|
||||
|
||||
impl<I: IntoIterator<Item = E> + Copy, E: BufferFmt> BufferFmt for SeparatedPath<I> {
|
||||
fn append_to(&self, buf: &mut String) {
|
||||
for (idx, item) in self.0.into_iter().enumerate() {
|
||||
if idx > 0 {
|
||||
buf.push_str("::");
|
||||
}
|
||||
item.append_to(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct LocalMeta {
|
||||
struct LocalMeta {
|
||||
refs: Option<String>,
|
||||
initialized: bool,
|
||||
}
|
||||
@ -2965,7 +2742,7 @@ impl LocalMeta {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct MapChain<'a, K, V>
|
||||
struct MapChain<'a, K, V>
|
||||
where
|
||||
K: cmp::Eq + hash::Hash,
|
||||
{
|
||||
@ -3069,7 +2846,7 @@ fn is_copyable_within_op(expr: &Expr<'_>, within_op: bool) -> bool {
|
||||
}
|
||||
|
||||
/// Returns `true` if this is an `Attr` where the `obj` is `"self"`.
|
||||
pub(crate) fn is_attr_self(mut expr: &Expr<'_>) -> bool {
|
||||
fn is_attr_self(mut expr: &Expr<'_>) -> bool {
|
||||
loop {
|
||||
match expr {
|
||||
Expr::Attr(obj, _) if matches!(***obj, Expr::Var("self")) => return true,
|
||||
@ -3082,7 +2859,7 @@ pub(crate) fn is_attr_self(mut expr: &Expr<'_>) -> bool {
|
||||
/// Returns `true` if the outcome of this expression may be used multiple times in the same
|
||||
/// `write!()` call, without evaluating the expression again, i.e. the expression should be
|
||||
/// side-effect free.
|
||||
pub(crate) fn is_cacheable(expr: &WithSpan<'_, Expr<'_>>) -> bool {
|
||||
fn is_cacheable(expr: &WithSpan<'_, Expr<'_>>) -> bool {
|
||||
match &**expr {
|
||||
// Literals are the definition of pure:
|
||||
Expr::BoolLit(_) => true,
|
||||
@ -3209,26 +2986,3 @@ fn normalize_identifier(ident: &str) -> &str {
|
||||
// SAFETY: We know that the input byte slice is pure-ASCII.
|
||||
unsafe { std::str::from_utf8_unchecked(&replacement[..ident.len() + 2]) }
|
||||
}
|
||||
|
||||
/// Similar to `write!(dest, "{src:?}")`, but only escapes the strictly needed characters,
|
||||
/// and without the surrounding `"…"` quotation marks.
|
||||
pub(crate) fn string_escape(dest: &mut String, src: &str) {
|
||||
// SAFETY: we will only push valid str slices
|
||||
let dest = unsafe { dest.as_mut_vec() };
|
||||
let src = src.as_bytes();
|
||||
let mut last = 0;
|
||||
|
||||
// According to <https://doc.rust-lang.org/reference/tokens.html#string-literals>, every
|
||||
// character is valid except `" \ IsolatedCR`. We don't test if the `\r` is isolated or not,
|
||||
// but always escape it.
|
||||
for x in memchr::memchr3_iter(b'\\', b'"', b'\r', src) {
|
||||
dest.extend(&src[last..x]);
|
||||
dest.extend(match src[x] {
|
||||
b'\\' => br"\\",
|
||||
b'\"' => br#"\""#,
|
||||
_ => br"\r",
|
||||
});
|
||||
last = x + 1;
|
||||
}
|
||||
dest.extend(&src[last..]);
|
||||
}
|
||||
|
318
rinja_derive/src/integration.rs
Normal file
318
rinja_derive/src/integration.rs
Normal file
@ -0,0 +1,318 @@
|
||||
use std::fmt::{Arguments, Display, Write};
|
||||
|
||||
use quote::quote;
|
||||
use syn::DeriveInput;
|
||||
|
||||
/// Implement every integration for the given item
|
||||
pub(crate) fn impl_everything(ast: &DeriveInput, buf: &mut Buffer, only_template: bool) {
|
||||
impl_display(ast, buf);
|
||||
impl_fast_writable(ast, buf);
|
||||
|
||||
if !only_template {
|
||||
#[cfg(feature = "with-actix-web")]
|
||||
impl_actix_web_responder(ast, buf);
|
||||
#[cfg(feature = "with-axum")]
|
||||
impl_axum_into_response(ast, buf);
|
||||
#[cfg(feature = "with-rocket")]
|
||||
impl_rocket_responder(ast, buf);
|
||||
#[cfg(feature = "with-warp")]
|
||||
impl_warp_reply(ast, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes header for the `impl` for `TraitFromPathName` or `Template` for the given item
|
||||
pub(crate) fn write_header(
|
||||
ast: &DeriveInput,
|
||||
buf: &mut Buffer,
|
||||
target: impl Display,
|
||||
params: Option<Vec<syn::GenericParam>>,
|
||||
) {
|
||||
let mut generics;
|
||||
let (impl_generics, orig_ty_generics, where_clause) = if let Some(params) = params {
|
||||
generics = ast.generics.clone();
|
||||
for param in params {
|
||||
generics.params.push(param);
|
||||
}
|
||||
|
||||
let (_, orig_ty_generics, _) = ast.generics.split_for_impl();
|
||||
let (impl_generics, _, where_clause) = generics.split_for_impl();
|
||||
(impl_generics, orig_ty_generics, where_clause)
|
||||
} else {
|
||||
ast.generics.split_for_impl()
|
||||
};
|
||||
|
||||
let ident = &ast.ident;
|
||||
buf.write(format_args!(
|
||||
"impl {} {} for {} {{",
|
||||
quote!(#impl_generics),
|
||||
target,
|
||||
quote!(#ident #orig_ty_generics #where_clause),
|
||||
));
|
||||
}
|
||||
|
||||
/// Implement `Display` for the given item.
|
||||
fn impl_display(ast: &DeriveInput, buf: &mut Buffer) {
|
||||
let ident = &ast.ident;
|
||||
buf.write(format_args!(
|
||||
"\
|
||||
/// Implement the [`format!()`][rinja::helpers::std::format] trait for [`{}`]\n\
|
||||
///\n\
|
||||
/// Please be aware of the rendering performance notice in the \
|
||||
[`Template`][rinja::Template] trait.\n\
|
||||
",
|
||||
quote!(#ident),
|
||||
));
|
||||
write_header(ast, buf, "rinja::helpers::core::fmt::Display", None);
|
||||
buf.write(
|
||||
"\
|
||||
#[inline]\
|
||||
fn fmt(\
|
||||
&self,\
|
||||
f: &mut rinja::helpers::core::fmt::Formatter<'_>\
|
||||
) -> rinja::helpers::core::fmt::Result {\
|
||||
rinja::Template::render_into(self, f)\
|
||||
.map_err(|_| rinja::helpers::core::fmt::Error)\
|
||||
}\
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
/// Implement `FastWritable` for the given item.
|
||||
fn impl_fast_writable(ast: &DeriveInput, buf: &mut Buffer) {
|
||||
write_header(ast, buf, "rinja::filters::FastWritable", None);
|
||||
buf.write(
|
||||
"\
|
||||
#[inline]\
|
||||
fn write_into<RinjaW>(&self, dest: &mut RinjaW) -> rinja::Result<()> \
|
||||
where \
|
||||
RinjaW: rinja::helpers::core::fmt::Write + ?rinja::helpers::core::marker::Sized,\
|
||||
{\
|
||||
rinja::Template::render_into(self, dest)\
|
||||
}\
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
/// Implement Actix-web's `Responder`.
|
||||
#[cfg(feature = "with-actix-web")]
|
||||
fn impl_actix_web_responder(ast: &DeriveInput, buf: &mut Buffer) {
|
||||
write_header(ast, buf, "::rinja_actix::actix_web::Responder", None);
|
||||
buf.write(
|
||||
"\
|
||||
type Body = ::rinja_actix::actix_web::body::BoxBody;\
|
||||
#[inline]\
|
||||
fn respond_to(self, _req: &::rinja_actix::actix_web::HttpRequest)\
|
||||
-> ::rinja_actix::actix_web::HttpResponse<Self::Body> {\
|
||||
::rinja_actix::into_response(&self)\
|
||||
}\
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
/// Implement Axum's `IntoResponse`.
|
||||
#[cfg(feature = "with-axum")]
|
||||
fn impl_axum_into_response(ast: &DeriveInput, buf: &mut Buffer) {
|
||||
write_header(
|
||||
ast,
|
||||
buf,
|
||||
"::rinja_axum::axum_core::response::IntoResponse",
|
||||
None,
|
||||
);
|
||||
buf.write(
|
||||
"\
|
||||
#[inline]\
|
||||
fn into_response(self) -> ::rinja_axum::axum_core::response::Response {\
|
||||
::rinja_axum::into_response(&self)\
|
||||
}\
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
/// Implement Rocket's `Responder`.
|
||||
#[cfg(feature = "with-rocket")]
|
||||
fn impl_rocket_responder(ast: &DeriveInput, buf: &mut Buffer) {
|
||||
let lifetime1 = syn::Lifetime::new("'rinja1", proc_macro2::Span::call_site());
|
||||
let param1 = syn::GenericParam::Lifetime(syn::LifetimeParam::new(lifetime1));
|
||||
|
||||
write_header(
|
||||
ast,
|
||||
buf,
|
||||
"::rinja_rocket::rocket::response::Responder<'rinja1, 'static>",
|
||||
Some(vec![param1]),
|
||||
);
|
||||
buf.write(
|
||||
"\
|
||||
#[inline]\
|
||||
fn respond_to(self, _: &'rinja1 ::rinja_rocket::rocket::request::Request<'_>)\
|
||||
-> ::rinja_rocket::rocket::response::Result<'static>\
|
||||
{\
|
||||
::rinja_rocket::respond(&self)\
|
||||
}\
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
/// Implement Warp's `Reply`.
|
||||
#[cfg(feature = "with-warp")]
|
||||
fn impl_warp_reply(ast: &DeriveInput, buf: &mut Buffer) {
|
||||
write_header(ast, buf, "::rinja_warp::warp::reply::Reply", None);
|
||||
buf.write(
|
||||
"\
|
||||
#[inline]\
|
||||
fn into_response(self) -> ::rinja_warp::warp::reply::Response {\
|
||||
::rinja_warp::into_response(&self)\
|
||||
}\
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Buffer {
|
||||
// The buffer to generate the code into
|
||||
buf: String,
|
||||
discard: bool,
|
||||
last_was_write_str: bool,
|
||||
}
|
||||
|
||||
impl Display for Buffer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&self.buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
buf: String::new(),
|
||||
discard: false,
|
||||
last_was_write_str: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn into_string(self) -> String {
|
||||
self.buf
|
||||
}
|
||||
|
||||
pub(crate) fn is_discard(&self) -> bool {
|
||||
self.discard
|
||||
}
|
||||
|
||||
pub(crate) fn set_discard(&mut self, discard: bool) {
|
||||
self.discard = discard;
|
||||
self.last_was_write_str = false;
|
||||
}
|
||||
|
||||
pub(crate) fn write(&mut self, src: impl BufferFmt) {
|
||||
if self.discard {
|
||||
return;
|
||||
}
|
||||
self.last_was_write_str = false;
|
||||
|
||||
src.append_to(&mut self.buf);
|
||||
}
|
||||
|
||||
pub(crate) fn write_separated_path(&mut self, path: &[&str]) {
|
||||
if self.discard {
|
||||
return;
|
||||
}
|
||||
self.last_was_write_str = false;
|
||||
|
||||
for (idx, item) in path.iter().enumerate() {
|
||||
if idx > 0 {
|
||||
self.buf.push_str("::");
|
||||
}
|
||||
self.buf.push_str(item);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn write_escaped_str(&mut self, s: &str) {
|
||||
if self.discard {
|
||||
return;
|
||||
}
|
||||
self.last_was_write_str = false;
|
||||
|
||||
self.buf.push('"');
|
||||
string_escape(&mut self.buf, s);
|
||||
self.buf.push('"');
|
||||
}
|
||||
|
||||
pub(crate) fn write_writer(&mut self, s: &str) -> usize {
|
||||
const OPEN: &str = r#"writer.write_str(""#;
|
||||
const CLOSE: &str = r#"")?;"#;
|
||||
|
||||
if !s.is_empty() && !self.discard {
|
||||
if !self.last_was_write_str {
|
||||
self.last_was_write_str = true;
|
||||
self.buf.push_str(OPEN);
|
||||
} else {
|
||||
// strip trailing `")?;`, leaving an unterminated string
|
||||
self.buf.truncate(self.buf.len() - CLOSE.len());
|
||||
}
|
||||
string_escape(&mut self.buf, s);
|
||||
self.buf.push_str(CLOSE);
|
||||
}
|
||||
s.len()
|
||||
}
|
||||
|
||||
pub(crate) fn clear(&mut self) {
|
||||
self.buf.clear();
|
||||
self.last_was_write_str = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait BufferFmt {
|
||||
fn append_to(&self, buf: &mut String);
|
||||
}
|
||||
|
||||
impl<T: BufferFmt + ?Sized> BufferFmt for &T {
|
||||
fn append_to(&self, buf: &mut String) {
|
||||
T::append_to(self, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferFmt for char {
|
||||
fn append_to(&self, buf: &mut String) {
|
||||
buf.push(*self);
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferFmt for str {
|
||||
fn append_to(&self, buf: &mut String) {
|
||||
buf.push_str(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferFmt for String {
|
||||
fn append_to(&self, buf: &mut String) {
|
||||
buf.push_str(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferFmt for Arguments<'_> {
|
||||
fn append_to(&self, buf: &mut String) {
|
||||
buf.write_fmt(*self).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Similar to `write!(dest, "{src:?}")`, but only escapes the strictly needed characters,
|
||||
/// and without the surrounding `"…"` quotation marks.
|
||||
fn string_escape(dest: &mut String, src: &str) {
|
||||
// SAFETY: we will only push valid str slices
|
||||
let dest = unsafe { dest.as_mut_vec() };
|
||||
let src = src.as_bytes();
|
||||
let mut last = 0;
|
||||
|
||||
// According to <https://doc.rust-lang.org/reference/tokens.html#string-literals>, every
|
||||
// character is valid except `" \ IsolatedCR`. We don't test if the `\r` is isolated or not,
|
||||
// but always escape it.
|
||||
for x in memchr::memchr3_iter(b'\\', b'"', b'\r', src) {
|
||||
dest.extend(&src[last..x]);
|
||||
dest.extend(match src[x] {
|
||||
b'\\' => br"\\",
|
||||
b'\"' => br#"\""#,
|
||||
_ => br"\r",
|
||||
});
|
||||
last = x + 1;
|
||||
}
|
||||
dest.extend(&src[last..]);
|
||||
}
|
@ -7,6 +7,7 @@ mod generator;
|
||||
mod heritage;
|
||||
mod html;
|
||||
mod input;
|
||||
mod integration;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
@ -18,7 +19,7 @@ use std::path::Path;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use config::{Config, read_config_file};
|
||||
use generator::{Generator, MapChain};
|
||||
use generator::template_to_string;
|
||||
use heritage::{Context, Heritage};
|
||||
use input::{Print, TemplateArgs, TemplateInput};
|
||||
use parser::{Parsed, WithSpan, strip_common};
|
||||
@ -160,15 +161,7 @@ fn build_skeleton(ast: &syn::DeriveInput) -> Result<String, CompileError> {
|
||||
let mut contexts = HashMap::default();
|
||||
let parsed = parser::Parsed::default();
|
||||
contexts.insert(&input.path, Context::empty(&parsed));
|
||||
Generator::new(
|
||||
&input,
|
||||
&contexts,
|
||||
None,
|
||||
MapChain::default(),
|
||||
input.block.is_some(),
|
||||
0,
|
||||
)
|
||||
.build(&contexts[&input.path])
|
||||
template_to_string(&input, &contexts, None)
|
||||
}
|
||||
|
||||
/// Takes a `syn::DeriveInput` and generates source code for it
|
||||
@ -233,15 +226,7 @@ fn build_template_inner(
|
||||
eprintln!("{:?}", templates[&input.path].nodes());
|
||||
}
|
||||
|
||||
let code = Generator::new(
|
||||
&input,
|
||||
&contexts,
|
||||
heritage.as_ref(),
|
||||
MapChain::default(),
|
||||
input.block.is_some(),
|
||||
0,
|
||||
)
|
||||
.build(&contexts[&input.path])?;
|
||||
let code = template_to_string(&input, &contexts, heritage.as_ref())?;
|
||||
if input.print == Print::Code || input.print == Print::All {
|
||||
eprintln!("{code}");
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user