diff options
Diffstat (limited to 'askama_shared')
| -rw-r--r-- | askama_shared/src/config.rs | 536 | ||||
| -rw-r--r-- | askama_shared/src/generator.rs | 5 | ||||
| -rw-r--r-- | askama_shared/src/heritage.rs | 3 | ||||
| -rw-r--r-- | askama_shared/src/input.rs | 3 | ||||
| -rw-r--r-- | askama_shared/src/lib.rs | 532 | ||||
| -rw-r--r-- | askama_shared/src/parser.rs | 5 | 
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) {  | 
