Merge pull request #260 from Kijewski/pr-stuff-in-derive

derive: some clean-ups and optimizations
This commit is contained in:
Guillaume Gomez 2024-11-24 01:29:33 +01:00 committed by GitHub
commit e4b89797b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 157 additions and 190 deletions

View File

@ -20,7 +20,7 @@ pub(crate) struct Config {
pub(crate) syntaxes: BTreeMap<String, SyntaxAndCache<'static>>, pub(crate) syntaxes: BTreeMap<String, SyntaxAndCache<'static>>,
pub(crate) default_syntax: &'static str, pub(crate) default_syntax: &'static str,
pub(crate) escapers: Vec<(Vec<Cow<'static, str>>, Cow<'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 // `Config` is self referential and `_key` owns it data, so it must come last
_key: OwnedConfigKey, _key: OwnedConfigKey,
} }
@ -39,7 +39,7 @@ struct OwnedConfigKey(&'static ConfigKey<'static>);
struct ConfigKey<'a> { struct ConfigKey<'a> {
source: Cow<'a, str>, source: Cow<'a, str>,
config_path: Option<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> { impl<'a> ToOwned for ConfigKey<'a> {
@ -52,10 +52,7 @@ impl<'a> ToOwned for ConfigKey<'a> {
.config_path .config_path
.as_ref() .as_ref()
.map(|s| Cow::Owned(s.as_ref().to_owned())), .map(|s| Cow::Owned(s.as_ref().to_owned())),
template_whitespace: self template_whitespace: self.template_whitespace,
.template_whitespace
.as_ref()
.map(|s| Cow::Owned(s.as_ref().to_owned())),
}; };
OwnedConfigKey(Box::leak(Box::new(owned_key))) OwnedConfigKey(Box::leak(Box::new(owned_key)))
} }
@ -72,7 +69,7 @@ impl Config {
pub(crate) fn new( pub(crate) fn new(
source: &str, source: &str,
config_path: Option<&str>, config_path: Option<&str>,
template_whitespace: Option<&str>, template_whitespace: Option<Whitespace>,
config_span: Option<Span>, config_span: Option<Span>,
) -> Result<&'static Config, CompileError> { ) -> Result<&'static Config, CompileError> {
static CACHE: ManuallyDrop<OnceLock<OnceMap<OwnedConfigKey, &'static Config>>> = static CACHE: ManuallyDrop<OnceLock<OnceMap<OwnedConfigKey, &'static Config>>> =
@ -81,7 +78,7 @@ impl Config {
&ConfigKey { &ConfigKey {
source: source.into(), source: source.into(),
config_path: config_path.map(Cow::Borrowed), config_path: config_path.map(Cow::Borrowed),
template_whitespace: template_whitespace.map(Cow::Borrowed), template_whitespace,
}, },
|key| { |key| {
let config = Config::new_uncached(key.to_owned(), config_span)?; let config = Config::new_uncached(key.to_owned(), config_span)?;
@ -100,7 +97,6 @@ impl Config {
) -> Result<Config, CompileError> { ) -> Result<Config, CompileError> {
let s = key.0.source.as_ref(); let s = key.0.source.as_ref();
let config_path = key.0.config_path.as_deref(); let config_path = key.0.config_path.as_deref();
let template_whitespace = key.0.template_whitespace.as_deref();
let root = manifest_root(); let root = manifest_root();
let default_dirs = vec![root.join("templates")]; let default_dirs = vec![root.join("templates")];
@ -114,7 +110,7 @@ impl Config {
RawConfig::from_toml_str(s)? RawConfig::from_toml_str(s)?
}; };
let (dirs, default_syntax, mut whitespace) = match raw.general { let (dirs, default_syntax, whitespace) = match raw.general {
Some(General { Some(General {
dirs, dirs,
default_syntax, default_syntax,
@ -126,26 +122,10 @@ impl Config {
default_syntax.unwrap_or(DEFAULT_SYNTAX_NAME), default_syntax.unwrap_or(DEFAULT_SYNTAX_NAME),
whitespace, whitespace,
), ),
None => ( None => (default_dirs, DEFAULT_SYNTAX_NAME, Whitespace::default()),
default_dirs,
DEFAULT_SYNTAX_NAME,
WhitespaceHandling::default(),
),
}; };
let file_info = config_path.map(|path| FileInfo::new(Path::new(path), None, None)); let file_info = config_path.map(|path| FileInfo::new(Path::new(path), None, None));
if let Some(template_whitespace) = template_whitespace { let whitespace = key.0.template_whitespace.unwrap_or(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,
));
}
};
}
if let Some(raw_syntaxes) = raw.syntax { if let Some(raw_syntaxes) = raw.syntax {
for raw_s in raw_syntaxes { 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))] #[cfg_attr(feature = "config", derive(Deserialize))]
struct General<'a> { struct General<'a> {
#[cfg_attr(feature = "config", serde(borrow))] #[cfg_attr(feature = "config", serde(borrow))]
dirs: Option<Vec<&'a str>>, dirs: Option<Vec<&'a str>>,
default_syntax: Option<&'a str>, default_syntax: Option<&'a str>,
#[cfg_attr(feature = "config", serde(default))] #[cfg_attr(feature = "config", serde(default))]
whitespace: WhitespaceHandling, whitespace: Whitespace,
} }
#[cfg_attr(feature = "config", derive(Deserialize))] #[cfg_attr(feature = "config", derive(Deserialize))]
@ -704,10 +659,10 @@ mod tests {
None, None,
) )
.unwrap(); .unwrap();
assert_eq!(config.whitespace, WhitespaceHandling::Suppress); assert_eq!(config.whitespace, Whitespace::Suppress);
let config = Config::new(r#""#, None, None, None).unwrap(); 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( let config = Config::new(
r#" r#"
@ -719,7 +674,7 @@ mod tests {
None, None,
) )
.unwrap(); .unwrap();
assert_eq!(config.whitespace, WhitespaceHandling::Preserve); assert_eq!(config.whitespace, Whitespace::Preserve);
let config = Config::new( let config = Config::new(
r#" r#"
@ -731,7 +686,7 @@ mod tests {
None, None,
) )
.unwrap(); .unwrap();
assert_eq!(config.whitespace, WhitespaceHandling::Minimize); assert_eq!(config.whitespace, Whitespace::Minimize);
} }
#[cfg(feature = "config")] #[cfg(feature = "config")]
@ -739,30 +694,20 @@ mod tests {
fn test_whitespace_in_template() { fn test_whitespace_in_template() {
// Checking that template arguments have precedence over general configuration. // Checking that template arguments have precedence over general configuration.
// So in here, in the template arguments, there is `whitespace = "minimize"` so // 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( let config = Config::new(
r#" r#"
[general] [general]
whitespace = "suppress" whitespace = "suppress"
"#, "#,
None, None,
Some("minimize"), Some(Whitespace::Minimize),
None, None,
) )
.unwrap(); .unwrap();
assert_eq!(config.whitespace, WhitespaceHandling::Minimize); assert_eq!(config.whitespace, Whitespace::Minimize);
let config = Config::new(r#""#, None, Some("minimize"), None).unwrap(); let config = Config::new(r#""#, None, Some(Whitespace::Minimize), None).unwrap();
assert_eq!(config.whitespace, WhitespaceHandling::Minimize); assert_eq!(config.whitespace, Whitespace::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");
}
} }
} }

View File

@ -15,7 +15,6 @@ use parser::{
}; };
use rustc_hash::FxBuildHasher; use rustc_hash::FxBuildHasher;
use crate::config::WhitespaceHandling;
use crate::heritage::{Context, Heritage}; use crate::heritage::{Context, Heritage};
use crate::html::write_escaped_str; use crate::html::write_escaped_str;
use crate::input::{Source, TemplateInput}; use crate::input::{Source, TemplateInput};
@ -72,7 +71,7 @@ struct Generator<'a, 'h> {
next_ws: Option<&'a str>, next_ws: Option<&'a str>,
// Whitespace suppression from the previous non-literal. Will be used to // Whitespace suppression from the previous non-literal. Will be used to
// determine whether to flush prefix whitespace from the next literal. // 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 // If currently in a block, this will contain the name of a potential parent block
super_block: Option<(&'a str, usize)>, super_block: Option<(&'a str, usize)>,
// Buffer for writable // Buffer for writable
@ -96,7 +95,7 @@ impl<'a, 'h> Generator<'a, 'h> {
heritage, heritage,
locals, locals,
next_ws: None, next_ws: None,
skip_ws: WhitespaceHandling::Preserve, skip_ws: Whitespace::Preserve,
super_block: None, super_block: None,
buf_writable: WritableBuffer { buf_writable: WritableBuffer {
discard: buf_writable_discard, discard: buf_writable_discard,
@ -328,7 +327,7 @@ impl<'a, 'h> Generator<'a, 'h> {
if AstLevel::Top == level { if AstLevel::Top == level {
// Handle any pending whitespace. // Handle any pending whitespace.
if self.next_ws.is_some() { 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)?; 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 (def, own_ctx) = if let Some(s) = scope {
let path = ctx.imports.get(s).ok_or_else(|| { 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(|| { 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(|| { 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) (def, mctx)
} else { } else {
let def = ctx let def = ctx.macros.get(name).ok_or_else(|| {
.macros ctx.generate_error(format_args!("macro {name:?} not found"), call)
.get(name) })?;
.ok_or_else(|| ctx.generate_error(&format!("macro {name:?} not found"), call))?;
(def, ctx) (def, ctx)
}; };
@ -794,7 +795,7 @@ impl<'a, 'h> Generator<'a, 'h> {
}; };
if !def.args.iter().any(|(arg, _)| arg == arg_name) { if !def.args.iter().any(|(arg, _)| arg == arg_name) {
return Err(ctx.generate_error( 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, call,
)); ));
} }
@ -825,7 +826,7 @@ impl<'a, 'h> Generator<'a, 'h> {
// to use unnamed ones at this point anymore. // to use unnamed ones at this point anymore.
if !allow_positional { if !allow_positional {
return Err(ctx.generate_error( return Err(ctx.generate_error(
&format!( format_args!(
"cannot have unnamed argument (`{arg}`) after named argument \ "cannot have unnamed argument (`{arg}`) after named argument \
in call to macro {name:?}" in call to macro {name:?}"
), ),
@ -837,7 +838,7 @@ impl<'a, 'h> Generator<'a, 'h> {
Some(arg_expr) if used_named_args[index] => { Some(arg_expr) if used_named_args[index] => {
let Expr::NamedArgument(name, _) = **arg_expr else { unreachable!() }; let Expr::NamedArgument(name, _) = **arg_expr else { unreachable!() };
return Err(ctx.generate_error( return Err(ctx.generate_error(
&format!("`{name}` is passed more than once"), format_args!("`{name}` is passed more than once"),
call, call,
)); ));
} }
@ -845,7 +846,7 @@ impl<'a, 'h> Generator<'a, 'h> {
if let Some(default_value) = default_value { if let Some(default_value) = default_value {
default_value default_value
} else { } 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 // A block definition contains a block definition of the same name
(Some(cur_name), Some((prev_name, _))) if cur_name == prev_name => { (Some(cur_name), Some((prev_name, _))) if cur_name == prev_name => {
return Err(ctx.generate_error( return Err(ctx.generate_error(
&format!("cannot define recursive blocks ({cur_name})"), format_args!("cannot define recursive blocks ({cur_name})"),
node, node,
)); ));
} }
@ -1325,15 +1326,15 @@ impl<'a, 'h> Generator<'a, 'h> {
let Lit { lws, val, rws } = *lit; let Lit { lws, val, rws } = *lit;
if !lws.is_empty() { if !lws.is_empty() {
match self.skip_ws { match self.skip_ws {
WhitespaceHandling::Suppress => {} Whitespace::Suppress => {}
_ if val.is_empty() => { _ if val.is_empty() => {
assert!(rws.is_empty()); assert!(rws.is_empty());
self.next_ws = Some(lws); self.next_ws = Some(lws);
} }
WhitespaceHandling::Preserve => { Whitespace::Preserve => {
self.buf_writable.push(Writable::Lit(Cow::Borrowed(lws))); self.buf_writable.push(Writable::Lit(Cow::Borrowed(lws)));
} }
WhitespaceHandling::Minimize => { Whitespace::Minimize => {
self.buf_writable.push(Writable::Lit(Cow::Borrowed( self.buf_writable.push(Writable::Lit(Cow::Borrowed(
match lws.contains('\n') { match lws.contains('\n') {
true => "\n", true => "\n",
@ -1345,7 +1346,7 @@ impl<'a, 'h> Generator<'a, 'h> {
} }
if !val.is_empty() { if !val.is_empty() {
self.skip_ws = WhitespaceHandling::Preserve; self.skip_ws = Whitespace::Preserve;
self.buf_writable.push(Writable::Lit(Cow::Borrowed(val))); self.buf_writable.push(Writable::Lit(Cow::Borrowed(val)));
} }
@ -1577,7 +1578,7 @@ impl<'a, 'h> Generator<'a, 'h> {
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
if cfg!(not(feature = "urlencode")) { if cfg!(not(feature = "urlencode")) {
return Err(ctx.generate_error( 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, node,
)); ));
} }
@ -1666,9 +1667,10 @@ impl<'a, 'h> Generator<'a, 'h> {
node: &WithSpan<'_, T>, node: &WithSpan<'_, T>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
if args.len() != 1 { if args.len() != 1 {
return Err( return Err(ctx.generate_error(
ctx.generate_error(&format!("unexpected argument(s) in `{name}` filter"), node) format_args!("unexpected argument(s) in `{name}` filter"),
); node,
));
} }
buf.write(format_args!( buf.write(format_args!(
"rinja::filters::{name}(&(&&rinja::filters::AutoEscaper::new(&(", "rinja::filters::{name}(&(&&rinja::filters::AutoEscaper::new(&(",
@ -1778,7 +1780,7 @@ impl<'a, 'h> Generator<'a, 'h> {
"CStr" "CStr"
}; };
return Err(ctx.generate_error( return Err(ctx.generate_error(
&format!( format_args!(
"invalid escaper `b{content:?}`. Expected a string, found a {kind}" "invalid escaper `b{content:?}`. Expected a string, found a {kind}"
), ),
&args[1], &args[1],
@ -1804,7 +1806,7 @@ impl<'a, 'h> Generator<'a, 'h> {
}) })
.ok_or_else(|| { .ok_or_else(|| {
ctx.generate_error( ctx.generate_error(
&format!( format_args!(
"invalid escaper '{name}' for `escape` filter. {}", "invalid escaper '{name}' for `escape` filter. {}",
MsgValidEscapers(&self.input.config.escapers), 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 => { sub_left => {
match sub_left { match sub_left {
@ -2342,13 +2348,8 @@ impl<'a, 'h> Generator<'a, 'h> {
self.prepare_ws(ws); self.prepare_ws(ws);
} }
fn should_trim_ws(&self, ws: Option<Whitespace>) -> WhitespaceHandling { fn should_trim_ws(&self, ws: Option<Whitespace>) -> Whitespace {
match ws { ws.unwrap_or(self.input.config.whitespace)
Some(Whitespace::Suppress) => WhitespaceHandling::Suppress,
Some(Whitespace::Preserve) => WhitespaceHandling::Preserve,
Some(Whitespace::Minimize) => WhitespaceHandling::Minimize,
None => self.input.config.whitespace,
}
} }
// If the previous literal left some trailing whitespace in `next_ws` and the // 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 // If `whitespace` is set to `suppress`, we keep the whitespace characters only if there is
// a `+` character. // a `+` character.
match self.should_trim_ws(ws.0) { match self.should_trim_ws(ws.0) {
WhitespaceHandling::Preserve => { Whitespace::Preserve => {
let val = self.next_ws.unwrap(); let val = self.next_ws.unwrap();
if !val.is_empty() { if !val.is_empty() {
self.buf_writable.push(Writable::Lit(Cow::Borrowed(val))); self.buf_writable.push(Writable::Lit(Cow::Borrowed(val)));
} }
} }
WhitespaceHandling::Minimize => { Whitespace::Minimize => {
let val = self.next_ws.unwrap(); let val = self.next_ws.unwrap();
if !val.is_empty() { if !val.is_empty() {
self.buf_writable.push(Writable::Lit(Cow::Borrowed( 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; self.next_ws = None;
} }
@ -2419,7 +2420,7 @@ fn macro_call_ensure_arg_count(
_ => (nb_default_args, "at least "), _ => (nb_default_args, "at least "),
}; };
Err(ctx.generate_error( Err(ctx.generate_error(
&format!( format_args!(
"macro {:?} expected {extra}{expected_args} argument{}, found {}", "macro {:?} expected {extra}{expected_args} argument{}, found {}",
def.name, def.name,
if expected_args != 1 { "s" } else { "" }, if expected_args != 1 { "s" } else { "" },

View File

@ -1,3 +1,4 @@
use core::fmt;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::Path; use std::path::Path;
use std::sync::Arc; 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( CompileError::new(
msg, msg,
self.path.map(|path| FileInfo::of(node, path, self.parsed)), self.path.map(|path| FileInfo::of(node, path, self.parsed)),

View File

@ -7,6 +7,7 @@ use std::str::FromStr;
use std::sync::{Arc, OnceLock}; use std::sync::{Arc, OnceLock};
use mime::Mime; use mime::Mime;
use parser::node::Whitespace;
use parser::{Node, Parsed}; use parser::{Node, Parsed};
use proc_macro2::Span; use proc_macro2::Span;
use rustc_hash::FxBuildHasher; use rustc_hash::FxBuildHasher;
@ -282,7 +283,7 @@ pub(crate) struct TemplateArgs {
ext_span: Option<Span>, ext_span: Option<Span>,
syntax: Option<String>, syntax: Option<String>,
config: Option<String>, config: Option<String>,
pub(crate) whitespace: Option<String>, pub(crate) whitespace: Option<Whitespace>,
pub(crate) template_span: Option<Span>, pub(crate) template_span: Option<Span>,
pub(crate) config_span: Option<Span>, pub(crate) config_span: Option<Span>,
} }
@ -307,15 +308,15 @@ impl TemplateArgs {
}; };
Ok(Self { Ok(Self {
source: match args.source { source: match args.source {
Some((_, PartialTemplateArgsSource::Path(s))) => { Some(PartialTemplateArgsSource::Path(s)) => {
(Source::Path(s.value()), Some(s.span())) (Source::Path(s.value()), Some(s.span()))
} }
Some((_, PartialTemplateArgsSource::Source(s))) => { Some(PartialTemplateArgsSource::Source(s)) => {
(Source::Source(s.value().into()), Some(s.span())) (Source::Source(s.value().into()), Some(s.span()))
} }
#[cfg(feature = "code-in-doc")] #[cfg(feature = "code-in-doc")]
Some((ident, PartialTemplateArgsSource::InDoc(_))) => { Some(PartialTemplateArgsSource::InDoc(s)) => {
source_from_docs(&ident, &args.meta_docs, ast)? source_from_docs(s.span(), &args.meta_docs, ast)?
} }
None => { None => {
return Err(CompileError::no_file_info( return Err(CompileError::no_file_info(
@ -327,16 +328,16 @@ impl TemplateArgs {
)); ));
} }
}, },
block: args.block.map(|(_, value)| value.value()), block: args.block.map(|value| value.value()),
print: args.print.map(|(_, _, value)| value).unwrap_or_default(), print: args.print.unwrap_or_default(),
escaping: args.escape.map(|(_, value)| value.value()), escaping: args.escape.map(|value| value.value()),
ext: args.ext.as_ref().map(|(_, s)| s.value()), ext: args.ext.as_ref().map(|value| value.value()),
ext_span: args.ext.as_ref().map(|(_, s)| s.span()), ext_span: args.ext.as_ref().map(|value| value.span()),
syntax: args.syntax.map(|(_, value)| value.value()), syntax: args.syntax.map(|value| value.value()),
config: args.config.as_ref().map(|(_, s)| s.value()), config: args.config.as_ref().map(|value| value.value()),
whitespace: args.whitespace.map(|(_, value)| value.value()), whitespace: args.whitespace,
template_span: Some(template.span()), 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. /// This is only done if no path or source was given in the `#[template]` attribute.
#[cfg(feature = "code-in-doc")] #[cfg(feature = "code-in-doc")]
fn source_from_docs( fn source_from_docs(
name: &Ident, span: Span,
docs: &[Attribute], docs: &[Attribute],
ast: &syn::DeriveInput, ast: &syn::DeriveInput,
) -> Result<(Source, Option<Span>), CompileError> { ) -> 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 = strip_common_ws_prefix(source);
let source = collect_rinja_code_blocks(name, ast, source)?; let source = collect_rinja_code_blocks(span, ast, source)?;
Ok((source, span)) Ok((source, source_span))
} }
#[cfg(feature = "code-in-doc")] #[cfg(feature = "code-in-doc")]
fn collect_comment_blocks( fn collect_comment_blocks(
name: &Ident, span: Span,
docs: &[Attribute], docs: &[Attribute],
ast: &syn::DeriveInput, ast: &syn::DeriveInput,
) -> Result<(Option<Span>, String), CompileError> { ) -> 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| { let mut assign_span = |kv: &syn::MetaNameValue| {
// FIXME: uncomment once <https://github.com/rust-lang/rust/issues/54725> is stable // FIXME: uncomment once <https://github.com/rust-lang/rust/issues/54725> is stable
// let new_span = kv.path.span(); // 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), // Some(cur_span) => cur_span.join(new_span).unwrap_or(cur_span),
// None => new_span, // None => new_span,
// }); // });
if span.is_none() { if source_span.is_none() {
span = Some(kv.path.span()); source_span = Some(kv.path.span());
} }
}; };
@ -424,14 +425,14 @@ fn collect_comment_blocks(
source.push('\n'); source.push('\n');
} }
if source.is_empty() { 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")] #[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 { let kind = match &ast.data {
syn::Data::Struct(_) => "struct", syn::Data::Struct(_) => "struct",
syn::Data::Enum(_) => "enum", syn::Data::Enum(_) => "enum",
@ -440,9 +441,10 @@ fn no_rinja_code_block(name: &Ident, ast: &syn::DeriveInput) -> CompileError {
}; };
CompileError::no_file_info( CompileError::no_file_info(
format!( 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")] #[cfg(feature = "code-in-doc")]
fn collect_rinja_code_blocks( fn collect_rinja_code_blocks(
name: &Ident, span: Span,
ast: &syn::DeriveInput, ast: &syn::DeriveInput,
source: String, source: String,
) -> Result<Source, CompileError> { ) -> Result<Source, CompileError> {
@ -499,7 +501,7 @@ fn collect_rinja_code_blocks(
} }
} }
if !had_rinja_code { 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') { if tmpl_source.ends_with('\n') {
@ -651,14 +653,14 @@ pub(crate) fn get_template_source(
pub(crate) struct PartialTemplateArgs { pub(crate) struct PartialTemplateArgs {
pub(crate) template: Option<Ident>, pub(crate) template: Option<Ident>,
pub(crate) meta_docs: Vec<Attribute>, pub(crate) meta_docs: Vec<Attribute>,
pub(crate) source: Option<(Ident, PartialTemplateArgsSource)>, pub(crate) source: Option<PartialTemplateArgsSource>,
pub(crate) block: Option<(Ident, LitStr)>, pub(crate) block: Option<LitStr>,
pub(crate) print: Option<(Ident, LitStr, Print)>, pub(crate) print: Option<Print>,
pub(crate) escape: Option<(Ident, LitStr)>, pub(crate) escape: Option<LitStr>,
pub(crate) ext: Option<(Ident, LitStr)>, pub(crate) ext: Option<LitStr>,
pub(crate) syntax: Option<(Ident, LitStr)>, pub(crate) syntax: Option<LitStr>,
pub(crate) config: Option<(Ident, LitStr)>, pub(crate) config: Option<LitStr>,
pub(crate) whitespace: Option<(Ident, LitStr)>, pub(crate) whitespace: Option<Whitespace>,
} }
pub(crate) enum PartialTemplateArgsSource { pub(crate) enum PartialTemplateArgsSource {
@ -719,12 +721,11 @@ const _: () = {
if ident == "path" { if ident == "path" {
ensure_source_only_once(ident, &this.source)?; ensure_source_only_once(ident, &this.source)?;
let value = get_strlit(ident, value)?; this.source = Some(PartialTemplateArgsSource::Path(get_strlit(ident, value)?));
this.source = Some((ident.clone(), PartialTemplateArgsSource::Path(value)));
} else if ident == "source" { } else if ident == "source" {
ensure_source_only_once(ident, &this.source)?; ensure_source_only_once(ident, &this.source)?;
let value = get_strlit(ident, value)?; this.source =
this.source = Some((ident.clone(), PartialTemplateArgsSource::Source(value))); Some(PartialTemplateArgsSource::Source(get_strlit(ident, value)?));
} else if ident == "in_doc" { } else if ident == "in_doc" {
let value = get_boollit(ident, value)?; let value = get_boollit(ident, value)?;
if !value.value() { if !value.value() {
@ -741,19 +742,12 @@ const _: () = {
} }
#[cfg(feature = "code-in-doc")] #[cfg(feature = "code-in-doc")]
{ {
this.source = this.source = Some(PartialTemplateArgsSource::InDoc(value));
Some((ident.clone(), PartialTemplateArgsSource::InDoc(value)));
} }
} else if ident == "block" { } else if ident == "block" {
set_strlit_pair(ident, value, &mut this.block)?; set_strlit_pair(ident, value, &mut this.block)?;
} else if ident == "print" { } else if ident == "print" {
ensure_only_once(ident, &mut this.print)?; set_parseable_string(ident, value, &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));
} else if ident == "escape" { } else if ident == "escape" {
set_strlit_pair(ident, value, &mut this.escape)?; set_strlit_pair(ident, value, &mut this.escape)?;
} else if ident == "ext" { } else if ident == "ext" {
@ -763,7 +757,7 @@ const _: () = {
} else if ident == "config" { } else if ident == "config" {
set_strlit_pair(ident, value, &mut this.config)?; set_strlit_pair(ident, value, &mut this.config)?;
} else if ident == "whitespace" { } else if ident == "whitespace" {
set_strlit_pair(ident, value, &mut this.whitespace)?; set_parseable_string(ident, value, &mut this.whitespace)?;
} else { } else {
return Err(CompileError::no_file_info( return Err(CompileError::no_file_info(
format!("unsupported template attribute `{ident}` found"), format!("unsupported template attribute `{ident}` found"),
@ -778,11 +772,26 @@ const _: () = {
fn set_strlit_pair( fn set_strlit_pair(
name: &Ident, name: &Ident,
value: ExprLit, value: ExprLit,
dest: &mut Option<(Ident, LitStr)>, dest: &mut Option<LitStr>,
) -> Result<(), CompileError> { ) -> Result<(), CompileError> {
ensure_only_once(name, dest)?; ensure_only_once(name, dest)?;
let value = get_strlit(name, value)?; *dest = Some(get_strlit(name, value)?);
*dest = Some((name.clone(), 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(()) Ok(())
} }
@ -836,7 +845,7 @@ const _: () = {
fn ensure_source_only_once( fn ensure_source_only_once(
name: &Ident, name: &Ident,
source: &Option<(Ident, PartialTemplateArgsSource)>, source: &Option<PartialTemplateArgsSource>,
) -> Result<(), CompileError> { ) -> Result<(), CompileError> {
if source.is_some() { if source.is_some() {
return Err(CompileError::no_file_info( return Err(CompileError::no_file_info(

View File

@ -128,11 +128,7 @@ pub fn derive_template(input: TokenStream12) -> TokenStream12 {
}; };
match build_template(&ast) { match build_template(&ast) {
Ok(source) => source.parse().unwrap(), Ok(source) => source.parse().unwrap(),
Err(CompileError { Err(CompileError { msg, span }) => {
msg,
span,
rendered: _rendered,
}) => {
let mut ts = compile_error(std::iter::once(msg), span.unwrap_or(ast.ident.span())); let mut ts = compile_error(std::iter::once(msg), span.unwrap_or(ast.ident.span()));
if let Ok(source) = build_skeleton(&ast) { if let Ok(source) = build_skeleton(&ast) {
let source: TokenStream = source.parse().unwrap(); let source: TokenStream = source.parse().unwrap();
@ -191,7 +187,7 @@ fn build_template_inner(
let config = Config::new( let config = Config::new(
&s, &s,
config_path, config_path,
template_args.whitespace.as_deref(), template_args.whitespace,
template_args.config_span, template_args.config_span,
)?; )?;
let input = TemplateInput::new(ast, config, template_args)?; let input = TemplateInput::new(ast, config, template_args)?;
@ -237,7 +233,6 @@ fn build_template_inner(
struct CompileError { struct CompileError {
msg: String, msg: String,
span: Option<Span>, span: Option<Span>,
rendered: bool,
} }
impl CompileError { impl CompileError {
@ -254,18 +249,13 @@ impl CompileError {
Some(file_info) => format!("{msg}{file_info}"), Some(file_info) => format!("{msg}{file_info}"),
None => msg.to_string(), None => msg.to_string(),
}; };
Self { Self { msg, span }
msg,
span,
rendered: false,
}
} }
fn no_file_info<S: ToString>(msg: S, span: Option<Span>) -> Self { fn no_file_info<S: ToString>(msg: S, span: Option<Span>) -> Self {
Self { Self {
msg: msg.to_string(), msg: msg.to_string(),
span, span,
rendered: false,
} }
} }
} }

View File

@ -747,7 +747,8 @@ fn test_code_in_comment() {
let err = build_template(&ast).unwrap_err(); let err = build_template(&ast).unwrap_err();
assert_eq!( assert_eq!(
err.to_string(), 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 = " let ts = "

View File

@ -1,5 +1,5 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::str; use std::str::{self, FromStr};
use winnow::Parser; use winnow::Parser;
use winnow::combinator::{ 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 { pub enum Whitespace {
#[default]
Preserve, Preserve,
Suppress, Suppress,
Minimize, 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>( fn check_block_start<'a>(
i: &'a str, i: &'a str,
start: &'a str, start: &'a str,

View File

@ -6,11 +6,11 @@ error: unknown node `fail`
5 | /// Some documentation 5 | /// Some documentation
| ^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^
error: when using `in_doc = true`, the struct's documentation needs a `rinja` code block error: when using `in_doc` with the value `true`, the struct's documentation needs a `rinja` code block
--> tests/ui/rinja-block.rs:15:25 --> tests/ui/rinja-block.rs:15:34
| |
15 | #[template(ext = "txt", in_doc = true)] 15 | #[template(ext = "txt", in_doc = true)]
| ^^^^^^ | ^^^^
error: specify one template argument `path`, `source` or `in_doc` error: specify one template argument `path`, `source` or `in_doc`
--> tests/ui/rinja-block.rs:24:3 --> 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")] 51 | #[template(ext = "txt", in_doc = "yes")]
| ^^^^^ | ^^^^^
error: when using `in_doc = true`, the enum's documentation needs a `rinja` code block error: when using `in_doc` with the value `true`, the enum's documentation needs a `rinja` code block
--> tests/ui/rinja-block.rs:60:25 --> tests/ui/rinja-block.rs:60:34
| |
60 | #[template(ext = "txt", in_doc = true)] 60 | #[template(ext = "txt", in_doc = true)]
| ^^^^^^ | ^^^^
error: rinja templates are not supported for `union` types, only `struct` and `enum` error: rinja templates are not supported for `union` types, only `struct` and `enum`
--> tests/ui/rinja-block.rs:68:1 --> tests/ui/rinja-block.rs:68:1