mirror of
https://github.com/askama-rs/askama.git
synced 2025-09-28 13:30:59 +00:00
Merge pull request #260 from Kijewski/pr-stuff-in-derive
derive: some clean-ups and optimizations
This commit is contained in:
commit
e4b89797b0
@ -20,7 +20,7 @@ pub(crate) struct Config {
|
||||
pub(crate) syntaxes: BTreeMap<String, SyntaxAndCache<'static>>,
|
||||
pub(crate) default_syntax: &'static str,
|
||||
pub(crate) escapers: Vec<(Vec<Cow<'static, str>>, Cow<'static, str>)>,
|
||||
pub(crate) whitespace: WhitespaceHandling,
|
||||
pub(crate) whitespace: Whitespace,
|
||||
// `Config` is self referential and `_key` owns it data, so it must come last
|
||||
_key: OwnedConfigKey,
|
||||
}
|
||||
@ -39,7 +39,7 @@ struct OwnedConfigKey(&'static ConfigKey<'static>);
|
||||
struct ConfigKey<'a> {
|
||||
source: Cow<'a, str>,
|
||||
config_path: Option<Cow<'a, str>>,
|
||||
template_whitespace: Option<Cow<'a, str>>,
|
||||
template_whitespace: Option<Whitespace>,
|
||||
}
|
||||
|
||||
impl<'a> ToOwned for ConfigKey<'a> {
|
||||
@ -52,10 +52,7 @@ impl<'a> ToOwned for ConfigKey<'a> {
|
||||
.config_path
|
||||
.as_ref()
|
||||
.map(|s| Cow::Owned(s.as_ref().to_owned())),
|
||||
template_whitespace: self
|
||||
.template_whitespace
|
||||
.as_ref()
|
||||
.map(|s| Cow::Owned(s.as_ref().to_owned())),
|
||||
template_whitespace: self.template_whitespace,
|
||||
};
|
||||
OwnedConfigKey(Box::leak(Box::new(owned_key)))
|
||||
}
|
||||
@ -72,7 +69,7 @@ impl Config {
|
||||
pub(crate) fn new(
|
||||
source: &str,
|
||||
config_path: Option<&str>,
|
||||
template_whitespace: Option<&str>,
|
||||
template_whitespace: Option<Whitespace>,
|
||||
config_span: Option<Span>,
|
||||
) -> Result<&'static Config, CompileError> {
|
||||
static CACHE: ManuallyDrop<OnceLock<OnceMap<OwnedConfigKey, &'static Config>>> =
|
||||
@ -81,7 +78,7 @@ impl Config {
|
||||
&ConfigKey {
|
||||
source: source.into(),
|
||||
config_path: config_path.map(Cow::Borrowed),
|
||||
template_whitespace: template_whitespace.map(Cow::Borrowed),
|
||||
template_whitespace,
|
||||
},
|
||||
|key| {
|
||||
let config = Config::new_uncached(key.to_owned(), config_span)?;
|
||||
@ -100,7 +97,6 @@ impl Config {
|
||||
) -> Result<Config, CompileError> {
|
||||
let s = key.0.source.as_ref();
|
||||
let config_path = key.0.config_path.as_deref();
|
||||
let template_whitespace = key.0.template_whitespace.as_deref();
|
||||
|
||||
let root = manifest_root();
|
||||
let default_dirs = vec![root.join("templates")];
|
||||
@ -114,7 +110,7 @@ impl Config {
|
||||
RawConfig::from_toml_str(s)?
|
||||
};
|
||||
|
||||
let (dirs, default_syntax, mut whitespace) = match raw.general {
|
||||
let (dirs, default_syntax, whitespace) = match raw.general {
|
||||
Some(General {
|
||||
dirs,
|
||||
default_syntax,
|
||||
@ -126,26 +122,10 @@ impl Config {
|
||||
default_syntax.unwrap_or(DEFAULT_SYNTAX_NAME),
|
||||
whitespace,
|
||||
),
|
||||
None => (
|
||||
default_dirs,
|
||||
DEFAULT_SYNTAX_NAME,
|
||||
WhitespaceHandling::default(),
|
||||
),
|
||||
None => (default_dirs, DEFAULT_SYNTAX_NAME, Whitespace::default()),
|
||||
};
|
||||
let file_info = config_path.map(|path| FileInfo::new(Path::new(path), None, None));
|
||||
if let Some(template_whitespace) = template_whitespace {
|
||||
whitespace = match template_whitespace {
|
||||
"suppress" => WhitespaceHandling::Suppress,
|
||||
"minimize" => WhitespaceHandling::Minimize,
|
||||
"preserve" => WhitespaceHandling::Preserve,
|
||||
s => {
|
||||
return Err(CompileError::new(
|
||||
format!("invalid value for `whitespace`: \"{s}\""),
|
||||
file_info,
|
||||
));
|
||||
}
|
||||
};
|
||||
}
|
||||
let whitespace = key.0.template_whitespace.unwrap_or(whitespace);
|
||||
|
||||
if let Some(raw_syntaxes) = raw.syntax {
|
||||
for raw_s in raw_syntaxes {
|
||||
@ -327,38 +307,13 @@ impl RawConfig<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash)]
|
||||
#[cfg_attr(feature = "config", derive(Deserialize))]
|
||||
#[cfg_attr(feature = "config", serde(field_identifier, rename_all = "lowercase"))]
|
||||
pub(crate) enum WhitespaceHandling {
|
||||
/// The default behavior. It will leave the whitespace characters "as is".
|
||||
#[default]
|
||||
Preserve,
|
||||
/// It'll remove all the whitespace characters before and after the jinja block.
|
||||
Suppress,
|
||||
/// It'll remove all the whitespace characters except one before and after the jinja blocks.
|
||||
/// If there is a newline character, the preserved character in the trimmed characters, it will
|
||||
/// the one preserved.
|
||||
Minimize,
|
||||
}
|
||||
|
||||
impl From<WhitespaceHandling> for Whitespace {
|
||||
fn from(ws: WhitespaceHandling) -> Self {
|
||||
match ws {
|
||||
WhitespaceHandling::Suppress => Whitespace::Suppress,
|
||||
WhitespaceHandling::Preserve => Whitespace::Preserve,
|
||||
WhitespaceHandling::Minimize => Whitespace::Minimize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "config", derive(Deserialize))]
|
||||
struct General<'a> {
|
||||
#[cfg_attr(feature = "config", serde(borrow))]
|
||||
dirs: Option<Vec<&'a str>>,
|
||||
default_syntax: Option<&'a str>,
|
||||
#[cfg_attr(feature = "config", serde(default))]
|
||||
whitespace: WhitespaceHandling,
|
||||
whitespace: Whitespace,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "config", derive(Deserialize))]
|
||||
@ -704,10 +659,10 @@ mod tests {
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(config.whitespace, WhitespaceHandling::Suppress);
|
||||
assert_eq!(config.whitespace, Whitespace::Suppress);
|
||||
|
||||
let config = Config::new(r#""#, None, None, None).unwrap();
|
||||
assert_eq!(config.whitespace, WhitespaceHandling::Preserve);
|
||||
assert_eq!(config.whitespace, Whitespace::Preserve);
|
||||
|
||||
let config = Config::new(
|
||||
r#"
|
||||
@ -719,7 +674,7 @@ mod tests {
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(config.whitespace, WhitespaceHandling::Preserve);
|
||||
assert_eq!(config.whitespace, Whitespace::Preserve);
|
||||
|
||||
let config = Config::new(
|
||||
r#"
|
||||
@ -731,7 +686,7 @@ mod tests {
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(config.whitespace, WhitespaceHandling::Minimize);
|
||||
assert_eq!(config.whitespace, Whitespace::Minimize);
|
||||
}
|
||||
|
||||
#[cfg(feature = "config")]
|
||||
@ -739,30 +694,20 @@ mod tests {
|
||||
fn test_whitespace_in_template() {
|
||||
// Checking that template arguments have precedence over general configuration.
|
||||
// So in here, in the template arguments, there is `whitespace = "minimize"` so
|
||||
// the `WhitespaceHandling` should be `Minimize` as well.
|
||||
// the `Whitespace` should be `Minimize` as well.
|
||||
let config = Config::new(
|
||||
r#"
|
||||
[general]
|
||||
whitespace = "suppress"
|
||||
"#,
|
||||
None,
|
||||
Some("minimize"),
|
||||
Some(Whitespace::Minimize),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(config.whitespace, WhitespaceHandling::Minimize);
|
||||
assert_eq!(config.whitespace, Whitespace::Minimize);
|
||||
|
||||
let config = Config::new(r#""#, None, Some("minimize"), None).unwrap();
|
||||
assert_eq!(config.whitespace, WhitespaceHandling::Minimize);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_whitespace_error() {
|
||||
let config = Config::new(r"", None, Some("trim"), None);
|
||||
if let Err(err) = config {
|
||||
assert_eq!(err.msg, "invalid value for `whitespace`: \"trim\"");
|
||||
} else {
|
||||
panic!("Config::new should have return an error");
|
||||
}
|
||||
let config = Config::new(r#""#, None, Some(Whitespace::Minimize), None).unwrap();
|
||||
assert_eq!(config.whitespace, Whitespace::Minimize);
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ use parser::{
|
||||
};
|
||||
use rustc_hash::FxBuildHasher;
|
||||
|
||||
use crate::config::WhitespaceHandling;
|
||||
use crate::heritage::{Context, Heritage};
|
||||
use crate::html::write_escaped_str;
|
||||
use crate::input::{Source, TemplateInput};
|
||||
@ -72,7 +71,7 @@ struct Generator<'a, 'h> {
|
||||
next_ws: Option<&'a str>,
|
||||
// Whitespace suppression from the previous non-literal. Will be used to
|
||||
// determine whether to flush prefix whitespace from the next literal.
|
||||
skip_ws: WhitespaceHandling,
|
||||
skip_ws: Whitespace,
|
||||
// If currently in a block, this will contain the name of a potential parent block
|
||||
super_block: Option<(&'a str, usize)>,
|
||||
// Buffer for writable
|
||||
@ -96,7 +95,7 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
heritage,
|
||||
locals,
|
||||
next_ws: None,
|
||||
skip_ws: WhitespaceHandling::Preserve,
|
||||
skip_ws: Whitespace::Preserve,
|
||||
super_block: None,
|
||||
buf_writable: WritableBuffer {
|
||||
discard: buf_writable_discard,
|
||||
@ -328,7 +327,7 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
if AstLevel::Top == level {
|
||||
// Handle any pending whitespace.
|
||||
if self.next_ws.is_some() {
|
||||
self.flush_ws(Ws(Some(self.skip_ws.into()), None));
|
||||
self.flush_ws(Ws(Some(self.skip_ws), None));
|
||||
}
|
||||
|
||||
size_hint += self.write_buf_writable(ctx, buf)?;
|
||||
@ -758,20 +757,22 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
|
||||
let (def, own_ctx) = if let Some(s) = scope {
|
||||
let path = ctx.imports.get(s).ok_or_else(|| {
|
||||
ctx.generate_error(&format!("no import found for scope {s:?}"), call)
|
||||
ctx.generate_error(format_args!("no import found for scope {s:?}"), call)
|
||||
})?;
|
||||
let mctx = self.contexts.get(path).ok_or_else(|| {
|
||||
ctx.generate_error(&format!("context for {path:?} not found"), call)
|
||||
ctx.generate_error(format_args!("context for {path:?} not found"), call)
|
||||
})?;
|
||||
let def = mctx.macros.get(name).ok_or_else(|| {
|
||||
ctx.generate_error(&format!("macro {name:?} not found in scope {s:?}"), call)
|
||||
ctx.generate_error(
|
||||
format_args!("macro {name:?} not found in scope {s:?}"),
|
||||
call,
|
||||
)
|
||||
})?;
|
||||
(def, mctx)
|
||||
} else {
|
||||
let def = ctx
|
||||
.macros
|
||||
.get(name)
|
||||
.ok_or_else(|| ctx.generate_error(&format!("macro {name:?} not found"), call))?;
|
||||
let def = ctx.macros.get(name).ok_or_else(|| {
|
||||
ctx.generate_error(format_args!("macro {name:?} not found"), call)
|
||||
})?;
|
||||
(def, ctx)
|
||||
};
|
||||
|
||||
@ -794,7 +795,7 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
};
|
||||
if !def.args.iter().any(|(arg, _)| arg == arg_name) {
|
||||
return Err(ctx.generate_error(
|
||||
&format!("no argument named `{arg_name}` in macro {name:?}"),
|
||||
format_args!("no argument named `{arg_name}` in macro {name:?}"),
|
||||
call,
|
||||
));
|
||||
}
|
||||
@ -825,7 +826,7 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
// to use unnamed ones at this point anymore.
|
||||
if !allow_positional {
|
||||
return Err(ctx.generate_error(
|
||||
&format!(
|
||||
format_args!(
|
||||
"cannot have unnamed argument (`{arg}`) after named argument \
|
||||
in call to macro {name:?}"
|
||||
),
|
||||
@ -837,7 +838,7 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
Some(arg_expr) if used_named_args[index] => {
|
||||
let Expr::NamedArgument(name, _) = **arg_expr else { unreachable!() };
|
||||
return Err(ctx.generate_error(
|
||||
&format!("`{name}` is passed more than once"),
|
||||
format_args!("`{name}` is passed more than once"),
|
||||
call,
|
||||
));
|
||||
}
|
||||
@ -845,7 +846,7 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
if let Some(default_value) = default_value {
|
||||
default_value
|
||||
} else {
|
||||
return Err(ctx.generate_error(&format!("missing `{arg}` argument"), call));
|
||||
return Err(ctx.generate_error(format_args!("missing `{arg}` argument"), call));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1129,7 +1130,7 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
// A block definition contains a block definition of the same name
|
||||
(Some(cur_name), Some((prev_name, _))) if cur_name == prev_name => {
|
||||
return Err(ctx.generate_error(
|
||||
&format!("cannot define recursive blocks ({cur_name})"),
|
||||
format_args!("cannot define recursive blocks ({cur_name})"),
|
||||
node,
|
||||
));
|
||||
}
|
||||
@ -1325,15 +1326,15 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
let Lit { lws, val, rws } = *lit;
|
||||
if !lws.is_empty() {
|
||||
match self.skip_ws {
|
||||
WhitespaceHandling::Suppress => {}
|
||||
Whitespace::Suppress => {}
|
||||
_ if val.is_empty() => {
|
||||
assert!(rws.is_empty());
|
||||
self.next_ws = Some(lws);
|
||||
}
|
||||
WhitespaceHandling::Preserve => {
|
||||
Whitespace::Preserve => {
|
||||
self.buf_writable.push(Writable::Lit(Cow::Borrowed(lws)));
|
||||
}
|
||||
WhitespaceHandling::Minimize => {
|
||||
Whitespace::Minimize => {
|
||||
self.buf_writable.push(Writable::Lit(Cow::Borrowed(
|
||||
match lws.contains('\n') {
|
||||
true => "\n",
|
||||
@ -1345,7 +1346,7 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
}
|
||||
|
||||
if !val.is_empty() {
|
||||
self.skip_ws = WhitespaceHandling::Preserve;
|
||||
self.skip_ws = Whitespace::Preserve;
|
||||
self.buf_writable.push(Writable::Lit(Cow::Borrowed(val)));
|
||||
}
|
||||
|
||||
@ -1577,7 +1578,7 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
) -> Result<DisplayWrap, CompileError> {
|
||||
if cfg!(not(feature = "urlencode")) {
|
||||
return Err(ctx.generate_error(
|
||||
&format!("the `{name}` filter requires the `urlencode` feature to be enabled"),
|
||||
format_args!("the `{name}` filter requires the `urlencode` feature to be enabled"),
|
||||
node,
|
||||
));
|
||||
}
|
||||
@ -1666,9 +1667,10 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
node: &WithSpan<'_, T>,
|
||||
) -> Result<DisplayWrap, CompileError> {
|
||||
if args.len() != 1 {
|
||||
return Err(
|
||||
ctx.generate_error(&format!("unexpected argument(s) in `{name}` filter"), node)
|
||||
);
|
||||
return Err(ctx.generate_error(
|
||||
format_args!("unexpected argument(s) in `{name}` filter"),
|
||||
node,
|
||||
));
|
||||
}
|
||||
buf.write(format_args!(
|
||||
"rinja::filters::{name}(&(&&rinja::filters::AutoEscaper::new(&(",
|
||||
@ -1778,7 +1780,7 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
"CStr"
|
||||
};
|
||||
return Err(ctx.generate_error(
|
||||
&format!(
|
||||
format_args!(
|
||||
"invalid escaper `b{content:?}`. Expected a string, found a {kind}"
|
||||
),
|
||||
&args[1],
|
||||
@ -1804,7 +1806,7 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
ctx.generate_error(
|
||||
&format!(
|
||||
format_args!(
|
||||
"invalid escaper '{name}' for `escape` filter. {}",
|
||||
MsgValidEscapers(&self.input.config.escapers),
|
||||
),
|
||||
@ -2053,7 +2055,11 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
);
|
||||
}
|
||||
},
|
||||
s => return Err(ctx.generate_error(&format!("unknown loop method: {s:?}"), left)),
|
||||
s => {
|
||||
return Err(
|
||||
ctx.generate_error(format_args!("unknown loop method: {s:?}"), left)
|
||||
);
|
||||
}
|
||||
},
|
||||
sub_left => {
|
||||
match sub_left {
|
||||
@ -2342,13 +2348,8 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
self.prepare_ws(ws);
|
||||
}
|
||||
|
||||
fn should_trim_ws(&self, ws: Option<Whitespace>) -> WhitespaceHandling {
|
||||
match ws {
|
||||
Some(Whitespace::Suppress) => WhitespaceHandling::Suppress,
|
||||
Some(Whitespace::Preserve) => WhitespaceHandling::Preserve,
|
||||
Some(Whitespace::Minimize) => WhitespaceHandling::Minimize,
|
||||
None => self.input.config.whitespace,
|
||||
}
|
||||
fn should_trim_ws(&self, ws: Option<Whitespace>) -> Whitespace {
|
||||
ws.unwrap_or(self.input.config.whitespace)
|
||||
}
|
||||
|
||||
// If the previous literal left some trailing whitespace in `next_ws` and the
|
||||
@ -2362,13 +2363,13 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
// If `whitespace` is set to `suppress`, we keep the whitespace characters only if there is
|
||||
// a `+` character.
|
||||
match self.should_trim_ws(ws.0) {
|
||||
WhitespaceHandling::Preserve => {
|
||||
Whitespace::Preserve => {
|
||||
let val = self.next_ws.unwrap();
|
||||
if !val.is_empty() {
|
||||
self.buf_writable.push(Writable::Lit(Cow::Borrowed(val)));
|
||||
}
|
||||
}
|
||||
WhitespaceHandling::Minimize => {
|
||||
Whitespace::Minimize => {
|
||||
let val = self.next_ws.unwrap();
|
||||
if !val.is_empty() {
|
||||
self.buf_writable.push(Writable::Lit(Cow::Borrowed(
|
||||
@ -2379,7 +2380,7 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
)));
|
||||
}
|
||||
}
|
||||
WhitespaceHandling::Suppress => {}
|
||||
Whitespace::Suppress => {}
|
||||
}
|
||||
self.next_ws = None;
|
||||
}
|
||||
@ -2419,7 +2420,7 @@ fn macro_call_ensure_arg_count(
|
||||
_ => (nb_default_args, "at least "),
|
||||
};
|
||||
Err(ctx.generate_error(
|
||||
&format!(
|
||||
format_args!(
|
||||
"macro {:?} expected {extra}{expected_args} argument{}, found {}",
|
||||
def.name,
|
||||
if expected_args != 1 { "s" } else { "" },
|
||||
|
@ -1,3 +1,4 @@
|
||||
use core::fmt;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
@ -140,7 +141,11 @@ impl Context<'_> {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn generate_error<T>(&self, msg: &str, node: &WithSpan<'_, T>) -> CompileError {
|
||||
pub(crate) fn generate_error<T>(
|
||||
&self,
|
||||
msg: impl fmt::Display,
|
||||
node: &WithSpan<'_, T>,
|
||||
) -> CompileError {
|
||||
CompileError::new(
|
||||
msg,
|
||||
self.path.map(|path| FileInfo::of(node, path, self.parsed)),
|
||||
|
@ -7,6 +7,7 @@ use std::str::FromStr;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use mime::Mime;
|
||||
use parser::node::Whitespace;
|
||||
use parser::{Node, Parsed};
|
||||
use proc_macro2::Span;
|
||||
use rustc_hash::FxBuildHasher;
|
||||
@ -282,7 +283,7 @@ pub(crate) struct TemplateArgs {
|
||||
ext_span: Option<Span>,
|
||||
syntax: Option<String>,
|
||||
config: Option<String>,
|
||||
pub(crate) whitespace: Option<String>,
|
||||
pub(crate) whitespace: Option<Whitespace>,
|
||||
pub(crate) template_span: Option<Span>,
|
||||
pub(crate) config_span: Option<Span>,
|
||||
}
|
||||
@ -307,15 +308,15 @@ impl TemplateArgs {
|
||||
};
|
||||
Ok(Self {
|
||||
source: match args.source {
|
||||
Some((_, PartialTemplateArgsSource::Path(s))) => {
|
||||
Some(PartialTemplateArgsSource::Path(s)) => {
|
||||
(Source::Path(s.value()), Some(s.span()))
|
||||
}
|
||||
Some((_, PartialTemplateArgsSource::Source(s))) => {
|
||||
Some(PartialTemplateArgsSource::Source(s)) => {
|
||||
(Source::Source(s.value().into()), Some(s.span()))
|
||||
}
|
||||
#[cfg(feature = "code-in-doc")]
|
||||
Some((ident, PartialTemplateArgsSource::InDoc(_))) => {
|
||||
source_from_docs(&ident, &args.meta_docs, ast)?
|
||||
Some(PartialTemplateArgsSource::InDoc(s)) => {
|
||||
source_from_docs(s.span(), &args.meta_docs, ast)?
|
||||
}
|
||||
None => {
|
||||
return Err(CompileError::no_file_info(
|
||||
@ -327,16 +328,16 @@ impl TemplateArgs {
|
||||
));
|
||||
}
|
||||
},
|
||||
block: args.block.map(|(_, value)| value.value()),
|
||||
print: args.print.map(|(_, _, value)| value).unwrap_or_default(),
|
||||
escaping: args.escape.map(|(_, value)| value.value()),
|
||||
ext: args.ext.as_ref().map(|(_, s)| s.value()),
|
||||
ext_span: args.ext.as_ref().map(|(_, s)| s.span()),
|
||||
syntax: args.syntax.map(|(_, value)| value.value()),
|
||||
config: args.config.as_ref().map(|(_, s)| s.value()),
|
||||
whitespace: args.whitespace.map(|(_, value)| value.value()),
|
||||
block: args.block.map(|value| value.value()),
|
||||
print: args.print.unwrap_or_default(),
|
||||
escaping: args.escape.map(|value| value.value()),
|
||||
ext: args.ext.as_ref().map(|value| value.value()),
|
||||
ext_span: args.ext.as_ref().map(|value| value.span()),
|
||||
syntax: args.syntax.map(|value| value.value()),
|
||||
config: args.config.as_ref().map(|value| value.value()),
|
||||
whitespace: args.whitespace,
|
||||
template_span: Some(template.span()),
|
||||
config_span: args.config.as_ref().map(|(_, s)| s.span()),
|
||||
config_span: args.config.as_ref().map(|value| value.span()),
|
||||
})
|
||||
}
|
||||
|
||||
@ -366,33 +367,33 @@ impl TemplateArgs {
|
||||
/// This is only done if no path or source was given in the `#[template]` attribute.
|
||||
#[cfg(feature = "code-in-doc")]
|
||||
fn source_from_docs(
|
||||
name: &Ident,
|
||||
span: Span,
|
||||
docs: &[Attribute],
|
||||
ast: &syn::DeriveInput,
|
||||
) -> Result<(Source, Option<Span>), CompileError> {
|
||||
let (span, source) = collect_comment_blocks(name, docs, ast)?;
|
||||
let (source_span, source) = collect_comment_blocks(span, docs, ast)?;
|
||||
let source = strip_common_ws_prefix(source);
|
||||
let source = collect_rinja_code_blocks(name, ast, source)?;
|
||||
Ok((source, span))
|
||||
let source = collect_rinja_code_blocks(span, ast, source)?;
|
||||
Ok((source, source_span))
|
||||
}
|
||||
|
||||
#[cfg(feature = "code-in-doc")]
|
||||
fn collect_comment_blocks(
|
||||
name: &Ident,
|
||||
span: Span,
|
||||
docs: &[Attribute],
|
||||
ast: &syn::DeriveInput,
|
||||
) -> Result<(Option<Span>, String), CompileError> {
|
||||
let mut span: Option<Span> = None;
|
||||
let mut source_span: Option<Span> = None;
|
||||
let mut assign_span = |kv: &syn::MetaNameValue| {
|
||||
// FIXME: uncomment once <https://github.com/rust-lang/rust/issues/54725> is stable
|
||||
// let new_span = kv.path.span();
|
||||
// span = Some(match span {
|
||||
// source_span = Some(match source_span {
|
||||
// Some(cur_span) => cur_span.join(new_span).unwrap_or(cur_span),
|
||||
// None => new_span,
|
||||
// });
|
||||
|
||||
if span.is_none() {
|
||||
span = Some(kv.path.span());
|
||||
if source_span.is_none() {
|
||||
source_span = Some(kv.path.span());
|
||||
}
|
||||
};
|
||||
|
||||
@ -424,14 +425,14 @@ fn collect_comment_blocks(
|
||||
source.push('\n');
|
||||
}
|
||||
if source.is_empty() {
|
||||
return Err(no_rinja_code_block(name, ast));
|
||||
return Err(no_rinja_code_block(span, ast));
|
||||
}
|
||||
|
||||
Ok((span, source))
|
||||
Ok((source_span, source))
|
||||
}
|
||||
|
||||
#[cfg(feature = "code-in-doc")]
|
||||
fn no_rinja_code_block(name: &Ident, ast: &syn::DeriveInput) -> CompileError {
|
||||
fn no_rinja_code_block(span: Span, ast: &syn::DeriveInput) -> CompileError {
|
||||
let kind = match &ast.data {
|
||||
syn::Data::Struct(_) => "struct",
|
||||
syn::Data::Enum(_) => "enum",
|
||||
@ -440,9 +441,10 @@ fn no_rinja_code_block(name: &Ident, ast: &syn::DeriveInput) -> CompileError {
|
||||
};
|
||||
CompileError::no_file_info(
|
||||
format!(
|
||||
"when using `in_doc = true`, the {kind}'s documentation needs a `rinja` code block"
|
||||
"when using `in_doc` with the value `true`, the {kind}'s documentation needs a \
|
||||
`rinja` code block"
|
||||
),
|
||||
Some(name.span()),
|
||||
Some(span),
|
||||
)
|
||||
}
|
||||
|
||||
@ -476,7 +478,7 @@ fn strip_common_ws_prefix(source: String) -> String {
|
||||
|
||||
#[cfg(feature = "code-in-doc")]
|
||||
fn collect_rinja_code_blocks(
|
||||
name: &Ident,
|
||||
span: Span,
|
||||
ast: &syn::DeriveInput,
|
||||
source: String,
|
||||
) -> Result<Source, CompileError> {
|
||||
@ -499,7 +501,7 @@ fn collect_rinja_code_blocks(
|
||||
}
|
||||
}
|
||||
if !had_rinja_code {
|
||||
return Err(no_rinja_code_block(name, ast));
|
||||
return Err(no_rinja_code_block(span, ast));
|
||||
}
|
||||
|
||||
if tmpl_source.ends_with('\n') {
|
||||
@ -651,14 +653,14 @@ pub(crate) fn get_template_source(
|
||||
pub(crate) struct PartialTemplateArgs {
|
||||
pub(crate) template: Option<Ident>,
|
||||
pub(crate) meta_docs: Vec<Attribute>,
|
||||
pub(crate) source: Option<(Ident, PartialTemplateArgsSource)>,
|
||||
pub(crate) block: Option<(Ident, LitStr)>,
|
||||
pub(crate) print: Option<(Ident, LitStr, Print)>,
|
||||
pub(crate) escape: Option<(Ident, LitStr)>,
|
||||
pub(crate) ext: Option<(Ident, LitStr)>,
|
||||
pub(crate) syntax: Option<(Ident, LitStr)>,
|
||||
pub(crate) config: Option<(Ident, LitStr)>,
|
||||
pub(crate) whitespace: Option<(Ident, LitStr)>,
|
||||
pub(crate) source: Option<PartialTemplateArgsSource>,
|
||||
pub(crate) block: Option<LitStr>,
|
||||
pub(crate) print: Option<Print>,
|
||||
pub(crate) escape: Option<LitStr>,
|
||||
pub(crate) ext: Option<LitStr>,
|
||||
pub(crate) syntax: Option<LitStr>,
|
||||
pub(crate) config: Option<LitStr>,
|
||||
pub(crate) whitespace: Option<Whitespace>,
|
||||
}
|
||||
|
||||
pub(crate) enum PartialTemplateArgsSource {
|
||||
@ -719,12 +721,11 @@ const _: () = {
|
||||
|
||||
if ident == "path" {
|
||||
ensure_source_only_once(ident, &this.source)?;
|
||||
let value = get_strlit(ident, value)?;
|
||||
this.source = Some((ident.clone(), PartialTemplateArgsSource::Path(value)));
|
||||
this.source = Some(PartialTemplateArgsSource::Path(get_strlit(ident, value)?));
|
||||
} else if ident == "source" {
|
||||
ensure_source_only_once(ident, &this.source)?;
|
||||
let value = get_strlit(ident, value)?;
|
||||
this.source = Some((ident.clone(), PartialTemplateArgsSource::Source(value)));
|
||||
this.source =
|
||||
Some(PartialTemplateArgsSource::Source(get_strlit(ident, value)?));
|
||||
} else if ident == "in_doc" {
|
||||
let value = get_boollit(ident, value)?;
|
||||
if !value.value() {
|
||||
@ -741,19 +742,12 @@ const _: () = {
|
||||
}
|
||||
#[cfg(feature = "code-in-doc")]
|
||||
{
|
||||
this.source =
|
||||
Some((ident.clone(), PartialTemplateArgsSource::InDoc(value)));
|
||||
this.source = Some(PartialTemplateArgsSource::InDoc(value));
|
||||
}
|
||||
} else if ident == "block" {
|
||||
set_strlit_pair(ident, value, &mut this.block)?;
|
||||
} else if ident == "print" {
|
||||
ensure_only_once(ident, &mut this.print)?;
|
||||
let str_value = get_strlit(ident, value)?;
|
||||
let value = str_value
|
||||
.value()
|
||||
.parse()
|
||||
.map_err(|msg| CompileError::no_file_info(msg, Some(ident.span())))?;
|
||||
this.print = Some((ident.clone(), str_value, value));
|
||||
set_parseable_string(ident, value, &mut this.print)?;
|
||||
} else if ident == "escape" {
|
||||
set_strlit_pair(ident, value, &mut this.escape)?;
|
||||
} else if ident == "ext" {
|
||||
@ -763,7 +757,7 @@ const _: () = {
|
||||
} else if ident == "config" {
|
||||
set_strlit_pair(ident, value, &mut this.config)?;
|
||||
} else if ident == "whitespace" {
|
||||
set_strlit_pair(ident, value, &mut this.whitespace)?;
|
||||
set_parseable_string(ident, value, &mut this.whitespace)?;
|
||||
} else {
|
||||
return Err(CompileError::no_file_info(
|
||||
format!("unsupported template attribute `{ident}` found"),
|
||||
@ -778,11 +772,26 @@ const _: () = {
|
||||
fn set_strlit_pair(
|
||||
name: &Ident,
|
||||
value: ExprLit,
|
||||
dest: &mut Option<(Ident, LitStr)>,
|
||||
dest: &mut Option<LitStr>,
|
||||
) -> Result<(), CompileError> {
|
||||
ensure_only_once(name, dest)?;
|
||||
let value = get_strlit(name, value)?;
|
||||
*dest = Some((name.clone(), value));
|
||||
*dest = Some(get_strlit(name, value)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_parseable_string<T: FromStr<Err: ToString>>(
|
||||
name: &Ident,
|
||||
value: ExprLit,
|
||||
dest: &mut Option<T>,
|
||||
) -> Result<(), CompileError> {
|
||||
ensure_only_once(name, dest)?;
|
||||
let str_value = get_strlit(name, value)?;
|
||||
*dest = Some(
|
||||
str_value
|
||||
.value()
|
||||
.parse()
|
||||
.map_err(|msg| CompileError::no_file_info(msg, Some(str_value.span())))?,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -836,7 +845,7 @@ const _: () = {
|
||||
|
||||
fn ensure_source_only_once(
|
||||
name: &Ident,
|
||||
source: &Option<(Ident, PartialTemplateArgsSource)>,
|
||||
source: &Option<PartialTemplateArgsSource>,
|
||||
) -> Result<(), CompileError> {
|
||||
if source.is_some() {
|
||||
return Err(CompileError::no_file_info(
|
||||
|
@ -128,11 +128,7 @@ pub fn derive_template(input: TokenStream12) -> TokenStream12 {
|
||||
};
|
||||
match build_template(&ast) {
|
||||
Ok(source) => source.parse().unwrap(),
|
||||
Err(CompileError {
|
||||
msg,
|
||||
span,
|
||||
rendered: _rendered,
|
||||
}) => {
|
||||
Err(CompileError { msg, span }) => {
|
||||
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();
|
||||
@ -191,7 +187,7 @@ fn build_template_inner(
|
||||
let config = Config::new(
|
||||
&s,
|
||||
config_path,
|
||||
template_args.whitespace.as_deref(),
|
||||
template_args.whitespace,
|
||||
template_args.config_span,
|
||||
)?;
|
||||
let input = TemplateInput::new(ast, config, template_args)?;
|
||||
@ -237,7 +233,6 @@ fn build_template_inner(
|
||||
struct CompileError {
|
||||
msg: String,
|
||||
span: Option<Span>,
|
||||
rendered: bool,
|
||||
}
|
||||
|
||||
impl CompileError {
|
||||
@ -254,18 +249,13 @@ impl CompileError {
|
||||
Some(file_info) => format!("{msg}{file_info}"),
|
||||
None => msg.to_string(),
|
||||
};
|
||||
Self {
|
||||
msg,
|
||||
span,
|
||||
rendered: false,
|
||||
}
|
||||
Self { msg, span }
|
||||
}
|
||||
|
||||
fn no_file_info<S: ToString>(msg: S, span: Option<Span>) -> Self {
|
||||
Self {
|
||||
msg: msg.to_string(),
|
||||
span,
|
||||
rendered: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -747,7 +747,8 @@ fn test_code_in_comment() {
|
||||
let err = build_template(&ast).unwrap_err();
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"when using `in_doc = true`, the struct's documentation needs a `rinja` code block"
|
||||
"when using `in_doc` with the value `true`, the struct's documentation needs a `rinja` \
|
||||
code block"
|
||||
);
|
||||
|
||||
let ts = "
|
||||
|
@ -1,5 +1,5 @@
|
||||
use std::collections::HashSet;
|
||||
use std::str;
|
||||
use std::str::{self, FromStr};
|
||||
|
||||
use winnow::Parser;
|
||||
use winnow::combinator::{
|
||||
@ -426,8 +426,11 @@ impl<'a> CondTest<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash)]
|
||||
#[cfg_attr(feature = "config", derive(serde::Deserialize))]
|
||||
#[cfg_attr(feature = "config", serde(field_identifier, rename_all = "lowercase"))]
|
||||
pub enum Whitespace {
|
||||
#[default]
|
||||
Preserve,
|
||||
Suppress,
|
||||
Minimize,
|
||||
@ -448,6 +451,19 @@ impl Whitespace {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Whitespace {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"+" | "preserve" => Ok(Whitespace::Preserve),
|
||||
"-" | "suppress" => Ok(Whitespace::Suppress),
|
||||
"~" | "minimize" => Ok(Whitespace::Minimize),
|
||||
s => Err(format!("invalid value for `whitespace`: {s:?}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_block_start<'a>(
|
||||
i: &'a str,
|
||||
start: &'a str,
|
||||
|
@ -6,11 +6,11 @@ error: unknown node `fail`
|
||||
5 | /// Some documentation
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: when using `in_doc = true`, the struct's documentation needs a `rinja` code block
|
||||
--> tests/ui/rinja-block.rs:15:25
|
||||
error: when using `in_doc` with the value `true`, the struct's documentation needs a `rinja` code block
|
||||
--> tests/ui/rinja-block.rs:15:34
|
||||
|
|
||||
15 | #[template(ext = "txt", in_doc = true)]
|
||||
| ^^^^^^
|
||||
| ^^^^
|
||||
|
||||
error: specify one template argument `path`, `source` or `in_doc`
|
||||
--> tests/ui/rinja-block.rs:24:3
|
||||
@ -36,11 +36,11 @@ error: template attribute `in_doc` expects a boolean value
|
||||
51 | #[template(ext = "txt", in_doc = "yes")]
|
||||
| ^^^^^
|
||||
|
||||
error: when using `in_doc = true`, the enum's documentation needs a `rinja` code block
|
||||
--> tests/ui/rinja-block.rs:60:25
|
||||
error: when using `in_doc` with the value `true`, the enum's documentation needs a `rinja` code block
|
||||
--> tests/ui/rinja-block.rs:60:34
|
||||
|
|
||||
60 | #[template(ext = "txt", in_doc = true)]
|
||||
| ^^^^^^
|
||||
| ^^^^
|
||||
|
||||
error: rinja templates are not supported for `union` types, only `struct` and `enum`
|
||||
--> tests/ui/rinja-block.rs:68:1
|
||||
|
Loading…
x
Reference in New Issue
Block a user