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) 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);
}
}

View File

@ -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 { "" },

View File

@ -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)),

View File

@ -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(

View File

@ -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,
}
}
}

View File

@ -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 = "

View File

@ -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,

View File

@ -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