Skip to content

Commit 77e846f

Browse files
Merge pull request #337 from Kijewski/pr-blocks
derive: implement template attribute `blocks`
2 parents c130590 + 137aaa0 commit 77e846f

File tree

11 files changed

+385
-32
lines changed

11 files changed

+385
-32
lines changed

rinja/Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,15 @@ maintenance = { status = "actively-developed" }
4444

4545
[features]
4646
default = ["config", "std", "urlencode"]
47-
full = ["default", "code-in-doc", "serde_json"]
47+
full = ["default", "blocks", "code-in-doc", "serde_json"]
4848

4949
alloc = [
5050
"rinja_derive/alloc",
5151
"serde?/alloc",
5252
"serde_json?/alloc",
5353
"percent-encoding?/alloc"
5454
]
55+
blocks = ["rinja_derive/blocks"]
5556
code-in-doc = ["rinja_derive/code-in-doc"]
5657
config = ["rinja_derive/config"]
5758
serde_json = ["rinja_derive/serde_json", "dep:serde", "dep:serde_json"]

rinja_derive/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ syn = { version = "2.0.3", features = ["full"] }
3939

4040
[features]
4141
alloc = []
42+
blocks = ["syn/full"]
4243
code-in-doc = ["dep:pulldown-cmark"]
4344
config = ["dep:serde", "dep:basic-toml", "parser/config"]
4445
urlencode = []

rinja_derive/src/generator.rs

