mirror of
https://github.com/askama-rs/askama.git
synced 2025-10-02 23:35:07 +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) 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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 { "" },
|
||||||
|
@ -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)),
|
||||||
|
@ -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(
|
||||||
|
@ -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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 = "
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user