mirror of
https://github.com/askama-rs/askama.git
synced 2025-10-02 23:35:07 +00:00
Merge pull request #87 from Kijewski/pr-include-once
Every used template gets referenced exactly once
This commit is contained in:
commit
7eddf9d2ca
@ -33,4 +33,10 @@ once_map = "0.4.18"
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
serde = { version = "1.0", optional = true, features = ["derive"] }
|
||||
syn = "2"
|
||||
syn = "2.0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
console = "0.15.8"
|
||||
similar = "2.6.0"
|
||||
prettyplease = "0.2.20"
|
||||
syn = { version = "2.0.3", features = ["extra-traits", "full"] }
|
||||
|
@ -102,20 +102,23 @@ impl<'a> Generator<'a> {
|
||||
|
||||
buf.discard = self.buf_writable.discard;
|
||||
// Make sure the compiler understands that the generated code depends on the template files.
|
||||
for path in self.contexts.keys() {
|
||||
let mut paths = self
|
||||
.contexts
|
||||
.keys()
|
||||
.map(|path| -> &Path { path })
|
||||
.collect::<Vec<_>>();
|
||||
paths.sort();
|
||||
for path in paths {
|
||||
// Skip the fake path of templates defined in rust source.
|
||||
let path_is_valid = match self.input.source {
|
||||
Source::Path(_) => true,
|
||||
Source::Source(_) => **path != self.input.path,
|
||||
Source::Source(_) => path != &*self.input.path,
|
||||
};
|
||||
if path_is_valid {
|
||||
let path = path.to_str().unwrap();
|
||||
buf.writeln(
|
||||
quote! {
|
||||
const _: &[::core::primitive::u8] = ::core::include_bytes!(#path);
|
||||
}
|
||||
.to_string(),
|
||||
);
|
||||
buf.writeln(format_args!(
|
||||
"const _: &[::core::primitive::u8] = ::core::include_bytes!({path:#?});",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -788,17 +791,6 @@ impl<'a> Generator<'a> {
|
||||
.config
|
||||
.find_template(i.path, Some(&self.input.path))?;
|
||||
|
||||
// Make sure the compiler understands that the generated code depends on the template file.
|
||||
{
|
||||
let path = path.to_str().unwrap();
|
||||
buf.writeln(
|
||||
quote! {
|
||||
const _: &[::core::primitive::u8] = ::core::include_bytes!(#path);
|
||||
}
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
// We clone the context of the child in order to preserve their macros and imports.
|
||||
// But also add all the imports and macros from this template that don't override the
|
||||
// child's ones to preserve this template's context.
|
||||
|
@ -1,6 +1,10 @@
|
||||
//! Files containing tests for generated code.
|
||||
|
||||
use std::fmt::Write;
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
|
||||
use console::style;
|
||||
use similar::{Algorithm, ChangeTag, TextDiffConfig};
|
||||
|
||||
use crate::build_template;
|
||||
|
||||
@ -9,51 +13,91 @@ fn check_if_let() {
|
||||
// This function makes it much easier to compare expected code by adding the wrapping around
|
||||
// the code we want to check.
|
||||
#[track_caller]
|
||||
fn compare(jinja: &str, expected: &str) {
|
||||
let jinja = format!(
|
||||
r##"#[template(source = r#"{jinja}"#, ext = "txt")]
|
||||
struct Foo;"##
|
||||
);
|
||||
let generated =
|
||||
build_template(&syn::parse_str::<syn::DeriveInput>(&jinja).unwrap()).unwrap();
|
||||
fn compare(jinja: &str, expected: &str, size_hint: usize) {
|
||||
let jinja = format!(r#"#[template(source = {jinja:?}, ext = "txt")] struct Foo;"#);
|
||||
let generated = build_template(&syn::parse_str::<syn::DeriveInput>(&jinja).unwrap())
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap();
|
||||
let generated: syn::File = syn::parse2(generated).unwrap();
|
||||
|
||||
let generated_s = syn::parse_str::<proc_macro2::TokenStream>(&generated)
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let mut new_expected = String::with_capacity(expected.len());
|
||||
for line in expected.split('\n') {
|
||||
new_expected.write_fmt(format_args!("{line}\n")).unwrap();
|
||||
let size_hint = proc_macro2::Literal::usize_unsuffixed(size_hint);
|
||||
let expected: proc_macro2::TokenStream = expected.parse().unwrap();
|
||||
let expected: syn::File = syn::parse_quote! {
|
||||
impl ::rinja::Template for Foo {
|
||||
fn render_into<RinjaW>(&self, writer: &mut RinjaW) -> ::rinja::Result<()>
|
||||
where
|
||||
RinjaW: ::core::fmt::Write + ?::core::marker::Sized,
|
||||
{
|
||||
use ::rinja::filters::AutoEscape as _;
|
||||
use ::core::fmt::Write as _;
|
||||
#expected
|
||||
::rinja::Result::Ok(())
|
||||
}
|
||||
const EXTENSION: ::std::option::Option<&'static ::std::primitive::str> = Some("txt");
|
||||
const SIZE_HINT: ::std::primitive::usize = #size_hint;
|
||||
const MIME_TYPE: &'static ::std::primitive::str = "text/plain; charset=utf-8";
|
||||
}
|
||||
impl ::std::fmt::Display for Foo {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
::rinja::Template::render_into(self, f).map_err(|_| ::std::fmt::Error {})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if expected != generated {
|
||||
let expected = prettyplease::unparse(&expected);
|
||||
let generated = prettyplease::unparse(&generated);
|
||||
|
||||
struct Diff<'a>(&'a str, &'a str);
|
||||
|
||||
impl fmt::Display for Diff<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let diff = TextDiffConfig::default()
|
||||
.algorithm(Algorithm::Patience)
|
||||
.diff_lines(self.0, self.1);
|
||||
for change in diff.iter_all_changes() {
|
||||
let (change, line) = match change.tag() {
|
||||
ChangeTag::Equal => (
|
||||
style(" ").dim().bold(),
|
||||
style(change.to_string_lossy()).dim(),
|
||||
),
|
||||
ChangeTag::Delete => (
|
||||
style("-").red().bold(),
|
||||
style(change.to_string_lossy()).red(),
|
||||
),
|
||||
ChangeTag::Insert => (
|
||||
style("+").green().bold(),
|
||||
style(change.to_string_lossy()).green(),
|
||||
),
|
||||
};
|
||||
write!(f, "{change}{line}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
panic!(
|
||||
"\n\
|
||||
=== Expected ===\n\
|
||||
\n\
|
||||
{expected}\n\
|
||||
\n\
|
||||
=== Generated ===\n\
|
||||
\n\
|
||||
{generated}\n\
|
||||
\n\
|
||||
=== Diff ===\n\
|
||||
\n\
|
||||
{diff}\n\
|
||||
\n\
|
||||
=== FAILURE ===",
|
||||
expected = style(&expected).red(),
|
||||
generated = style(&generated).green(),
|
||||
diff = Diff(&expected, &generated),
|
||||
);
|
||||
}
|
||||
let expected = format!(
|
||||
r#"impl ::rinja::Template for Foo {{
|
||||
fn render_into<RinjaW>(&self, writer: &mut RinjaW) -> ::rinja::Result<()>
|
||||
where
|
||||
RinjaW: ::core::fmt::Write + ?::core::marker::Sized,
|
||||
{{
|
||||
use ::rinja::filters::AutoEscape as _;
|
||||
use ::core::fmt::Write as _;
|
||||
{new_expected}
|
||||
::rinja::Result::Ok(())
|
||||
}}
|
||||
const EXTENSION: ::std::option::Option<&'static ::std::primitive::str> = Some("txt");
|
||||
const SIZE_HINT: ::std::primitive::usize = 3;
|
||||
const MIME_TYPE: &'static ::std::primitive::str = "text/plain; charset=utf-8";
|
||||
}}
|
||||
impl ::std::fmt::Display for Foo {{
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {{
|
||||
::rinja::Template::render_into(self, f).map_err(|_| ::std::fmt::Error {{}})
|
||||
}}
|
||||
}}"#
|
||||
);
|
||||
let expected_s = syn::parse_str::<proc_macro2::TokenStream>(&expected)
|
||||
.unwrap()
|
||||
.to_string();
|
||||
assert_eq!(
|
||||
generated_s, expected_s,
|
||||
"=== Expected ===\n{}\n=== Found ===\n{}\n=====",
|
||||
generated, expected
|
||||
);
|
||||
}
|
||||
|
||||
// In this test, we ensure that `query` never is `self.query`.
|
||||
@ -66,6 +110,7 @@ impl ::std::fmt::Display for Foo {{
|
||||
expr0 = &(&&::rinja::filters::AutoEscaper::new(&(query), ::rinja::filters::Text)).rinja_auto_escape()?,
|
||||
)?;
|
||||
}"#,
|
||||
3,
|
||||
);
|
||||
|
||||
// In this test, we ensure that `s` is `self.s` only in the first `if let Some(s) = self.s`
|
||||
@ -79,6 +124,7 @@ impl ::std::fmt::Display for Foo {{
|
||||
expr0 = &(&&::rinja::filters::AutoEscaper::new(&(s), ::rinja::filters::Text)).rinja_auto_escape()?,
|
||||
)?;
|
||||
}"#,
|
||||
3,
|
||||
);
|
||||
|
||||
// In this test, we ensure that `s` is `self.s` only in the first `if let Some(s) = self.s`
|
||||
@ -92,5 +138,25 @@ impl ::std::fmt::Display for Foo {{
|
||||
expr0 = &(&&::rinja::filters::AutoEscaper::new(&(s), ::rinja::filters::Text)).rinja_auto_escape()?,
|
||||
)?;
|
||||
}"#,
|
||||
3,
|
||||
);
|
||||
|
||||
// In this test we make sure that every used template gets referenced exactly once.
|
||||
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("templates");
|
||||
let path1 = path.join("include1.html");
|
||||
let path2 = path.join("include2.html");
|
||||
let path3 = path.join("include3.html");
|
||||
compare(
|
||||
r#"{% include "include1.html" %}"#,
|
||||
&format!(
|
||||
r#"const _: &[::core::primitive::u8] = ::core::include_bytes!({path1:#?});
|
||||
const _: &[::core::primitive::u8] = ::core::include_bytes!({path2:#?});
|
||||
const _: &[::core::primitive::u8] = ::core::include_bytes!({path3:#?});
|
||||
writer.write_str("3")?;
|
||||
writer.write_str("3")?;
|
||||
writer.write_str("3")?;
|
||||
writer.write_str("3")?;"#
|
||||
),
|
||||
4,
|
||||
);
|
||||
}
|
||||
|
2
rinja_derive/templates/include1.html
Normal file
2
rinja_derive/templates/include1.html
Normal file
@ -0,0 +1,2 @@
|
||||
{%- include "include2.html" -%}
|
||||
{%- include "include2.html" -%}
|
2
rinja_derive/templates/include2.html
Normal file
2
rinja_derive/templates/include2.html
Normal file
@ -0,0 +1,2 @@
|
||||
{%- include "include3.html" -%}
|
||||
{%- include "include3.html" -%}
|
1
rinja_derive/templates/include3.html
Normal file
1
rinja_derive/templates/include3.html
Normal file
@ -0,0 +1 @@
|
||||
3
|
@ -37,6 +37,11 @@ syn = "2"
|
||||
[dev-dependencies]
|
||||
criterion = "0.5"
|
||||
|
||||
console = "0.15.8"
|
||||
similar = "2.6.0"
|
||||
prettyplease = "0.2.20"
|
||||
syn = { version = "2.0.3", features = ["extra-traits", "full"] }
|
||||
|
||||
[[bench]]
|
||||
name = "derive-template"
|
||||
harness = false
|
||||
|
Loading…
x
Reference in New Issue
Block a user