+156-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub(crate) fn template_to_string(
2525
input: &TemplateInput<'_>,
2626
contexts: &HashMap<&Arc<Path>, Context<'_>, FxBuildHasher>,
2727
heritage: Option<&Heritage<'_, '_>>,
28-
tmpl_kind: TmplKind,
28+
tmpl_kind: TmplKind<'_>,
2929
) -> Result<usize, CompileError> {
3030
let generator = Generator::new(
3131
input,
@@ -50,11 +50,14 @@ pub(crate) fn template_to_string(
5050
}
5151

5252
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53-
pub(crate) enum TmplKind {
53+
pub(crate) enum TmplKind<'a> {
5454
/// [`rinja::Template`]
5555
Struct,
5656
/// [`rinja::helpers::EnumVariantTemplate`]
5757
Variant,
58+
/// Used in `blocks` implementation
59+
#[allow(unused)]
60+
Block(&'a str),
5861
}
5962

6063
struct Generator<'a, 'h> {
@@ -113,13 +116,14 @@ impl<'a, 'h> Generator<'a, 'h> {
113116
fn impl_template(
114117
mut self,
115118
buf: &mut Buffer,
116-
tmpl_kind: TmplKind,
119+
tmpl_kind: TmplKind<'a>,
117120
) -> Result<usize, CompileError> {
118121
let ctx = &self.contexts[&self.input.path];
119122

120123
let target = match tmpl_kind {
121124
TmplKind::Struct => "rinja::Template",
122125
TmplKind::Variant => "rinja::helpers::EnumVariantTemplate",
126+
TmplKind::Block(trait_name) => trait_name,
123127
};
124128
write_header(self.input.ast, buf, target);
125129
buf.write(
@@ -170,9 +174,158 @@ impl<'a, 'h> Generator<'a, 'h> {
170174
}
171175

172176
buf.write('}');
177+
178+
#[cfg(feature = "blocks")]
179+
for (block, span) in self.input.blocks {
180+
self.impl_block(buf, block, span)?;
181+
}
182+
173183
Ok(size_hint)
174184
}
175185

186+
#[cfg(feature = "blocks")]
187+
fn impl_block(
188+
&self,
189+
buf: &mut Buffer,
190+
block: &str,
191+
span: &proc_macro2::Span,
192+
) -> Result<(), CompileError> {
193+
// RATIONALE: `*self` must be the input type, implementation details should not leak:
194+
// - impl Self { fn as_block(self) } ->
195+
// - struct __Rinja__Self__as__block__Wrapper { this: self } ->
196+
// - impl Template for __Rinja__Self__as__block__Wrapper { fn render_into_with_values() } ->
197+
// - impl __Rinja__Self__as__block for Self { render_into_with_values() }
198+
199+
use quote::quote_spanned;
200+
use syn::{GenericParam, Ident, Lifetime, LifetimeParam, Token};
201+
202+
let span = *span;
203+
buf.write(
204+
"\
205+
#[allow(missing_docs, non_camel_case_types, non_snake_case, unreachable_pub)]\
206+
const _: () = {",
207+
);
208+
209+
let ident = &self.input.ast.ident;
210+
211+
let doc = format!("A sub-template that renders only the block `{block}` of [`{ident}`].");
212+
let method_name = format!("as_{block}");
213+
let trait_name = format!("__Rinja__{ident}__as__{block}");
214+
let wrapper_name = format!("__Rinja__{ident}__as__{block}__Wrapper");
215+
let self_lt_name = format!("'__Rinja__{ident}__as__{block}__self");
216+
217+
let method_id = Ident::new(&method_name, span);
218+
let trait_id = Ident::new(&trait_name, span);
219+
let wrapper_id = Ident::new(&wrapper_name, span);
220+
let self_lt = Lifetime::new(&self_lt_name, span);
221+
222+
// generics of the input with an additional lifetime to capture `self`
223+
let mut wrapper_generics = self.input.ast.generics.clone();
224+
if wrapper_generics.lt_token.is_none() {
225+
wrapper_generics.lt_token = Some(Token![<](span));
226+
wrapper_generics.gt_token = Some(Token![>](span));
227+
}
228+
wrapper_generics.params.insert(
229+
0,
230+
GenericParam::Lifetime(LifetimeParam::new(self_lt.clone())),
231+
);
232+
233+
let (impl_generics, ty_generics, where_clause) = self.input.ast.generics.split_for_impl();
234+
let (wrapper_impl_generics, wrapper_ty_generics, wrapper_where_clause) =
235+
wrapper_generics.split_for_impl();
236+
237+
let input = TemplateInput {
238+
block: Some((block, span)),
239+
#[cfg(feature = "blocks")]
240+
blocks: &[],
241+
..self.input.clone()
242+
};
243+
let size_hint = template_to_string(
244+
buf,
245+
&input,
246+
self.contexts,
247+
self.heritage,
248+
TmplKind::Block(&trait_name),
249+
)?;
250+
251+
buf.write(quote_spanned! {
252+
span =>
253+
pub trait #trait_id {
254+
fn render_into_with_values<RinjaW>(
255+
&self,
256+
writer: &mut RinjaW,
257+
values: &dyn rinja::Values,
258+
) -> rinja::Result<()>
259+
where
260+
RinjaW:
261+
rinja::helpers::core::fmt::Write + ?rinja::helpers::core::marker::Sized;
262+
}
263+
264+
impl #impl_generics #ident #ty_generics #where_clause {
265+
#[inline]
266+
#[doc = #doc]
267+
pub fn #method_id(&self) -> impl rinja::Template + '_ {
268+
#wrapper_id {
269+
this: self,
270+
}
271+
}
272+
}
273+
274+
#[rinja::helpers::core::prelude::rust_2021::derive(
275+
rinja::helpers::core::prelude::rust_2021::Clone,
276+
rinja::helpers::core::prelude::rust_2021::Copy
277+
)]
278+
pub struct #wrapper_id #wrapper_generics #wrapper_where_clause {
279+
this: &#self_lt #ident #ty_generics,
280+
}
281+
282+
impl #wrapper_impl_generics rinja::Template
283+
for #wrapper_id #wrapper_ty_generics #wrapper_where_clause {
284+
#[inline]
285+
fn render_into_with_values<RinjaW>(
286+
&self,
287+
writer: &mut RinjaW,
288+
values: &dyn rinja::Values
289+
) -> rinja::Result<()>
290+
where
291+
RinjaW: rinja::helpers::core::fmt::Write + ?rinja::helpers::core::marker::Sized
292+
{
293+
<_ as #trait_id>::render_into_with_values(self.this, writer, values)
294+
}
295+
296+
const SIZE_HINT: rinja::helpers::core::primitive::usize = #size_hint;
297+
}
298+
299+
// cannot use `crate::integrations::impl_fast_writable()` w/o cloning the struct
300+
impl #wrapper_impl_generics rinja::filters::FastWritable
301+
for #wrapper_id #wrapper_ty_generics #wrapper_where_clause {
302+
#[inline]
303+
fn write_into<RinjaW>(&self, dest: &mut RinjaW) -> rinja::Result<()>
304+
where
305+
RinjaW: rinja::helpers::core::fmt::Write + ?rinja::helpers::core::marker::Sized
306+
{
307+
<_ as rinja::Template>::render_into(self, dest)
308+
}
309+
}
310+
311+
// cannot use `crate::integrations::impl_display()` w/o cloning the struct
312+
impl #wrapper_impl_generics rinja::helpers::core::fmt::Display
313+
for #wrapper_id #wrapper_ty_generics #wrapper_where_clause {
314+
#[inline]
315+
fn fmt(
316+
&self,
317+
f: &mut rinja::helpers::core::fmt::Formatter<'_>
318+
) -> rinja::helpers::core::fmt::Result {
319+
<_ as rinja::Template>::render_into(self, f)
320+
.map_err(|_| rinja::helpers::core::fmt::Error)
321+
}
322+
}
323+
});
324+
325+
buf.write("};");
326+
Ok(())
327+
}
328+
176329
fn is_var_defined(&self, var_name: &str) -> bool {
177330
self.locals.get(var_name).is_some() || self.input.fields.iter().any(|f| f == var_name)
178331
}

rinja_derive/src/generator/node.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1013,7 +1013,8 @@ impl<'a> Generator<'a, '_> {
10131013

10141014
self.write_buf_writable(ctx, buf)?;
10151015

1016-
let block_fragment_write = self.input.block == name && self.buf_writable.discard;
1016+
let block_fragment_write =
1017+
self.input.block.map(|(block, _)| block) == name && self.buf_writable.discard;
10171018
// Allow writing to the buffer if we're in the block fragment
10181019
if block_fragment_write {
10191020
self.buf_writable.discard = false;

0 commit comments

Comments
 (0)