mirror of
https://github.com/askama-rs/askama.git
synced 2025-09-26 20:40:39 +00:00
Implement feature "nightly-spans"
This commit is contained in:
parent
a0d99ba6fc
commit
8c02e48cdb
31
.github/workflows/rust.yml
vendored
31
.github/workflows/rust.yml
vendored
@ -145,7 +145,7 @@ jobs:
|
||||
|
||||
####################################################################################################
|
||||
# STEP 2: INTERMEDIATE
|
||||
# ["Test", "Package", "MSRV"]
|
||||
# ["Test", "Package", "Nightly", "MSRV"]
|
||||
####################################################################################################
|
||||
|
||||
Test:
|
||||
@ -168,8 +168,8 @@ jobs:
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- run: cargo test --all-features
|
||||
- run: cargo test --all-targets --all-features
|
||||
- run: cargo test --features full
|
||||
- run: cargo test --all-targets --features full
|
||||
|
||||
Package:
|
||||
needs: ["Rustfmt", "Docs", "Audit", "Book", "Typos", "Jinja2-Assumptions", "DevSkim", "CargoSort"]
|
||||
@ -193,6 +193,23 @@ jobs:
|
||||
- run: cd ${{ matrix.package }} && cargo test --all-targets
|
||||
- run: cd ${{ matrix.package }} && cargo clippy --all-targets -- -D warnings
|
||||
|
||||
Nightly:
|
||||
needs: ["Rustfmt", "Docs", "Audit", "Book", "Typos", "Jinja2-Assumptions", "DevSkim", "CargoSort"]
|
||||
strategy:
|
||||
matrix:
|
||||
package: [
|
||||
askama, askama_derive, askama_escape, askama_macros, askama_parser,
|
||||
testing, testing-alloc, testing-no-std, testing-renamed,
|
||||
]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- run: cd ${{ matrix.package }} && cargo test --all-features
|
||||
|
||||
MSRV:
|
||||
needs: ["Rustfmt", "Docs", "Audit", "Book", "Typos", "Jinja2-Assumptions", "DevSkim", "CargoSort"]
|
||||
runs-on: ubuntu-latest
|
||||
@ -203,7 +220,7 @@ jobs:
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: "1.88.0"
|
||||
- run: cargo check --lib -p askama --all-features
|
||||
- run: cargo check --lib -p askama --features full
|
||||
|
||||
####################################################################################################
|
||||
# STEP 2: SLOW
|
||||
@ -211,7 +228,7 @@ jobs:
|
||||
####################################################################################################
|
||||
|
||||
Fuzz:
|
||||
needs: ["Test", "Package", "MSRV"]
|
||||
needs: ["Test", "Package", "Nightly", "MSRV"]
|
||||
strategy:
|
||||
matrix:
|
||||
fuzz_target:
|
||||
@ -231,7 +248,7 @@ jobs:
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rust-src
|
||||
- run: curl --location --silent --show-error --fail https://github.com/cargo-bins/cargo-quickinstall/releases/download/cargo-fuzz-0.12.0/cargo-fuzz-0.12.0-x86_64-unknown-linux-gnu.tar.gz | tar -xzvvf - -C $HOME/.cargo/bin
|
||||
- run: curl --location --silent --show-error --fail https://github.com/cargo-bins/cargo-quickinstall/releases/download/cargo-fuzz-0.13.1/cargo-fuzz-0.13.1-x86_64-unknown-linux-gnu.tar.gz | tar -xzvvf - -C $HOME/.cargo/bin
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- run: cargo fuzz run ${{ matrix.fuzz_target }} --jobs 4 -- -max_total_time=600
|
||||
working-directory: fuzzing
|
||||
@ -239,7 +256,7 @@ jobs:
|
||||
RUSTFLAGS: '-Ctarget-feature=-crt-static'
|
||||
|
||||
Cluster-Fuzz:
|
||||
needs: ["Test", "Package", "MSRV"]
|
||||
needs: ["Test", "Package", "Nightly", "MSRV"]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
|
@ -57,6 +57,7 @@ blocks = ["askama_macros?/blocks"]
|
||||
code-in-doc = ["askama_macros?/code-in-doc"]
|
||||
config = ["askama_macros?/config"]
|
||||
derive = ["dep:askama_macros", "dep:askama_macros"]
|
||||
nightly-spans = ["askama_macros/nightly-spans"]
|
||||
serde_json = ["std", "askama_macros?/serde_json", "dep:serde", "dep:serde_json"]
|
||||
std = [
|
||||
"alloc",
|
||||
|
@ -56,8 +56,15 @@ default = [
|
||||
alloc = []
|
||||
blocks = ["syn/full"]
|
||||
code-in-doc = ["dep:pulldown-cmark"]
|
||||
config = ["external-sources", "dep:basic-toml", "dep:serde", "dep:serde_derive", "parser/config"]
|
||||
config = [
|
||||
"external-sources",
|
||||
"dep:basic-toml",
|
||||
"dep:serde",
|
||||
"dep:serde_derive",
|
||||
"parser/config",
|
||||
]
|
||||
external-sources = []
|
||||
nightly-spans = []
|
||||
proc-macro = ["proc-macro2/proc-macro"]
|
||||
serde_json = []
|
||||
std = ["alloc"]
|
||||
|
@ -174,40 +174,49 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
TmplKind::Block(trait_name) => field_new(trait_name, span),
|
||||
};
|
||||
|
||||
let mut full_paths = TokenStream::new();
|
||||
let mut paths_ts = TokenStream::new();
|
||||
|
||||
if let Some(full_config_path) = &self.input.config.full_config_path {
|
||||
let full_config_path = self.rel_path(full_config_path).display().to_string();
|
||||
full_paths = quote_spanned!(span=>
|
||||
paths_ts.extend(quote_spanned!(span =>
|
||||
const _: &[askama::helpers::core::primitive::u8] =
|
||||
askama::helpers::core::include_bytes!(#full_config_path);
|
||||
);
|
||||
askama::helpers::core::include_bytes!(#full_config_path);
|
||||
));
|
||||
}
|
||||
|
||||
// Make sure the compiler understands that the generated code depends on the template files.
|
||||
let mut paths = self
|
||||
.contexts
|
||||
.keys()
|
||||
.map(|path| -> &Path { path })
|
||||
.collect::<Vec<_>>();
|
||||
paths.sort();
|
||||
let paths = paths
|
||||
.into_iter()
|
||||
.filter(|path| {
|
||||
.iter()
|
||||
.map(|(path, _ctx)| {
|
||||
(
|
||||
&***path,
|
||||
#[cfg(not(feature = "external-sources"))]
|
||||
(),
|
||||
#[cfg(feature = "external-sources")]
|
||||
_ctx,
|
||||
)
|
||||
})
|
||||
.filter(|&(path, _)| {
|
||||
// Skip the fake path of templates defined in rust source.
|
||||
match self.input.source {
|
||||
#[cfg(feature = "external-sources")]
|
||||
Source::Path(_) => true,
|
||||
Source::Source(_) => **path != *self.input.path,
|
||||
Source::Source(_) => *path != *self.input.path,
|
||||
}
|
||||
})
|
||||
.fold(TokenStream::new(), |mut acc, path| {
|
||||
let path = self.rel_path(path).display().to_string();
|
||||
acc.extend(quote_spanned!(span=>
|
||||
const _: &[askama::helpers::core::primitive::u8] =
|
||||
askama::helpers::core::include_bytes!(#path);
|
||||
));
|
||||
acc
|
||||
});
|
||||
.collect::<Vec<_>>();
|
||||
paths.sort_by_key(|&(path, _)| path);
|
||||
for (path, _ctx) in paths {
|
||||
let path = self.rel_path(path).display().to_string();
|
||||
paths_ts.extend(quote_spanned!(span=>
|
||||
const _: &[askama::helpers::core::primitive::u8] =
|
||||
askama::helpers::core::include_bytes!(#path);
|
||||
));
|
||||
|
||||
#[cfg(all(feature = "external-sources", feature = "nightly-spans"))]
|
||||
_ctx.resolve_path(&path);
|
||||
}
|
||||
|
||||
let mut content = Buffer::new();
|
||||
let size_hint = self.impl_template_inner(ctx, &mut content)?;
|
||||
@ -238,8 +247,7 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
helpers::{ResultConverter as _, core::fmt::Write as _},
|
||||
};
|
||||
|
||||
#full_paths
|
||||
#paths
|
||||
#paths_ts
|
||||
#content
|
||||
askama::Result::Ok(())
|
||||
}
|
||||
|
@ -177,6 +177,11 @@ impl<'a> Context<'a> {
|
||||
pub(crate) fn file_info_of(&self, node: Span) -> Option<FileInfo<'a>> {
|
||||
self.path.map(|path| FileInfo::of(node, path, self.parsed))
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "external-sources", feature = "nightly-spans"))]
|
||||
pub(crate) fn resolve_path(&self, path: &str) {
|
||||
self.literal.resolve_path(path);
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_top(
|
||||
|
@ -455,7 +455,7 @@ impl TemplateArgs {
|
||||
source: match args.source {
|
||||
#[cfg(feature = "external-sources")]
|
||||
Some(PartialTemplateArgsSource::Path(s)) => {
|
||||
(Source::Path(s.value().into()), SourceSpan::Path(s.span()))
|
||||
(Source::Path(s.value().into()), SourceSpan::from_path(s)?)
|
||||
}
|
||||
Some(PartialTemplateArgsSource::Source(s)) => {
|
||||
let (source, span) = SourceSpan::from_source(s)?;
|
||||
@ -463,7 +463,7 @@ impl TemplateArgs {
|
||||
}
|
||||
#[cfg(feature = "code-in-doc")]
|
||||
Some(PartialTemplateArgsSource::InDoc(span, source)) => {
|
||||
(source, SourceSpan::Span(span))
|
||||
(source, SourceSpan::CodeInDoc(span))
|
||||
}
|
||||
None => {
|
||||
return Err(CompileError::no_file_info(
|
||||
|
@ -1,6 +1,10 @@
|
||||
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
|
||||
#![deny(elided_lifetimes_in_paths)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![cfg_attr(
|
||||
all(feature = "external-sources", feature = "nightly-spans"),
|
||||
feature(proc_macro_def_site, proc_macro_expand)
|
||||
)]
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
|
@ -11,38 +11,53 @@ use crate::spans::rustc_literal_escaper::unescape;
|
||||
#[allow(private_interfaces)] // don't look behind the curtain
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum SourceSpan {
|
||||
Empty(Span),
|
||||
Source(SpannedSource),
|
||||
// TODO: transclude source file
|
||||
Path(Span),
|
||||
#[cfg(feature = "external-sources")]
|
||||
Path(SpannedPath),
|
||||
// TODO: implement for "code-in-doc"
|
||||
#[cfg_attr(not(feature = "code-in-doc"), allow(dead_code))]
|
||||
Span(Span),
|
||||
Empty(Span),
|
||||
CodeInDoc(Span),
|
||||
}
|
||||
|
||||
impl SourceSpan {
|
||||
pub(crate) fn empty() -> SourceSpan {
|
||||
Self::Empty(Span::call_site())
|
||||
}
|
||||
|
||||
pub(crate) fn from_source(source: LitStr) -> Result<(String, Self), CompileError> {
|
||||
let (source, span) = SpannedSource::from_source(source)?;
|
||||
let (source, span) = SpannedSource::new(source)?;
|
||||
Ok((source, Self::Source(span)))
|
||||
}
|
||||
|
||||
#[cfg(feature = "external-sources")]
|
||||
pub(crate) fn from_path(config: LitStr) -> Result<SourceSpan, CompileError> {
|
||||
Ok(Self::Path(SpannedPath::new(config)?))
|
||||
}
|
||||
|
||||
pub(crate) fn config_span(&self) -> Span {
|
||||
match self {
|
||||
SourceSpan::Source(literal) => literal.config_span(),
|
||||
SourceSpan::Path(span) | SourceSpan::Span(span) | Self::Empty(span) => *span,
|
||||
SourceSpan::Source(v) => v.config_span(),
|
||||
#[cfg(feature = "external-sources")]
|
||||
SourceSpan::Path(v) => v.config_span(),
|
||||
SourceSpan::CodeInDoc(span) | Self::Empty(span) => *span,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn content_subspan(&self, bytes: Range<usize>) -> Option<Span> {
|
||||
match self {
|
||||
Self::Source(source) => source.content_subspan(bytes),
|
||||
Self::Path(_) | Self::Span(_) => None,
|
||||
Self::Empty(_) => None,
|
||||
Self::Source(v) => v.content_subspan(bytes),
|
||||
#[cfg(feature = "external-sources")]
|
||||
SourceSpan::Path(v) => v.content_subspan(bytes),
|
||||
Self::CodeInDoc(_) | Self::Empty(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn empty() -> SourceSpan {
|
||||
Self::Empty(Span::call_site())
|
||||
#[cfg(all(feature = "external-sources", feature = "nightly-spans"))]
|
||||
pub(crate) fn resolve_path(&self, path: &str) {
|
||||
if let Self::Path(v) = self {
|
||||
v.resolve_path(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,7 +91,7 @@ impl SpannedSource {
|
||||
}
|
||||
}
|
||||
|
||||
fn from_source(source: LitStr) -> Result<(String, Self), CompileError> {
|
||||
fn new(source: LitStr) -> Result<(String, Self), CompileError> {
|
||||
let literal = source.token();
|
||||
let unparsed = literal.to_string();
|
||||
let result = if unparsed.starts_with('r') {
|
||||
@ -134,3 +149,114 @@ impl SpannedSource {
|
||||
Ok((source, Self { literal, positions }))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "external-sources")]
|
||||
#[cfg_attr(not(feature = "nightly-spans"), derive(Debug, Clone))]
|
||||
struct SpannedPath {
|
||||
config: Span,
|
||||
#[cfg(feature = "nightly-spans")]
|
||||
literal: std::cell::Cell<Option<Literal>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "external-sources")]
|
||||
impl SpannedPath {
|
||||
fn new(config: LitStr) -> Result<Self, CompileError> {
|
||||
Ok(Self {
|
||||
config: config.span(),
|
||||
#[cfg(feature = "nightly-spans")]
|
||||
literal: std::cell::Cell::new(None),
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn config_span(&self) -> Span {
|
||||
self.config
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "external-sources", not(feature = "nightly-spans")))]
|
||||
impl SpannedPath {
|
||||
#[inline]
|
||||
fn content_subspan(&self, _: Range<usize>) -> Option<Span> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "external-sources", feature = "nightly-spans"))]
|
||||
const _: () = {
|
||||
use std::cell::Cell;
|
||||
use std::fmt;
|
||||
|
||||
use proc_macro::TokenStream as TokenStream1;
|
||||
use proc_macro2::{TokenStream as TokenStream2, TokenTree};
|
||||
use quote::quote_spanned;
|
||||
|
||||
impl fmt::Debug for SpannedPath {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("SpannedPath")
|
||||
.field("config", &self.config)
|
||||
.field("span", &self.literal_span())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for SpannedPath {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
config: self.config.clone(),
|
||||
literal: Cell::new(self.literal()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpannedPath {
|
||||
fn content_subspan(&self, bytes: Range<usize>) -> Option<Span> {
|
||||
let literal = self.literal.take()?;
|
||||
let span = literal.subspan(bytes);
|
||||
self.literal.set(Some(literal));
|
||||
span
|
||||
}
|
||||
|
||||
fn literal_span(&self) -> Option<Span> {
|
||||
let literal = self.literal.take()?;
|
||||
let span = literal.span();
|
||||
self.literal.set(Some(literal));
|
||||
Some(span)
|
||||
}
|
||||
|
||||
fn literal(&self) -> Option<Literal> {
|
||||
let literal = self.literal.take()?;
|
||||
self.literal.set(Some(literal.clone()));
|
||||
Some(literal)
|
||||
}
|
||||
|
||||
fn resolve_path(&self, path: &str) {
|
||||
if proc_macro::is_available()
|
||||
&& let Ok(stream) = TokenStream1::from(quote_spanned! {
|
||||
// In token expansion, `extern crate some_name` does not work. Only crates that
|
||||
// were imported output _before_ the `#[derive(Template)]` invocation can be
|
||||
// used.
|
||||
//
|
||||
// In the macro expansion, using an identifier that was not defined will emit
|
||||
// an error `Diagnostic`. We cannot un-emit a `Diagnostic`, so this would be a
|
||||
// hard compilation error.
|
||||
//
|
||||
// At `call_site()`, macro `include_str!` may not exist (`#[no_implicit_prelude]`),
|
||||
// or may be shadowed. The symbol `askama` may not exist or be shadowed, too.
|
||||
//
|
||||
// At `def_site()`, the we know that the macro exists. We do not know if `core`
|
||||
// or `::core` exists, but the unprefixed macro `include_str!` does exist, and
|
||||
// it cannot be shadowed from outside of this function call.
|
||||
//
|
||||
// <https://doc.rust-lang.org/reference/names/preludes.html#r-names.preludes.lang.entities>
|
||||
proc_macro::Span::def_site().into() => include_str!(#path)
|
||||
})
|
||||
.expand_expr()
|
||||
&& let Some(TokenTree::Literal(literal)) =
|
||||
TokenStream2::from(stream).into_iter().next()
|
||||
{
|
||||
self.literal.set(Some(literal));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -32,6 +32,7 @@ alloc = ["askama_derive/alloc"]
|
||||
blocks = ["askama_derive/blocks"]
|
||||
code-in-doc = ["askama_derive/code-in-doc"]
|
||||
config = ["askama_derive/config"]
|
||||
nightly-spans = ["askama_derive/nightly-spans"]
|
||||
serde_json = ["askama_derive/serde_json"]
|
||||
std = ["askama_derive/std"]
|
||||
urlencode = ["askama_derive/urlencode"]
|
||||
|
@ -10,3 +10,6 @@ publish = false
|
||||
askama = { path = "../askama", version = "0.14.0", default-features = false, features = ["alloc", "derive"] }
|
||||
|
||||
assert_matches = "1.5.0"
|
||||
|
||||
[features]
|
||||
nightly-spans = ["askama/nightly-spans"]
|
||||
|
1
testing-alloc/templates/hello-world.html
Normal file
1
testing-alloc/templates/hello-world.html
Normal file
@ -0,0 +1 @@
|
||||
Hello {%- if let Some(user) = user? -%} , {{ user }} {%- endif -%}!
|
@ -7,7 +7,7 @@ use askama::Template;
|
||||
use assert_matches::assert_matches;
|
||||
|
||||
#[test]
|
||||
fn hello_world() {
|
||||
fn test_source() {
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
ext = "html",
|
||||
@ -17,30 +17,40 @@ fn hello_world() {
|
||||
user: Result<Option<&'a str>, CustomError>,
|
||||
}
|
||||
|
||||
test_common(|user| Hello { user });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path() {
|
||||
#[derive(Template)]
|
||||
#[template(path = "hello-world.html")]
|
||||
struct Hello<'a> {
|
||||
user: Result<Option<&'a str>, CustomError>,
|
||||
}
|
||||
|
||||
test_common(|user| Hello { user });
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn test_common<'a, T: Template + 'a>(hello: fn(Result<Option<&'a str>, CustomError>) -> T) {
|
||||
let mut buffer = [0; 32];
|
||||
|
||||
let tmpl = Hello { user: Ok(None) };
|
||||
let tmpl = hello(Ok(None));
|
||||
let mut cursor = Cursor::new(&mut buffer);
|
||||
assert_matches!(tmpl.render_into(&mut cursor), Ok(()));
|
||||
assert_eq!(cursor.finalize(), Ok("Hello!"));
|
||||
|
||||
let tmpl = Hello {
|
||||
user: Ok(Some("user")),
|
||||
};
|
||||
let tmpl = hello(Ok(Some("user")));
|
||||
let mut cursor = Cursor::new(&mut buffer);
|
||||
assert_matches!(tmpl.render_into(&mut cursor), Ok(()));
|
||||
assert_eq!(cursor.finalize(), Ok("Hello, user!"));
|
||||
|
||||
let tmpl = Hello {
|
||||
user: Ok(Some("<user>")),
|
||||
};
|
||||
let tmpl = hello(Ok(Some("<user>")));
|
||||
let mut cursor = Cursor::new(&mut buffer);
|
||||
assert_matches!(tmpl.render_into(&mut cursor), Ok(()));
|
||||
assert_eq!(cursor.finalize(), Ok("Hello, <user>!"));
|
||||
|
||||
let tmpl = Hello {
|
||||
user: Err(CustomError),
|
||||
};
|
||||
let tmpl = hello(Err(CustomError));
|
||||
let mut cursor = Cursor::new(&mut buffer);
|
||||
let err = match tmpl.render_into(&mut cursor) {
|
||||
Err(askama::Error::Custom(err)) => err,
|
||||
|
@ -10,3 +10,6 @@ publish = false
|
||||
askama = { path = "../askama", version = "0.14.0", default-features = false, features = ["derive"] }
|
||||
|
||||
assert_matches = "1.5.0"
|
||||
|
||||
[features]
|
||||
nightly-spans = ["askama/nightly-spans"]
|
||||
|
1
testing-no-std/templates/hello-world.html
Normal file
1
testing-no-std/templates/hello-world.html
Normal file
@ -0,0 +1 @@
|
||||
Hello {%- if let Some(user) = user? -%} , {{ user }} {%- endif -%}!
|
@ -7,7 +7,7 @@ use askama::Template;
|
||||
use assert_matches::assert_matches;
|
||||
|
||||
#[test]
|
||||
fn hello_world() {
|
||||
fn test_source() {
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
ext = "html",
|
||||
@ -17,30 +17,40 @@ fn hello_world() {
|
||||
user: Result<Option<&'a str>, fmt::Error>,
|
||||
}
|
||||
|
||||
test_common(|user| Hello { user })
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path() {
|
||||
#[derive(Template)]
|
||||
#[template(path = "hello-world.html")]
|
||||
struct Hello<'a> {
|
||||
user: Result<Option<&'a str>, fmt::Error>,
|
||||
}
|
||||
|
||||
test_common(|user| Hello { user })
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn test_common<'a, T: Template + 'a>(hello: fn(Result<Option<&'a str>, fmt::Error>) -> T) {
|
||||
let mut buffer = [0; 32];
|
||||
|
||||
let tmpl = Hello { user: Ok(None) };
|
||||
let tmpl = hello(Ok(None));
|
||||
let mut cursor = Cursor::new(&mut buffer);
|
||||
assert_matches!(tmpl.render_into(&mut cursor), Ok(()));
|
||||
assert_eq!(cursor.finalize(), Ok("Hello!"));
|
||||
|
||||
let tmpl = Hello {
|
||||
user: Ok(Some("user")),
|
||||
};
|
||||
let tmpl = hello(Ok(Some("user")));
|
||||
let mut cursor = Cursor::new(&mut buffer);
|
||||
assert_matches!(tmpl.render_into(&mut cursor), Ok(()));
|
||||
assert_eq!(cursor.finalize(), Ok("Hello, user!"));
|
||||
|
||||
let tmpl = Hello {
|
||||
user: Ok(Some("<user>")),
|
||||
};
|
||||
let tmpl = hello(Ok(Some("<user>")));
|
||||
let mut cursor = Cursor::new(&mut buffer);
|
||||
assert_matches!(tmpl.render_into(&mut cursor), Ok(()));
|
||||
assert_eq!(cursor.finalize(), Ok("Hello, <user>!"));
|
||||
|
||||
let tmpl = Hello {
|
||||
user: Err(fmt::Error),
|
||||
};
|
||||
let tmpl = hello(Err(fmt::Error));
|
||||
let mut cursor = Cursor::new(&mut buffer);
|
||||
assert_matches!(tmpl.render_into(&mut cursor), Err(askama::Error::Fmt));
|
||||
}
|
||||
|
@ -10,3 +10,6 @@ publish = false
|
||||
some_name = { package = "askama", path = "../askama", version = "0.14.0", default-features = false, features = ["derive"] }
|
||||
|
||||
assert_matches = "1.5.0"
|
||||
|
||||
[features]
|
||||
nightly-spans = ["some_name/nightly-spans"]
|
||||
|
1
testing-renamed/templates/hello-world.html
Normal file
1
testing-renamed/templates/hello-world.html
Normal file
@ -0,0 +1 @@
|
||||
Hello {%- if let Some(user) = user? -%} , {{ user }} {%- endif -%}!
|
@ -17,7 +17,7 @@ pub(crate) mod some {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hello_world() {
|
||||
fn test_source() {
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
ext = "html",
|
||||
@ -28,28 +28,41 @@ fn hello_world() {
|
||||
user: Result<Option<&'a str>, fmt::Error>,
|
||||
}
|
||||
|
||||
let tmpl = Hello { user: Ok(None) };
|
||||
test_common(|user| Hello { user });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path() {
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
path = "hello-world.html",
|
||||
askama = some::deeply::nested::path::with::some_name
|
||||
)]
|
||||
struct Hello<'a> {
|
||||
user: Result<Option<&'a str>, fmt::Error>,
|
||||
}
|
||||
|
||||
test_common(|user| Hello { user });
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn test_common<'a, T: Template + 'a>(hello: fn(Result<Option<&'a str>, fmt::Error>) -> T) {
|
||||
let tmpl = hello(Ok(None));
|
||||
let mut cursor = String::new();
|
||||
assert_matches!(tmpl.render_into(&mut cursor), Ok(()));
|
||||
assert_eq!(cursor, "Hello!");
|
||||
|
||||
let tmpl = Hello {
|
||||
user: Ok(Some("user")),
|
||||
};
|
||||
let tmpl = hello(Ok(Some("user")));
|
||||
let mut cursor = String::new();
|
||||
assert_matches!(tmpl.render_into(&mut cursor), Ok(()));
|
||||
assert_eq!(cursor, "Hello, user!");
|
||||
|
||||
let tmpl = Hello {
|
||||
user: Ok(Some("<user>")),
|
||||
};
|
||||
let tmpl = hello(Ok(Some("<user>")));
|
||||
let mut cursor = String::new();
|
||||
assert_matches!(tmpl.render_into(&mut cursor), Ok(()));
|
||||
assert_eq!(cursor, "Hello, <user>!");
|
||||
|
||||
let tmpl = Hello {
|
||||
user: Err(fmt::Error),
|
||||
};
|
||||
let tmpl = hello(Err(fmt::Error));
|
||||
let mut cursor = String::new();
|
||||
assert_matches!(tmpl.render_into(&mut cursor), Err(some_name::Error::Fmt));
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ trybuild = "1.0.100"
|
||||
default = ["blocks", "code-in-doc", "serde_json"]
|
||||
blocks = ["askama/blocks"]
|
||||
code-in-doc = ["askama/code-in-doc"]
|
||||
nightly-spans = ["askama/nightly-spans"]
|
||||
serde_json = ["dep:serde_json", "askama/serde_json"]
|
||||
|
||||
[lints.rust]
|
||||
|
@ -2,14 +2,26 @@
|
||||
|
||||
use ::askama::Template;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "hello.html")]
|
||||
struct HelloTemplate<'a> {
|
||||
name: &'a str,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn main() {
|
||||
fn test_source() {
|
||||
#[derive(Template)]
|
||||
#[template(ext = "html", source = "Hello, {{ name }}!")]
|
||||
struct HelloTemplate<'a> {
|
||||
name: &'a str,
|
||||
}
|
||||
|
||||
let hello = HelloTemplate { name: "world" };
|
||||
::std::assert_eq!("Hello, world!", hello.render().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path() {
|
||||
#[derive(Template)]
|
||||
#[template(path = "hello.html")]
|
||||
struct HelloTemplate<'a> {
|
||||
name: &'a str,
|
||||
}
|
||||
|
||||
let hello = HelloTemplate { name: "world" };
|
||||
::std::assert_eq!("Hello, world!", hello.render().unwrap());
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user