Merge pull request #253 from Kijewski/pr-refactor-generator

derive: refactor generator for greater re-usability
This commit is contained in:
René Kijewski 2024-11-18 20:31:46 +01:00 committed by GitHub
commit f86feddbb4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 414 additions and 357 deletions

View File

@ -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..]);
}

View 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..]);
}

View File

@ -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}");
}