aboutsummaryrefslogtreecommitdiffstats
path: root/askama_shared/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--askama_shared/src/lib.rs532
1 files changed, 6 insertions, 526 deletions
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);
- }
}