mirror of
https://github.com/askama-rs/askama.git
synced 2025-09-27 13:00:57 +00:00
generator: use re-exported core
and std
This way, even a shadowed `core` or `std` does not prevent the generated code from working correctly.
This commit is contained in:
parent
bba4e26ce6
commit
dfcfde9ce9
@ -1,6 +1,6 @@
|
||||
[licenses]
|
||||
version = 2
|
||||
allow = ["Apache-2.0", "BSD-2-Clause", "MIT", "Unicode-DFS-2016"]
|
||||
allow = ["Apache-2.0", "MIT", "MIT-0", "Unicode-DFS-2016"]
|
||||
private = { ignore = true }
|
||||
|
||||
[[licenses.clarify]]
|
||||
|
@ -67,6 +67,8 @@ mod html;
|
||||
use std::{fmt, io};
|
||||
|
||||
pub use rinja_derive::Template;
|
||||
#[doc(hidden)]
|
||||
pub use {core, std};
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use crate as shared;
|
||||
|
@ -20,7 +20,7 @@ use crate::config::WhitespaceHandling;
|
||||
use crate::heritage::{Context, Heritage};
|
||||
use crate::html::write_escaped_str;
|
||||
use crate::input::{Source, TemplateInput};
|
||||
use crate::{BUILT_IN_FILTERS, CompileError, FileInfo, MsgValidEscapers};
|
||||
use crate::{BUILT_IN_FILTERS, CRATE, CompileError, FileInfo, MsgValidEscapers};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
enum EvaluatedResult {
|
||||
@ -80,18 +80,6 @@ impl<'a> Generator<'a> {
|
||||
|
||||
// Takes a Context and generates the relevant implementations.
|
||||
pub(crate) fn build(mut self, ctx: &Context<'a>) -> Result<String, CompileError> {
|
||||
const CRATE: &str = if cfg!(feature = "with-actix-web") {
|
||||
"rinja_actix"
|
||||
} else if cfg!(feature = "with-axum") {
|
||||
"rinja_axum"
|
||||
} else if cfg!(feature = "with-rocket") {
|
||||
"rinja_rocket"
|
||||
} else if cfg!(feature = "with-warp") {
|
||||
"rinja_warp"
|
||||
} else {
|
||||
"rinja"
|
||||
};
|
||||
|
||||
let mut buf = Buffer::new();
|
||||
buf.write(format_args!(
|
||||
"\
|
||||
@ -140,10 +128,10 @@ impl<'a> Generator<'a> {
|
||||
buf.write(
|
||||
"fn render_into<RinjaW>(&self, writer: &mut RinjaW) -> rinja::Result<()>\
|
||||
where \
|
||||
RinjaW: ::core::fmt::Write + ?::core::marker::Sized\
|
||||
RinjaW: rinja::core::fmt::Write + ?rinja::core::marker::Sized\
|
||||
{\
|
||||
use rinja::filters::{AutoEscape as _, WriteWritable as _};\
|
||||
use ::core::fmt::Write as _;",
|
||||
use rinja::core::fmt::Write as _;",
|
||||
);
|
||||
|
||||
buf.set_discard(self.buf_writable.discard);
|
||||
@ -163,7 +151,8 @@ impl<'a> Generator<'a> {
|
||||
if path_is_valid {
|
||||
let path = path.to_str().unwrap();
|
||||
buf.write(format_args!(
|
||||
"const _: &[::core::primitive::u8] = ::core::include_bytes!({path:#?});",
|
||||
"const _: &[rinja::core::primitive::u8] =\
|
||||
rinja::core::include_bytes!({path:#?});",
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -181,10 +170,10 @@ impl<'a> Generator<'a> {
|
||||
"\
|
||||
rinja::Result::Ok(())\
|
||||
}}\
|
||||
const EXTENSION: ::core::option::Option<&'static ::core::primitive::str> =\
|
||||
::core::option::Option::{:?};\
|
||||
const SIZE_HINT: ::core::primitive::usize = {size_hint}usize;\
|
||||
const MIME_TYPE: &'static ::core::primitive::str = {:?};",
|
||||
const EXTENSION: rinja::core::option::Option<&'static rinja::core::primitive::str> =\
|
||||
rinja::core::option::Option::{:?};\
|
||||
const SIZE_HINT: rinja::core::primitive::usize = {size_hint}usize;\
|
||||
const MIME_TYPE: &'static rinja::core::primitive::str = {:?};",
|
||||
self.input.extension(),
|
||||
self.input.mime_type,
|
||||
));
|
||||
@ -198,19 +187,19 @@ impl<'a> Generator<'a> {
|
||||
let ident = &self.input.ast.ident;
|
||||
buf.write(format_args!(
|
||||
"\
|
||||
/// Implement the [`format!()`][::std::format] trait for [`{}`]\n\
|
||||
/// Implement the [`format!()`][rinja::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, "::core::fmt::Display", None);
|
||||
self.write_header(buf, "rinja::core::fmt::Display", None);
|
||||
buf.write(
|
||||
"\
|
||||
#[inline]\
|
||||
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {\
|
||||
rinja::Template::render_into(self, f).map_err(|_| ::core::fmt::Error)\
|
||||
fn fmt(&self, f: &mut rinja::core::fmt::Formatter<'_>) -> rinja::core::fmt::Result {\
|
||||
rinja::Template::render_into(self, f).map_err(|_| rinja::core::fmt::Error)\
|
||||
}\
|
||||
}",
|
||||
);
|
||||
@ -222,11 +211,11 @@ impl<'a> Generator<'a> {
|
||||
buf.write(
|
||||
"\
|
||||
#[inline]\
|
||||
fn write_into<RinjaW>(&self, dest: &mut RinjaW) -> ::core::fmt::Result \
|
||||
fn write_into<RinjaW>(&self, dest: &mut RinjaW) -> rinja::core::fmt::Result \
|
||||
where \
|
||||
RinjaW: ::core::fmt::Write + ?::core::marker::Sized,\
|
||||
RinjaW: rinja::core::fmt::Write + ?rinja::core::marker::Sized,\
|
||||
{\
|
||||
rinja::Template::render_into(self, dest).map_err(|_| ::core::fmt::Error)\
|
||||
rinja::Template::render_into(self, dest).map_err(|_| rinja::core::fmt::Error)\
|
||||
}\
|
||||
}",
|
||||
);
|
||||
@ -657,7 +646,7 @@ impl<'a> Generator<'a> {
|
||||
// finally dereferences it to `bool`.
|
||||
buf.write("*(&(");
|
||||
buf.write(this.visit_expr_root(ctx, expr)?);
|
||||
buf.write(") as &::core::primitive::bool) {");
|
||||
buf.write(") as &rinja::core::primitive::bool) {");
|
||||
}
|
||||
} else if pos != 0 {
|
||||
buf.write("} else {");
|
||||
@ -1012,7 +1001,7 @@ impl<'a> Generator<'a> {
|
||||
// build `FmtCell` that contains the inner block
|
||||
buf.write(format_args!(
|
||||
"let {FILTER_SOURCE} = rinja::helpers::FmtCell::new(\
|
||||
|writer: &mut ::core::fmt::Formatter<'_>| -> rinja::Result<()> {{"
|
||||
|writer: &mut rinja::core::fmt::Formatter<'_>| -> rinja::Result<()> {{"
|
||||
));
|
||||
let size_hint = self.push_locals(|this| {
|
||||
this.prepare_ws(filter.ws1);
|
||||
@ -1044,7 +1033,7 @@ impl<'a> Generator<'a> {
|
||||
),
|
||||
};
|
||||
buf.write(format_args!(
|
||||
"if ::core::write!(writer, \"{{}}\", {filter_buf}).is_err() {{\
|
||||
"if rinja::core::write!(writer, \"{{}}\", {filter_buf}).is_err() {{\
|
||||
return {FILTER_SOURCE}.take_err();\
|
||||
}}"
|
||||
));
|
||||
@ -1523,7 +1512,7 @@ impl<'a> Generator<'a> {
|
||||
) -> Result<DisplayWrap, CompileError> {
|
||||
buf.write("rinja::helpers::get_primitive_value(&(");
|
||||
self.visit_expr(ctx, buf, expr)?;
|
||||
buf.write(format_args!(")) as ::core::primitive::{target}"));
|
||||
buf.write(format_args!(")) as rinja::core::primitive::{target}"));
|
||||
Ok(DisplayWrap::Unwrapped)
|
||||
}
|
||||
|
||||
@ -1533,9 +1522,9 @@ impl<'a> Generator<'a> {
|
||||
buf: &mut Buffer,
|
||||
expr: &WithSpan<'_, Expr<'_>>,
|
||||
) -> Result<DisplayWrap, CompileError> {
|
||||
buf.write("::core::result::Result::map_err(");
|
||||
buf.write("rinja::core::result::Result::map_err(");
|
||||
self.visit_expr(ctx, buf, expr)?;
|
||||
buf.write(", |err| rinja::shared::Error::Custom(::core::convert::Into::into(err)))?");
|
||||
buf.write(", |err| rinja::shared::Error::Custom(rinja::core::convert::Into::into(err)))?");
|
||||
Ok(DisplayWrap::Unwrapped)
|
||||
}
|
||||
|
||||
@ -1896,7 +1885,7 @@ impl<'a> Generator<'a> {
|
||||
) -> Result<DisplayWrap, CompileError> {
|
||||
if !args.is_empty() {
|
||||
if let Expr::StrLit(ref fmt) = *args[0] {
|
||||
buf.write("::std::format!(");
|
||||
buf.write("rinja::std::format!(");
|
||||
self.visit_str_lit(buf, fmt);
|
||||
if args.len() > 1 {
|
||||
buf.write(',');
|
||||
@ -1919,7 +1908,7 @@ impl<'a> Generator<'a> {
|
||||
) -> Result<DisplayWrap, CompileError> {
|
||||
if let [_, arg2] = args {
|
||||
if let Expr::StrLit(ref fmt) = **arg2 {
|
||||
buf.write("::std::format!(");
|
||||
buf.write("rinja::std::format!(");
|
||||
self.visit_str_lit(buf, fmt);
|
||||
buf.write(',');
|
||||
self._visit_args(ctx, buf, &args[..1])?;
|
||||
@ -2108,7 +2097,7 @@ impl<'a> Generator<'a> {
|
||||
);\
|
||||
let _len = _cycle.len();\
|
||||
if _len == 0 {\
|
||||
return ::core::result::Result::Err(rinja::Error::Fmt);\
|
||||
return rinja::core::result::Result::Err(rinja::Error::Fmt);\
|
||||
}\
|
||||
_cycle[_loop_item.index % _len]\
|
||||
})",
|
||||
|
@ -27,8 +27,8 @@ use proc_macro::TokenStream as TokenStream12;
|
||||
#[cfg(feature = "__standalone")]
|
||||
use proc_macro2::TokenStream as TokenStream12;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::quote_spanned;
|
||||
use rustc_hash::FxBuildHasher;
|
||||
use syn::parse_quote_spanned;
|
||||
|
||||
/// The `Template` derive macro and its `template()` attribute.
|
||||
///
|
||||
@ -120,7 +120,10 @@ use syn::parse_quote_spanned;
|
||||
pub fn derive_template(input: TokenStream12) -> TokenStream12 {
|
||||
let ast = match syn::parse2(input.into()) {
|
||||
Ok(ast) => ast,
|
||||
Err(err) => return err.to_compile_error().into(),
|
||||
Err(err) => {
|
||||
let msgs = err.into_iter().map(|err| err.to_string());
|
||||
return compile_error(msgs, Span::call_site()).into();
|
||||
}
|
||||
};
|
||||
match build_template(&ast) {
|
||||
Ok(source) => source.parse().unwrap(),
|
||||
@ -129,10 +132,7 @@ pub fn derive_template(input: TokenStream12) -> TokenStream12 {
|
||||
span,
|
||||
rendered: _rendered,
|
||||
}) => {
|
||||
let mut ts: TokenStream = parse_quote_spanned! {
|
||||
span.unwrap_or(ast.ident.span()) =>
|
||||
::core::compile_error!(#msg);
|
||||
};
|
||||
let mut ts = compile_error(std::iter::once(msg), span.unwrap_or(ast.ident.span()));
|
||||
if let Ok(source) = build_skeleton(&ast) {
|
||||
let source: TokenStream = source.parse().unwrap();
|
||||
ts.extend(source);
|
||||
@ -142,6 +142,17 @@ pub fn derive_template(input: TokenStream12) -> TokenStream12 {
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_error(msgs: impl Iterator<Item = String>, span: Span) -> TokenStream {
|
||||
let crate_ = syn::Ident::new(CRATE, Span::call_site());
|
||||
quote_spanned! {
|
||||
span =>
|
||||
const _: () = {
|
||||
extern crate #crate_ as rinja;
|
||||
#(rinja::core::compile_error!(#msgs);)*
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn build_skeleton(ast: &syn::DeriveInput) -> Result<String, CompileError> {
|
||||
let template_args = TemplateArgs::fallback();
|
||||
let config = Config::new("", None, None, None)?;
|
||||
@ -433,3 +444,15 @@ const BUILT_IN_FILTERS: &[&str] = &[
|
||||
"urlencode",
|
||||
"wordcount",
|
||||
];
|
||||
|
||||
const CRATE: &str = if cfg!(feature = "with-actix-web") {
|
||||
"rinja_actix"
|
||||
} else if cfg!(feature = "with-axum") {
|
||||
"rinja_axum"
|
||||
} else if cfg!(feature = "with-rocket") {
|
||||
"rinja_rocket"
|
||||
} else if cfg!(feature = "with-warp") {
|
||||
"rinja_warp"
|
||||
} else {
|
||||
"rinja"
|
||||
};
|
||||
|
@ -46,36 +46,36 @@ struct Foo {{ {} }}"##,
|
||||
impl rinja::Template for Foo {
|
||||
fn render_into<RinjaW>(&self, writer: &mut RinjaW) -> rinja::Result<()>
|
||||
where
|
||||
RinjaW: ::core::fmt::Write + ?::core::marker::Sized,
|
||||
RinjaW: rinja::core::fmt::Write + ?rinja::core::marker::Sized,
|
||||
{
|
||||
use rinja::filters::{AutoEscape as _, WriteWritable as _};
|
||||
use ::core::fmt::Write as _;
|
||||
use rinja::core::fmt::Write as _;
|
||||
#expected
|
||||
rinja::Result::Ok(())
|
||||
}
|
||||
const EXTENSION: ::core::option::Option<&'static ::core::primitive::str> =
|
||||
::core::option::Option::Some("txt");
|
||||
const SIZE_HINT: ::core::primitive::usize = #size_hint;
|
||||
const MIME_TYPE: &'static ::core::primitive::str = "text/plain; charset=utf-8";
|
||||
const EXTENSION: rinja::core::option::Option<&'static rinja::core::primitive::str> =
|
||||
rinja::core::option::Option::Some("txt");
|
||||
const SIZE_HINT: rinja::core::primitive::usize = #size_hint;
|
||||
const MIME_TYPE: &'static rinja::core::primitive::str = "text/plain; charset=utf-8";
|
||||
}
|
||||
|
||||
/// Implement the [`format!()`][::std::format] trait for [`Foo`]
|
||||
/// Implement the [`format!()`][rinja::std::format] trait for [`Foo`]
|
||||
///
|
||||
/// Please be aware of the rendering performance notice in the [`Template`][rinja::Template] trait.
|
||||
impl ::core::fmt::Display for Foo {
|
||||
impl rinja::core::fmt::Display for Foo {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
|
||||
rinja::Template::render_into(self, f).map_err(|_| ::core::fmt::Error)
|
||||
fn fmt(&self, f: &mut rinja::core::fmt::Formatter<'_>) -> rinja::core::fmt::Result {
|
||||
rinja::Template::render_into(self, f).map_err(|_| rinja::core::fmt::Error)
|
||||
}
|
||||
}
|
||||
|
||||
impl rinja::filters::FastWritable for Foo {
|
||||
#[inline]
|
||||
fn write_into<RinjaW>(&self, dest: &mut RinjaW) -> ::core::fmt::Result
|
||||
fn write_into<RinjaW>(&self, dest: &mut RinjaW) -> rinja::core::fmt::Result
|
||||
where
|
||||
RinjaW: ::core::fmt::Write + ?::core::marker::Sized,
|
||||
RinjaW: rinja::core::fmt::Write + ?rinja::core::marker::Sized,
|
||||
{
|
||||
rinja::Template::render_into(self, dest).map_err(|_| ::core::fmt::Error)
|
||||
rinja::Template::render_into(self, dest).map_err(|_| rinja::core::fmt::Error)
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -194,9 +194,9 @@ fn check_if_let() {
|
||||
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:#?});
|
||||
r#"const _: &[rinja::core::primitive::u8] = rinja::core::include_bytes!({path1:#?});
|
||||
const _: &[rinja::core::primitive::u8] = rinja::core::include_bytes!({path2:#?});
|
||||
const _: &[rinja::core::primitive::u8] = rinja::core::include_bytes!({path3:#?});
|
||||
writer.write_str("3333")?;"#
|
||||
),
|
||||
&[],
|
||||
@ -292,7 +292,7 @@ writer.write_str("bla")?;"#,
|
||||
"{% if x == 12 %}bli
|
||||
{%- else if x is defined %}12
|
||||
{%- else %}nope{% endif %}",
|
||||
r#"if *(&(self.x == 12) as &::core::primitive::bool) {
|
||||
r#"if *(&(self.x == 12) as &rinja::core::primitive::bool) {
|
||||
writer.write_str("bli")?;
|
||||
} else {
|
||||
writer.write_str("12")?;
|
||||
@ -305,7 +305,7 @@ writer.write_str("12")?;
|
||||
// are present.
|
||||
compare(
|
||||
"{% if y is defined || x == 12 %}{{x}}{% endif %}",
|
||||
r"if *(&(self.x == 12) as &::core::primitive::bool) {
|
||||
r"if *(&(self.x == 12) as &rinja::core::primitive::bool) {
|
||||
match (
|
||||
&((&&rinja::filters::AutoEscaper::new(&(self.x), rinja::filters::Text)).rinja_auto_escape()?),
|
||||
) {
|
||||
@ -346,7 +346,7 @@ writer.write_str("12")?;
|
||||
compare(
|
||||
"{% if y is defined && y == 12 %}{{y}}{% else %}bli{% endif %}",
|
||||
r#"
|
||||
if *(&(self.y == 12) as &::core::primitive::bool) {
|
||||
if *(&(self.y == 12) as &rinja::core::primitive::bool) {
|
||||
match (
|
||||
&((&&rinja::filters::AutoEscaper::new(
|
||||
&(self.y),
|
||||
@ -417,7 +417,7 @@ match (
|
||||
// Ensure that the `!` is kept .
|
||||
compare(
|
||||
"{% if y is defined && !y %}bla{% endif %}",
|
||||
r#"if *(&(!self.y) as &::core::primitive::bool) {
|
||||
r#"if *(&(!self.y) as &rinja::core::primitive::bool) {
|
||||
writer.write_str("bla")?;
|
||||
}"#,
|
||||
&[("y", "bool")],
|
||||
@ -425,7 +425,7 @@ match (
|
||||
);
|
||||
compare(
|
||||
"{% if y is defined && !(y) %}bla{% endif %}",
|
||||
r#"if *(&(!(self.y)) as &::core::primitive::bool) {
|
||||
r#"if *(&(!(self.y)) as &rinja::core::primitive::bool) {
|
||||
writer.write_str("bla")?;
|
||||
}"#,
|
||||
&[("y", "bool")],
|
||||
@ -433,7 +433,7 @@ match (
|
||||
);
|
||||
compare(
|
||||
"{% if y is not defined || !y %}bla{% endif %}",
|
||||
r#"if *(&(!self.y) as &::core::primitive::bool) {
|
||||
r#"if *(&(!self.y) as &rinja::core::primitive::bool) {
|
||||
writer.write_str("bla")?;
|
||||
}"#,
|
||||
&[("y", "bool")],
|
||||
@ -441,7 +441,7 @@ match (
|
||||
);
|
||||
compare(
|
||||
"{% if y is not defined || !(y) %}bla{% endif %}",
|
||||
r#"if *(&(!(self.y)) as &::core::primitive::bool) {
|
||||
r#"if *(&(!(self.y)) as &rinja::core::primitive::bool) {
|
||||
writer.write_str("bla")?;
|
||||
}"#,
|
||||
&[("y", "bool")],
|
||||
@ -506,7 +506,7 @@ fn check_bool_conditions() {
|
||||
);
|
||||
compare(
|
||||
"{% if false || x == 12 %}{{x}}{% endif %}",
|
||||
r"if *(&(self.x == 12) as &::core::primitive::bool) {
|
||||
r"if *(&(self.x == 12) as &rinja::core::primitive::bool) {
|
||||
match (
|
||||
&((&&rinja::filters::AutoEscaper::new(
|
||||
&(self.x),
|
||||
@ -530,7 +530,7 @@ fn check_bool_conditions() {
|
||||
// condition.
|
||||
compare(
|
||||
"{% if y == 3 || (true || x == 12) %}{{x}}{% endif %}",
|
||||
r"if *(&(self.y == 3 || (true)) as &::core::primitive::bool) {
|
||||
r"if *(&(self.y == 3 || (true)) as &rinja::core::primitive::bool) {
|
||||
match (
|
||||
&((&&rinja::filters::AutoEscaper::new(
|
||||
&(self.x),
|
||||
@ -568,7 +568,7 @@ fn check_bool_conditions() {
|
||||
);
|
||||
compare(
|
||||
"{% if y == 3 || (x == 12 || true) %}{{x}}{% endif %}",
|
||||
r"if *(&(self.y == 3 || (self.x == 12 || true)) as &::core::primitive::bool) {
|
||||
r"if *(&(self.y == 3 || (self.x == 12 || true)) as &rinja::core::primitive::bool) {
|
||||
match (
|
||||
&((&&rinja::filters::AutoEscaper::new(
|
||||
&(self.x),
|
||||
|
@ -17,6 +17,9 @@ rinja = { path = "../rinja", version = "0.3.4" }
|
||||
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
|
||||
# intentionally shadow the name `::core` to test if the generated code still works fine
|
||||
core = { package = "intentionally-empty", version = "1.0.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
rinja = { path = "../rinja", version = "0.3.4", features = ["code-in-doc", "serde_json"] }
|
||||
|
||||
|
@ -207,7 +207,7 @@ hello
|
||||
{% set y = 12 %}
|
||||
|
||||
{% filter wordcount %}
|
||||
{%- include "../Cargo.toml" +%}
|
||||
{%- include "../test_trim.toml" +%}
|
||||
y is {{ y }}
|
||||
{% endfilter %}
|
||||
{%- endblock body %}
|
||||
@ -233,7 +233,7 @@ fn filter_block_include() {
|
||||
<body class=""><h1>Metadata</h1>
|
||||
|
||||
|
||||
105</body>
|
||||
7</body>
|
||||
</html>"#
|
||||
);
|
||||
}
|
||||
|
15
testing/tests/no_implicit_prelude.rs
Normal file
15
testing/tests/no_implicit_prelude.rs
Normal file
@ -0,0 +1,15 @@
|
||||
#![no_implicit_prelude]
|
||||
|
||||
use ::rinja::Template;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "hello.html")]
|
||||
struct HelloTemplate<'a> {
|
||||
name: &'a str,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn main() {
|
||||
let hello = HelloTemplate { name: "world" };
|
||||
::std::assert_eq!("Hello, world!", hello.render().unwrap());
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user