aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar René Kijewski <kijewski@library.vetmed.fu-berlin.de>2022-05-18 17:13:52 +0200
committerLibravatar Dirkjan Ochtman <dirkjan@ochtman.nl>2022-05-23 11:14:22 +0200
commite30cad33fd28c0d2546fbd70afa6834bea195f9e (patch)
tree90b2d331aa419077b506b4d8dfdc5b0ccfe72029
parentc78c1dfc25a8bbc6a1ceffbce31f102e9afc8007 (diff)
downloadaskama-e30cad33fd28c0d2546fbd70afa6834bea195f9e.tar.gz
askama-e30cad33fd28c0d2546fbd70afa6834bea195f9e.tar.bz2
askama-e30cad33fd28c0d2546fbd70afa6834bea195f9e.zip
Move configuration into its own module
-rw-r--r--askama_shared/src/config.rs536
-rw-r--r--askama_shared/src/generator.rs5
-rw-r--r--askama_shared/src/heritage.rs3
-rw-r--r--askama_shared/src/input.rs3
-rw-r--r--askama_shared/src/lib.rs532
-rw-r--r--askama_shared/src/parser.rs5
6 files changed, 551 insertions, 533 deletions
diff --git a/askama_shared/src/config.rs b/askama_shared/src/config.rs
new file mode 100644
index 0000000..01f81a2
--- /dev/null
+++ b/askama_shared/src/config.rs
@@ -0,0 +1,536 @@
+use std::collections::{BTreeMap, HashSet};
+use std::convert::TryFrom;
+use std::path::{Path, PathBuf};
+use std::{env, fs};
+
+#[cfg(feature = "serde")]
+use serde::Deserialize;
+
+use crate::CompileError;
+
+#[derive(Debug)]
+pub(crate) struct Config<'a> {
+ pub(crate) dirs: Vec<PathBuf>,
+ pub(crate) syntaxes: BTreeMap<String, Syntax<'a>>,
+ pub(crate) default_syntax: &'a str,
+ pub(crate) escapers: Vec<(HashSet<String>, String)>,
+ pub(crate) whitespace: WhitespaceHandling,
+}
+
+impl Config<'_> {
+ pub(crate) fn new(s: &str) -> std::result::Result<Config<'_>, CompileError> {
+ let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
+ let default_dirs = vec![root.join("templates")];
+
+ let mut syntaxes = BTreeMap::new();
+ syntaxes.insert(DEFAULT_SYNTAX_NAME.to_string(), Syntax::default());
+
+ let raw = if s.is_empty() {
+ RawConfig::default()
+ } else {
+ RawConfig::from_toml_str(s)?
+ };
+
+ let (dirs, default_syntax, whitespace) = match raw.general {
+ Some(General {
+ dirs,
+ default_syntax,
+ whitespace,
+ }) => (
+ dirs.map_or(default_dirs, |v| {
+ v.into_iter().map(|dir| root.join(dir)).collect()
+ }),
+ default_syntax.unwrap_or(DEFAULT_SYNTAX_NAME),
+ whitespace,
+ ),
+ None => (
+ default_dirs,
+ DEFAULT_SYNTAX_NAME,
+ WhitespaceHandling::default(),
+ ),
+ };
+
+ if let Some(raw_syntaxes) = raw.syntax {
+ for raw_s in raw_syntaxes {
+ let name = raw_s.name;
+
+ if syntaxes
+ .insert(name.to_string(), Syntax::try_from(raw_s)?)
+ .is_some()
+ {
+ return Err(format!("syntax \"{}\" is already defined", name).into());
+ }
+ }
+ }
+
+ if !syntaxes.contains_key(default_syntax) {
+ return Err(format!("default syntax \"{}\" not found", default_syntax).into());
+ }
+
+ let mut escapers = Vec::new();
+ if let Some(configured) = raw.escaper {
+ for escaper in configured {
+ escapers.push((
+ escaper
+ .extensions
+ .iter()
+ .map(|ext| (*ext).to_string())
+ .collect(),
+ escaper.path.to_string(),
+ ));
+ }
+ }
+ for (extensions, path) in DEFAULT_ESCAPERS {
+ escapers.push((str_set(extensions), (*path).to_string()));
+ }
+
+ Ok(Config {
+ dirs,
+ syntaxes,
+ default_syntax,
+ escapers,
+ whitespace,
+ })
+ }
+
+ pub(crate) fn find_template(
+ &self,
+ path: &str,
+ start_at: Option<&Path>,
+ ) -> std::result::Result<PathBuf, CompileError> {
+ if let Some(root) = start_at {
+ let relative = root.with_file_name(path);
+ if relative.exists() {
+ return Ok(relative);
+ }
+ }
+
+ for dir in &self.dirs {
+ let rooted = dir.join(path);
+ if rooted.exists() {
+ return Ok(rooted);
+ }
+ }
+
+ Err(format!(
+ "template {:?} not found in directories {:?}",
+ path, self.dirs
+ )
+ .into())
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct Syntax<'a> {
+ pub(crate) block_start: &'a str,
+ pub(crate) block_end: &'a str,
+ pub(crate) expr_start: &'a str,
+ pub(crate) expr_end: &'a str,
+ pub(crate) comment_start: &'a str,
+ pub(crate) comment_end: &'a str,
+}
+
+impl Default for Syntax<'_> {
+ fn default() -> Self {
+ Self {
+ block_start: "{%",
+ block_end: "%}",
+ expr_start: "{{",
+ expr_end: "}}",
+ comment_start: "{#",
+ comment_end: "#}",
+ }
+ }
+}
+
+impl<'a> TryFrom<RawSyntax<'a>> for Syntax<'a> {
+ type Error = CompileError;
+
+ fn try_from(raw: RawSyntax<'a>) -> std::result::Result<Self, Self::Error> {
+ let default = Self::default();
+ let syntax = Self {
+ block_start: raw.block_start.unwrap_or(default.block_start),
+ block_end: raw.block_end.unwrap_or(default.block_end),
+ expr_start: raw.expr_start.unwrap_or(default.expr_start),
+ expr_end: raw.expr_end.unwrap_or(default.expr_end),
+ comment_start: raw.comment_start.unwrap_or(default.comment_start),
+ comment_end: raw.comment_end.unwrap_or(default.comment_end),
+ };
+
+ if syntax.block_start.len() != 2
+ || syntax.block_end.len() != 2
+ || syntax.expr_start.len() != 2
+ || syntax.expr_end.len() != 2
+ || syntax.comment_start.len() != 2
+ || syntax.comment_end.len() != 2
+ {
+ return Err("length of delimiters must be two".into());
+ }
+
+ let bs = syntax.block_start.as_bytes()[0];
+ let be = syntax.block_start.as_bytes()[1];
+ let cs = syntax.comment_start.as_bytes()[0];
+ let ce = syntax.comment_start.as_bytes()[1];
+ let es = syntax.expr_start.as_bytes()[0];
+ let ee = syntax.expr_start.as_bytes()[1];
+ if !((bs == cs && bs == es) || (be == ce && be == ee)) {
+ return Err(format!("bad delimiters block_start: {}, comment_start: {}, expr_start: {}, needs one of the two characters in common", syntax.block_start, syntax.comment_start, syntax.expr_start).into());
+ }
+
+ Ok(syntax)
+ }
+}
+
+#[cfg_attr(feature = "serde", derive(Deserialize))]
+#[derive(Default)]
+struct RawConfig<'d> {
+ #[cfg_attr(feature = "serde", serde(borrow))]
+ general: Option<General<'d>>,
+ syntax: Option<Vec<RawSyntax<'d>>>,
+ escaper: Option<Vec<RawEscaper<'d>>>,
+}
+
+impl RawConfig<'_> {
+ #[cfg(feature = "config")]
+ fn from_toml_str(s: &str) -> std::result::Result<RawConfig<'_>, CompileError> {
+ toml::from_str(s).map_err(|e| format!("invalid TOML in {}: {}", CONFIG_FILE_NAME, e).into())
+ }
+
+ #[cfg(not(feature = "config"))]
+ fn from_toml_str(_: &str) -> std::result::Result<RawConfig<'_>, CompileError> {
+ Err("TOML support not available".into())
+ }
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+#[cfg_attr(feature = "serde", derive(Deserialize))]
+#[cfg_attr(feature = "serde", serde(field_identifier, rename_all = "lowercase"))]
+pub(crate) enum WhitespaceHandling {
+ /// The default behaviour. It will leave the whitespace characters "as is".
+ 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 Default for WhitespaceHandling {
+ fn default() -> Self {
+ WhitespaceHandling::Preserve
+ }
+}
+
+#[cfg_attr(feature = "serde", derive(Deserialize))]
+struct General<'a> {
+ #[cfg_attr(feature = "serde", serde(borrow))]
+ dirs: Option<Vec<&'a str>>,
+ default_syntax: Option<&'a str>,
+ #[cfg_attr(feature = "serde", serde(default))]
+ whitespace: WhitespaceHandling,
+}
+
+#[cfg_attr(feature = "serde", derive(Deserialize))]
+struct RawSyntax<'a> {
+ name: &'a str,
+ block_start: Option<&'a str>,
+ block_end: Option<&'a str>,
+ expr_start: Option<&'a str>,
+ expr_end: Option<&'a str>,
+ comment_start: Option<&'a str>,
+ comment_end: Option<&'a str>,
+}
+
+#[cfg_attr(feature = "serde", derive(Deserialize))]
+struct RawEscaper<'a> {
+ path: &'a str,
+ extensions: Vec<&'a str>,
+}
+
+pub(crate) fn read_config_file(
+ config_path: &Option<String>,
+) -> std::result::Result<String, CompileError> {
+ let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
+ let filename = match config_path {
+ Some(config_path) => root.join(config_path),
+ None => root.join(CONFIG_FILE_NAME),
+ };
+
+ if filename.exists() {
+ fs::read_to_string(&filename)
+ .map_err(|_| format!("unable to read {:?}", filename.to_str().unwrap()).into())
+ } else if config_path.is_some() {
+ Err(format!("`{}` does not exist", root.display()).into())
+ } else {
+ Ok("".to_string())
+ }
+}
+
+fn str_set<T>(vals: &[T]) -> HashSet<String>
+where
+ T: ToString,
+{
+ vals.iter().map(|s| s.to_string()).collect()
+}
+
+#[allow(clippy::match_wild_err_arm)]
+pub(crate) fn get_template_source(tpl_path: &Path) -> std::result::Result<String, CompileError> {
+ match fs::read_to_string(tpl_path) {
+ Err(_) => Err(format!(
+ "unable to open template file '{}'",
+ tpl_path.to_str().unwrap()
+ )
+ .into()),
+ Ok(mut source) => {
+ if source.ends_with('\n') {
+ let _ = source.pop();
+ }
+ Ok(source)
+ }
+ }
+}
+
+static CONFIG_FILE_NAME: &str = "askama.toml";
+static DEFAULT_SYNTAX_NAME: &str = "default";
+static DEFAULT_ESCAPERS: &[(&[&str], &str)] = &[
+ (&["html", "htm", "xml"], "::askama::Html"),
+ (&["md", "none", "txt", "yml", ""], "::askama::Text"),
+ (&["j2", "jinja", "jinja2"], "::askama::Html"),
+];
+
+#[cfg(test)]
+#[allow(clippy::blacklisted_name)]
+mod tests {
+ use std::env;
+ use std::path::{Path, PathBuf};
+
+ use super::*;
+
+ #[test]
+ fn get_source() {
+ let path = Config::new("")
+ .and_then(|config| config.find_template("b.html", None))
+ .unwrap();
+ assert_eq!(get_template_source(&path).unwrap(), "bar");
+ }
+
+ #[test]
+ fn test_default_config() {
+ let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
+ root.push("templates");
+ let config = Config::new("").unwrap();
+ assert_eq!(config.dirs, vec![root]);
+ }
+
+ #[cfg(feature = "config")]
+ #[test]
+ fn test_config_dirs() {
+ let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
+ root.push("tpl");
+ let config = Config::new("[general]\ndirs = [\"tpl\"]").unwrap();
+ assert_eq!(config.dirs, vec![root]);
+ }
+
+ fn assert_eq_rooted(actual: &Path, expected: &str) {
+ let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
+ root.push("templates");
+ let mut inner = PathBuf::new();
+ inner.push(expected);
+ assert_eq!(actual.strip_prefix(root).unwrap(), inner);
+ }
+
+ #[test]
+ fn find_absolute() {
+ let config = Config::new("").unwrap();
+ let root = config.find_template("a.html", None).unwrap();
+ let path = config.find_template("sub/b.html", Some(&root)).unwrap();
+ assert_eq_rooted(&path, "sub/b.html");
+ }
+
+ #[test]
+ #[should_panic]
+ fn find_relative_nonexistent() {
+ let config = Config::new("").unwrap();
+ let root = config.find_template("a.html", None).unwrap();
+ config.find_template("c.html", Some(&root)).unwrap();
+ }
+
+ #[test]
+ fn find_relative() {
+ let config = Config::new("").unwrap();
+ let root = config.find_template("sub/b.html", None).unwrap();
+ let path = config.find_template("c.html", Some(&root)).unwrap();
+ assert_eq_rooted(&path, "sub/c.html");
+ }
+
+ #[test]
+ fn find_relative_sub() {
+ let config = Config::new("").unwrap();
+ let root = config.find_template("sub/b.html", None).unwrap();
+ let path = config.find_template("sub1/d.html", Some(&root)).unwrap();
+ assert_eq_rooted(&path, "sub/sub1/d.html");
+ }
+
+ #[cfg(feature = "config")]
+ #[test]
+ fn add_syntax() {
+ let raw_config = r#"
+ [general]
+ default_syntax = "foo"
+
+ [[syntax]]
+ name = "foo"
+ block_start = "{<"
+
+ [[syntax]]
+ name = "bar"
+ expr_start = "{!"
+ "#;
+
+ let default_syntax = Syntax::default();
+ let config = Config::new(raw_config).unwrap();
+ assert_eq!(config.default_syntax, "foo");
+
+ let foo = config.syntaxes.get("foo").unwrap();
+ assert_eq!(foo.block_start, "{<");
+ assert_eq!(foo.block_end, default_syntax.block_end);
+ assert_eq!(foo.expr_start, default_syntax.expr_start);
+ assert_eq!(foo.expr_end, default_syntax.expr_end);
+ assert_eq!(foo.comment_start, default_syntax.comment_start);
+ assert_eq!(foo.comment_end, default_syntax.comment_end);
+
+ let bar = config.syntaxes.get("bar").unwrap();
+ assert_eq!(bar.block_start, default_syntax.block_start);
+ assert_eq!(bar.block_end, default_syntax.block_end);
+ assert_eq!(bar.expr_start, "{!");
+ assert_eq!(bar.expr_end, default_syntax.expr_end);
+ assert_eq!(bar.comment_start, default_syntax.comment_start);
+ assert_eq!(bar.comment_end, default_syntax.comment_end);
+ }
+
+ #[cfg(feature = "config")]
+ #[test]
+ fn add_syntax_two() {
+ let raw_config = r#"
+ syntax = [{ name = "foo", block_start = "{<" },
+ { name = "bar", expr_start = "{!" } ]
+
+ [general]
+ default_syntax = "foo"
+ "#;
+
+ let default_syntax = Syntax::default();
+ let config = Config::new(raw_config).unwrap();
+ assert_eq!(config.default_syntax, "foo");
+
+ let foo = config.syntaxes.get("foo").unwrap();
+ assert_eq!(foo.block_start, "{<");
+ assert_eq!(foo.block_end, default_syntax.block_end);
+ assert_eq!(foo.expr_start, default_syntax.expr_start);
+ assert_eq!(foo.expr_end, default_syntax.expr_end);
+ assert_eq!(foo.comment_start, default_syntax.comment_start);
+ assert_eq!(foo.comment_end, default_syntax.comment_end);
+
+ let bar = config.syntaxes.get("bar").unwrap();
+ assert_eq!(bar.block_start, default_syntax.block_start);
+ assert_eq!(bar.block_end, default_syntax.block_end);
+ assert_eq!(bar.expr_start, "{!");
+ assert_eq!(bar.expr_end, default_syntax.expr_end);
+ assert_eq!(bar.comment_start, default_syntax.comment_start);
+ assert_eq!(bar.comment_end, default_syntax.comment_end);
+ }
+
+ #[cfg(feature = "toml")]
+ #[should_panic]
+ #[test]
+ fn use_default_at_syntax_name() {
+ let raw_config = r#"
+ syntax = [{ name = "default" }]
+ "#;
+
+ let _config = Config::new(raw_config).unwrap();
+ }
+
+ #[cfg(feature = "toml")]
+ #[should_panic]
+ #[test]
+ fn duplicated_syntax_name_on_list() {
+ let raw_config = r#"
+ syntax = [{ name = "foo", block_start = "~<" },
+ { name = "foo", block_start = "%%" } ]
+ "#;
+
+ let _config = Config::new(raw_config).unwrap();
+ }
+
+ #[cfg(feature = "toml")]
+ #[should_panic]
+ #[test]
+ fn is_not_exist_default_syntax() {
+ let raw_config = r#"
+ [general]
+ default_syntax = "foo"
+ "#;
+
+ let _config = Config::new(raw_config).unwrap();
+ }
+
+ #[cfg(feature = "config")]
+ #[test]
+ fn escape_modes() {
+ let config = Config::new(
+ r#"
+ [[escaper]]
+ path = "::askama::Js"
+ extensions = ["js"]
+ "#,
+ )
+ .unwrap();
+ assert_eq!(
+ config.escapers,
+ vec![
+ (str_set(&["js"]), "::askama::Js".into()),
+ (str_set(&["html", "htm", "xml"]), "::askama::Html".into()),
+ (
+ str_set(&["md", "none", "txt", "yml", ""]),
+ "::askama::Text".into()
+ ),
+ (str_set(&["j2", "jinja", "jinja2"]), "::askama::Html".into()),
+ ]
+ );
+ }
+
+ #[test]
+ fn test_whitespace_parsing() {
+ let config = Config::new(
+ r#"
+ [general]
+ whitespace = "suppress"
+ "#,
+ )
+ .unwrap();
+ assert_eq!(config.whitespace, WhitespaceHandling::Suppress);
+
+ let config = Config::new(r#""#).unwrap();
+ assert_eq!(config.whitespace, WhitespaceHandling::Preserve);
+
+ let config = Config::new(
+ r#"
+ [general]
+ whitespace = "preserve"
+ "#,
+ )
+ .unwrap();
+ assert_eq!(config.whitespace, WhitespaceHandling::Preserve);
+
+ let config = Config::new(
+ r#"
+ [general]
+ whitespace = "minimize"
+ "#,
+ )
+ .unwrap();
+ assert_eq!(config.whitespace, WhitespaceHandling::Minimize);
+ }
+}
diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs
index 9174a77..ea95bd3 100644
--- a/askama_shared/src/generator.rs
+++ b/askama_shared/src/generator.rs
@@ -1,9 +1,8 @@
+use crate::config::{get_template_source, read_config_file, Config, WhitespaceHandling};
use crate::heritage::{Context, Heritage};
use crate::input::{Print, Source, TemplateInput};
use crate::parser::{parse, Cond, CondTest, Expr, Loop, Node, Target, When, Whitespace, Ws};
-use crate::{
- filters, get_template_source, read_config_file, CompileError, Config, WhitespaceHandling,
-};
+use crate::{filters, CompileError};
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
diff --git a/askama_shared/src/heritage.rs b/askama_shared/src/heritage.rs
index 49599af..52c14a2 100644
--- a/askama_shared/src/heritage.rs
+++ b/askama_shared/src/heritage.rs
@@ -1,8 +1,9 @@
use std::collections::HashMap;
use std::path::{Path, PathBuf};
+use crate::config::Config;
use crate::parser::{Expr, Loop, Macro, Node};
-use crate::{CompileError, Config};
+use crate::CompileError;
pub(crate) struct Heritage<'a> {
pub(crate) root: &'a Context<'a>,
diff --git a/askama_shared/src/input.rs b/askama_shared/src/input.rs
index 350fc01..1f367fd 100644
--- a/askama_shared/src/input.rs
+++ b/askama_shared/src/input.rs
@@ -1,5 +1,6 @@
+use crate::config::{Config, Syntax};
use crate::generator::TemplateArgs;
-use crate::{CompileError, Config, Syntax};
+use crate::CompileError;
use std::path::{Path, PathBuf};
use std::str::FromStr;
diff --git a/askama_shared/src/lib.rs b/askama_shared/src/lib.rs
index a94c509..a3ee4c1 100644
--- a/askama_shared/src/lib.rs
+++ b/askama_shared/src/lib.rs
@@ -4,19 +4,14 @@
#![deny(unreachable_pub)]
use std::borrow::Cow;
-use std::collections::{BTreeMap, HashSet};
-use std::convert::TryFrom;
-use std::path::{Path, PathBuf};
-use std::{env, fmt, fs};
-
-use proc_macro2::{Span, TokenStream};
-#[cfg(feature = "serde")]
-use serde::Deserialize;
+use std::fmt;
pub use crate::generator::derive_template;
pub use crate::input::extension_to_mime_type;
pub use askama_escape::MarkupDisplay;
+use proc_macro2::{Span, TokenStream};
+mod config;
mod error;
pub use crate::error::{Error, Result};
pub mod filters;
@@ -112,295 +107,6 @@ impl fmt::Display for dyn DynTemplate {
}
}
-#[derive(Debug)]
-struct Config<'a> {
- dirs: Vec<PathBuf>,
- syntaxes: BTreeMap<String, Syntax<'a>>,
- default_syntax: &'a str,
- escapers: Vec<(HashSet<String>, String)>,
- whitespace: WhitespaceHandling,
-}
-
-impl Config<'_> {
- fn new(s: &str) -> std::result::Result<Config<'_>, CompileError> {
- let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
- let default_dirs = vec![root.join("templates")];
-
- let mut syntaxes = BTreeMap::new();
- syntaxes.insert(DEFAULT_SYNTAX_NAME.to_string(), Syntax::default());
-
- let raw = if s.is_empty() {
- RawConfig::default()
- } else {
- RawConfig::from_toml_str(s)?
- };
-
- let (dirs, default_syntax, whitespace) = match raw.general {
- Some(General {
- dirs,
- default_syntax,
- whitespace,
- }) => (
- dirs.map_or(default_dirs, |v| {
- v.into_iter().map(|dir| root.join(dir)).collect()
- }),
- default_syntax.unwrap_or(DEFAULT_SYNTAX_NAME),
- whitespace,
- ),
- None => (
- default_dirs,
- DEFAULT_SYNTAX_NAME,
- WhitespaceHandling::default(),
- ),
- };
-
- if let Some(raw_syntaxes) = raw.syntax {
- for raw_s in raw_syntaxes {
- let name = raw_s.name;
-
- if syntaxes
- .insert(name.to_string(), Syntax::try_from(raw_s)?)
- .is_some()
- {
- return Err(format!("syntax \"{}\" is already defined", name).into());
- }
- }
- }
-
- if !syntaxes.contains_key(default_syntax) {
- return Err(format!("default syntax \"{}\" not found", default_syntax).into());
- }
-
- let mut escapers = Vec::new();
- if let Some(configured) = raw.escaper {
- for escaper in configured {
- escapers.push((
- escaper
- .extensions
- .iter()
- .map(|ext| (*ext).to_string())
- .collect(),
- escaper.path.to_string(),
- ));
- }
- }
- for (extensions, path) in DEFAULT_ESCAPERS {
- escapers.push((str_set(extensions), (*path).to_string()));
- }
-
- Ok(Config {
- dirs,
- syntaxes,
- default_syntax,
- escapers,
- whitespace,
- })
- }
-
- fn find_template(
- &self,
- path: &str,
- start_at: Option<&Path>,
- ) -> std::result::Result<PathBuf, CompileError> {
- if let Some(root) = start_at {
- let relative = root.with_file_name(path);
- if relative.exists() {
- return Ok(relative);
- }
- }
-
- for dir in &self.dirs {
- let rooted = dir.join(path);
- if rooted.exists() {
- return Ok(rooted);
- }
- }
-
- Err(format!(
- "template {:?} not found in directories {:?}",
- path, self.dirs
- )
- .into())
- }
-}
-
-#[derive(Debug)]
-struct Syntax<'a> {
- block_start: &'a str,
- block_end: &'a str,
- expr_start: &'a str,
- expr_end: &'a str,
- comment_start: &'a str,
- comment_end: &'a str,
-}
-
-impl Default for Syntax<'_> {
- fn default() -> Self {
- Self {
- block_start: "{%",
- block_end: "%}",
- expr_start: "{{",
- expr_end: "}}",
- comment_start: "{#",
- comment_end: "#}",
- }
- }
-}
-
-impl<'a> TryFrom<RawSyntax<'a>> for Syntax<'a> {
- type Error = CompileError;
-
- fn try_from(raw: RawSyntax<'a>) -> std::result::Result<Self, Self::Error> {
- let default = Self::default();
- let syntax = Self {
- block_start: raw.block_start.unwrap_or(default.block_start),
- block_end: raw.block_end.unwrap_or(default.block_end),
- expr_start: raw.expr_start.unwrap_or(default.expr_start),
- expr_end: raw.expr_end.unwrap_or(default.expr_end),
- comment_start: raw.comment_start.unwrap_or(default.comment_start),
- comment_end: raw.comment_end.unwrap_or(default.comment_end),
- };
-
- if syntax.block_start.len() != 2
- || syntax.block_end.len() != 2
- || syntax.expr_start.len() != 2
- || syntax.expr_end.len() != 2
- || syntax.comment_start.len() != 2
- || syntax.comment_end.len() != 2
- {
- return Err("length of delimiters must be two".into());
- }
-
- let bs = syntax.block_start.as_bytes()[0];
- let be = syntax.block_start.as_bytes()[1];
- let cs = syntax.comment_start.as_bytes()[0];
- let ce = syntax.comment_start.as_bytes()[1];
- let es = syntax.expr_start.as_bytes()[0];
- let ee = syntax.expr_start.as_bytes()[1];
- if !((bs == cs && bs == es) || (be == ce && be == ee)) {
- return Err(format!("bad delimiters block_start: {}, comment_start: {}, expr_start: {}, needs one of the two characters in common", syntax.block_start, syntax.comment_start, syntax.expr_start).into());
- }
-
- Ok(syntax)
- }
-}
-
-#[cfg_attr(feature = "serde", derive(Deserialize))]
-#[derive(Default)]
-struct RawConfig<'d> {
- #[cfg_attr(feature = "serde", serde(borrow))]
- general: Option<General<'d>>,
- syntax: Option<Vec<RawSyntax<'d>>>,
- escaper: Option<Vec<RawEscaper<'d>>>,
-}
-
-impl RawConfig<'_> {
- #[cfg(feature = "config")]
- fn from_toml_str(s: &str) -> std::result::Result<RawConfig<'_>, CompileError> {
- toml::from_str(s).map_err(|e| format!("invalid TOML in {}: {}", CONFIG_FILE_NAME, e).into())
- }
-
- #[cfg(not(feature = "config"))]
- fn from_toml_str(_: &str) -> std::result::Result<RawConfig<'_>, CompileError> {
- Err("TOML support not available".into())
- }
-}
-
-#[derive(Clone, Copy, PartialEq, Eq, Debug)]
-#[cfg_attr(feature = "serde", derive(Deserialize))]
-#[cfg_attr(feature = "serde", serde(field_identifier, rename_all = "lowercase"))]
-pub(crate) enum WhitespaceHandling {
- /// The default behaviour. It will leave the whitespace characters "as is".
- 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 Default for WhitespaceHandling {
- fn default() -> Self {
- WhitespaceHandling::Preserve
- }
-}
-
-#[cfg_attr(feature = "serde", derive(Deserialize))]
-struct General<'a> {
- #[cfg_attr(feature = "serde", serde(borrow))]
- dirs: Option<Vec<&'a str>>,
- default_syntax: Option<&'a str>,
- #[cfg_attr(feature = "serde", serde(default))]
- whitespace: WhitespaceHandling,
-}
-
-#[cfg_attr(feature = "serde", derive(Deserialize))]
-struct RawSyntax<'a> {
- name: &'a str,
- block_start: Option<&'a str>,
- block_end: Option<&'a str>,
- expr_start: Option<&'a str>,
- expr_end: Option<&'a str>,
- comment_start: Option<&'a str>,
- comment_end: Option<&'a str>,
-}
-
-#[cfg_attr(feature = "serde", derive(Deserialize))]
-struct RawEscaper<'a> {
- path: &'a str,
- extensions: Vec<&'a str>,
-}
-
-fn read_config_file(config_path: &Option<String>) -> std::result::Result<String, CompileError> {
- let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
- let filename = match config_path {
- Some(config_path) => root.join(config_path),
- None => root.join(CONFIG_FILE_NAME),
- };
-
- if filename.exists() {
- fs::read_to_string(&filename)
- .map_err(|_| format!("unable to read {:?}", filename.to_str().unwrap()).into())
- } else if config_path.is_some() {
- Err(format!("`{}` does not exist", root.display()).into())
- } else {
- Ok("".to_string())
- }
-}
-
-fn str_set<T>(vals: &[T]) -> HashSet<String>
-where
- T: ToString,
-{
- vals.iter().map(|s| s.to_string()).collect()
-}
-
-#[allow(clippy::match_wild_err_arm)]
-fn get_template_source(tpl_path: &Path) -> std::result::Result<String, CompileError> {
- match fs::read_to_string(tpl_path) {
- Err(_) => Err(format!(
- "unable to open template file '{}'",
- tpl_path.to_str().unwrap()
- )
- .into()),
- Ok(mut source) => {
- if source.ends_with('\n') {
- let _ = source.pop();
- }
- Ok(source)
- }
- }
-}
-
-static CONFIG_FILE_NAME: &str = "askama.toml";
-static DEFAULT_SYNTAX_NAME: &str = "default";
-static DEFAULT_ESCAPERS: &[(&[&str], &str)] = &[
- (&["html", "htm", "xml"], "::askama::Html"),
- (&["md", "none", "txt", "yml", ""], "::askama::Text"),
- (&["j2", "jinja", "jinja2"], "::askama::Html"),
-];
-
#[derive(Debug, Clone)]
struct CompileError {
msg: Cow<'static, str>,
@@ -446,203 +152,10 @@ impl From<String> for CompileError {
#[cfg(test)]
#[allow(clippy::blacklisted_name)]
mod tests {
- use super::*;
- use std::env;
- use std::path::{Path, PathBuf};
-
- #[test]
- fn get_source() {
- let path = Config::new("")
- .and_then(|config| config.find_template("b.html", None))
- .unwrap();
- assert_eq!(get_template_source(&path).unwrap(), "bar");
- }
-
- #[test]
- fn test_default_config() {
- let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
- root.push("templates");
- let config = Config::new("").unwrap();
- assert_eq!(config.dirs, vec![root]);
- }
-
- #[cfg(feature = "config")]
- #[test]
- fn test_config_dirs() {
- let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
- root.push("tpl");
- let config = Config::new("[general]\ndirs = [\"tpl\"]").unwrap();
- assert_eq!(config.dirs, vec![root]);
- }
-
- fn assert_eq_rooted(actual: &Path, expected: &str) {
- let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
- root.push("templates");
- let mut inner = PathBuf::new();
- inner.push(expected);
- assert_eq!(actual.strip_prefix(root).unwrap(), inner);
- }
-
- #[test]
- fn find_absolute() {
- let config = Config::new("").unwrap();
- let root = config.find_template("a.html", None).unwrap();
- let path = config.find_template("sub/b.html", Some(&root)).unwrap();
- assert_eq_rooted(&path, "sub/b.html");
- }
-
- #[test]
- #[should_panic]
- fn find_relative_nonexistent() {
- let config = Config::new("").unwrap();
- let root = config.find_template("a.html", None).unwrap();
- config.find_template("c.html", Some(&root)).unwrap();
- }
-
- #[test]
- fn find_relative() {
- let config = Config::new("").unwrap();
- let root = config.find_template("sub/b.html", None).unwrap();
- let path = config.find_template("c.html", Some(&root)).unwrap();
- assert_eq_rooted(&path, "sub/c.html");
- }
-
- #[test]
- fn find_relative_sub() {
- let config = Config::new("").unwrap();
- let root = config.find_template("sub/b.html", None).unwrap();
- let path = config.find_template("sub1/d.html", Some(&root)).unwrap();
- assert_eq_rooted(&path, "sub/sub1/d.html");
- }
-
- #[cfg(feature = "config")]
- #[test]
- fn add_syntax() {
- let raw_config = r#"
- [general]
- default_syntax = "foo"
-
- [[syntax]]
- name = "foo"
- block_start = "{<"
-
- [[syntax]]
- name = "bar"
- expr_start = "{!"
- "#;
-
- let default_syntax = Syntax::default();
- let config = Config::new(raw_config).unwrap();
- assert_eq!(config.default_syntax, "foo");
-
- let foo = config.syntaxes.get("foo").unwrap();
- assert_eq!(foo.block_start, "{<");
- assert_eq!(foo.block_end, default_syntax.block_end);
- assert_eq!(foo.expr_start, default_syntax.expr_start);
- assert_eq!(foo.expr_end, default_syntax.expr_end);
- assert_eq!(foo.comment_start, default_syntax.comment_start);
- assert_eq!(foo.comment_end, default_syntax.comment_end);
-
- let bar = config.syntaxes.get("bar").unwrap();
- assert_eq!(bar.block_start, default_syntax.block_start);
- assert_eq!(bar.block_end, default_syntax.block_end);
- assert_eq!(bar.expr_start, "{!");
- assert_eq!(bar.expr_end, default_syntax.expr_end);
- assert_eq!(bar.comment_start, default_syntax.comment_start);
- assert_eq!(bar.comment_end, default_syntax.comment_end);
- }
-
- #[cfg(feature = "config")]
- #[test]
- fn add_syntax_two() {
- let raw_config = r#"
- syntax = [{ name = "foo", block_start = "{<" },
- { name = "bar", expr_start = "{!" } ]
-
- [general]
- default_syntax = "foo"
- "#;
-
- let default_syntax = Syntax::default();
- let config = Config::new(raw_config).unwrap();
- assert_eq!(config.default_syntax, "foo");
-
- let foo = config.syntaxes.get("foo").unwrap();
- assert_eq!(foo.block_start, "{<");
- assert_eq!(foo.block_end, default_syntax.block_end);
- assert_eq!(foo.expr_start, default_syntax.expr_start);
- assert_eq!(foo.expr_end, default_syntax.expr_end);
- assert_eq!(foo.comment_start, default_syntax.comment_start);
- assert_eq!(foo.comment_end, default_syntax.comment_end);
-
- let bar = config.syntaxes.get("bar").unwrap();
- assert_eq!(bar.block_start, default_syntax.block_start);
- assert_eq!(bar.block_end, default_syntax.block_end);
- assert_eq!(bar.expr_start, "{!");
- assert_eq!(bar.expr_end, default_syntax.expr_end);
- assert_eq!(bar.comment_start, default_syntax.comment_start);
- assert_eq!(bar.comment_end, default_syntax.comment_end);
- }
-
- #[cfg(feature = "toml")]
- #[should_panic]
- #[test]
- fn use_default_at_syntax_name() {
- let raw_config = r#"
- syntax = [{ name = "default" }]
- "#;
-
- let _config = Config::new(raw_config).unwrap();
- }
+ use std::fmt;
- #[cfg(feature = "toml")]
- #[should_panic]
- #[test]
- fn duplicated_syntax_name_on_list() {
- let raw_config = r#"
- syntax = [{ name = "foo", block_start = "~<" },
- { name = "foo", block_start = "%%" } ]
- "#;
-
- let _config = Config::new(raw_config).unwrap();
- }
-
- #[cfg(feature = "toml")]
- #[should_panic]
- #[test]
- fn is_not_exist_default_syntax() {
- let raw_config = r#"
- [general]
- default_syntax = "foo"
- "#;
-
- let _config = Config::new(raw_config).unwrap();
- }
-
- #[cfg(feature = "config")]
- #[test]
- fn escape_modes() {
- let config = Config::new(
- r#"
- [[escaper]]
- path = "::askama::Js"
- extensions = ["js"]
- "#,
- )
- .unwrap();
- assert_eq!(
- config.escapers,
- vec![
- (str_set(&["js"]), "::askama::Js".into()),
- (str_set(&["html", "htm", "xml"]), "::askama::Html".into()),
- (
- str_set(&["md", "none", "txt", "yml", ""]),
- "::askama::Text".into()
- ),
- (str_set(&["j2", "jinja", "jinja2"]), "::askama::Html".into()),
- ]
- );
- }
+ use super::*;
+ use crate::{DynTemplate, Template};
#[test]
fn dyn_template() {
@@ -682,37 +195,4 @@ mod tests {
test.dyn_write_into(&mut vec).unwrap();
assert_eq!(vec, vec![b't', b'e', b's', b't']);
}
-
- #[test]
- fn test_whitespace_parsing() {
- let config = Config::new(
- r#"
- [general]
- whitespace = "suppress"
- "#,
- )
- .unwrap();
- assert_eq!(config.whitespace, WhitespaceHandling::Suppress);
-
- let config = Config::new(r#""#).unwrap();
- assert_eq!(config.whitespace, WhitespaceHandling::Preserve);
-
- let config = Config::new(
- r#"
- [general]
- whitespace = "preserve"
- "#,
- )
- .unwrap();
- assert_eq!(config.whitespace, WhitespaceHandling::Preserve);
-
- let config = Config::new(
- r#"
- [general]
- whitespace = "minimize"
- "#,
- )
- .unwrap();
- assert_eq!(config.whitespace, WhitespaceHandling::Minimize);
- }
}
diff --git a/askama_shared/src/parser.rs b/askama_shared/src/parser.rs
index 19df795..efcad73 100644
--- a/askama_shared/src/parser.rs
+++ b/askama_shared/src/parser.rs
@@ -10,7 +10,8 @@ use nom::multi::{fold_many0, many0, many1, separated_list0, separated_list1};
use nom::sequence::{delimited, pair, preceded, terminated, tuple};
use nom::{self, error_position, AsChar, IResult, InputTakeAtPosition};
-use crate::{CompileError, Syntax};
+use crate::config::Syntax;
+use crate::CompileError;
#[derive(Debug, PartialEq)]
pub(crate) enum Node<'a> {
@@ -1224,7 +1225,7 @@ pub(crate) fn parse<'a>(
#[cfg(test)]
mod tests {
use super::{Expr, Node, Whitespace, Ws};
- use crate::Syntax;
+ use crate::config::Syntax;
fn check_ws_split(s: &str, res: &(&str, &str, &str)) {
match super::split_ws_parts(s) {