diff options
author | bott <mhpoin@gmail.com> | 2018-10-05 05:51:22 +0200 |
---|---|---|
committer | Dirkjan Ochtman <dirkjan@ochtman.nl> | 2018-10-05 15:55:14 +0200 |
commit | 4bd302c68817fc78ff31594b1175a3b67cece171 (patch) | |
tree | f85361f6dc3d3045576aaae9efaacd0daed2a228 | |
parent | af880b3f202d3b94a7a38ec9a8503d3a18d924e6 (diff) | |
download | askama-4bd302c68817fc78ff31594b1175a3b67cece171.tar.gz askama-4bd302c68817fc78ff31594b1175a3b67cece171.tar.bz2 askama-4bd302c68817fc78ff31594b1175a3b67cece171.zip |
Add changing delimiters support
Diffstat (limited to '')
-rw-r--r-- | askama/src/lib.rs | 5 | ||||
-rw-r--r-- | askama_derive/src/generator.rs | 6 | ||||
-rw-r--r-- | askama_derive/src/input.rs | 46 | ||||
-rw-r--r-- | askama_derive/src/lib.rs | 13 | ||||
-rw-r--r-- | askama_derive/src/parser.rs | 174 | ||||
-rw-r--r-- | askama_shared/src/lib.rs | 223 |
6 files changed, 364 insertions, 103 deletions
diff --git a/askama/src/lib.rs b/askama/src/lib.rs index 9d39f47..0acf837 100644 --- a/askama/src/lib.rs +++ b/askama/src/lib.rs @@ -363,7 +363,7 @@ pub trait Template { pub use askama_derive::*; pub use shared::filters; -pub use shared::{Error, MarkupDisplay, Result}; +pub use shared::{read_config_file, Error, MarkupDisplay, Result}; #[cfg(feature = "with-iron")] pub mod iron { @@ -441,7 +441,8 @@ fn visit_dirs(dir: &Path, cb: &Fn(&DirEntry)) -> io::Result<()> { /// that have templates, to make sure the crate gets rebuilt when template /// source code changes. pub fn rerun_if_templates_changed() { - for template_dir in &shared::Config::new().dirs { + let file = read_config_file(); + for template_dir in &shared::Config::new(&file).dirs { visit_dirs(template_dir, &|e: &DirEntry| { println!("cargo:rerun-if-changed={}", e.path().to_str().unwrap()); }).unwrap(); diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 2a233d5..33fbe4c 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -1,6 +1,6 @@ use super::{get_template_source, Context, Heritage}; use input::TemplateInput; -use parser::{self, Cond, Expr, MatchParameter, MatchVariant, Node, Target, When, WS}; +use parser::{Cond, Expr, MatchParameter, MatchVariant, Node, Target, When, WS}; use shared::filters; use proc_macro2::Span; @@ -13,6 +13,8 @@ use std::{cmp, hash, str}; use syn; +use parser::parse; + pub(crate) fn generate( input: &TemplateInput, contexts: &HashMap<&PathBuf, Context>, @@ -482,7 +484,7 @@ impl<'a> Generator<'a> { .config .find_template(path, Some(&self.input.path)); let src = get_template_source(&path); - let nodes = parser::parse(&src); + let nodes = parse(&src, self.input.syntax); { // Since nodes must not outlive the Generator, we instantiate // a nested Generator here to handle the include's nodes. diff --git a/askama_derive/src/input.rs b/askama_derive/src/input.rs index 14531cf..5d3fa76 100644 --- a/askama_derive/src/input.rs +++ b/askama_derive/src/input.rs @@ -2,7 +2,7 @@ use proc_macro2::TokenStream; use quote::ToTokens; -use shared::Config; +use shared::{Config, Syntax}; use std::path::PathBuf; @@ -10,7 +10,8 @@ use syn; pub struct TemplateInput<'a> { pub ast: &'a syn::DeriveInput, - pub config: Config, + pub config: &'a Config<'a>, + pub syntax: &'a Syntax<'a>, pub source: Source, pub print: Print, pub escaping: EscapeMode, @@ -24,7 +25,7 @@ impl<'a> TemplateInput<'a> { /// mostly recovers the data for the `TemplateInput` fields from the /// `template()` attribute list fields; it also finds the of the `_parent` /// field, if any. - pub fn new(ast: &'a syn::DeriveInput) -> TemplateInput<'a> { + pub fn new<'n>(ast: &'n syn::DeriveInput, config: &'n Config) -> TemplateInput<'n> { // Check that an attribute called `template()` exists and that it is // the proper type (list). let mut meta = None; @@ -53,6 +54,7 @@ impl<'a> TemplateInput<'a> { let mut print = Print::None; let mut escaping = None; let mut ext = None; + let mut syntax = None; for nm_item in meta_list.nested { if let syn::NestedMeta::Meta(ref item) = nm_item { if let syn::Meta::NameValue(ref pair) = item { @@ -88,6 +90,11 @@ impl<'a> TemplateInput<'a> { } else { panic!("ext value must be string literal"); }, + "syntax" => if let syn::Lit::Str(ref s) = pair.lit { + syntax = Some(s.value()) + } else { + panic!("syntax value must be string literal"); + }, attr => panic!("unsupported annotation key '{}' found", attr), } } @@ -97,7 +104,6 @@ impl<'a> TemplateInput<'a> { // Validate the `source` and `ext` value together, since they are // related. In case `source` was used instead of `path`, the value // of `ext` is merged into a synthetic `path` value here. - let config = Config::new(); let source = source.expect("template path or source not found in attributes"); let path = match (&source, &ext) { (&Source::Path(ref path), None) => config.find_template(path, None), @@ -124,6 +130,37 @@ impl<'a> TemplateInput<'a> { _ => None, }; + // Validate syntax + let syntax = syntax.map_or_else( + || config.syntaxes.get(config.default_syntax).unwrap(), + |s| { + config + .syntaxes + .get(&s) + .expect(&format!("attribute syntax {} not exist", s)) + }, + ); + + 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 + { + panic!("length of delimiters must be two") + } + + 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.block_start.as_bytes()[0]; + let ee = syntax.block_start.as_bytes()[1]; + if !(bs == cs && bs == es) && !(be == ce && be == ee) { + panic!("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); + } + TemplateInput { ast, config, @@ -133,6 +170,7 @@ impl<'a> TemplateInput<'a> { ext, parent, path, + syntax, } } } diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index 45d98b9..cb7793f 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -14,8 +14,9 @@ mod parser; use input::{Print, Source, TemplateInput}; use parser::{Expr, Macro, Node}; use proc_macro::TokenStream; -use shared::Config; +use shared::{read_config_file, Config}; +use parser::parse; use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; @@ -34,7 +35,9 @@ pub fn derive_template(input: TokenStream) -> TokenStream { /// the parse tree and/or generated source according to the `print` key's /// value as passed to the `template()` attribute. fn build_template(ast: &syn::DeriveInput) -> String { - let input = TemplateInput::new(ast); + let file = read_config_file(); + let config = Config::new(&file); + let input = TemplateInput::new(ast, &config); let source: String = match input.source { Source::Source(ref s) => s.clone(), Source::Path(_) => get_template_source(&input.path), @@ -45,7 +48,7 @@ fn build_template(ast: &syn::DeriveInput) -> String { let mut parsed = HashMap::new(); for (path, src) in &sources { - parsed.insert(path, parser::parse(src)); + parsed.insert(path, parse(src, input.syntax)); } let mut contexts = HashMap::new(); @@ -74,7 +77,7 @@ fn build_template(ast: &syn::DeriveInput) -> String { fn find_used_templates(input: &TemplateInput, map: &mut HashMap<PathBuf, String>, source: String) { let mut check = vec![(input.path.clone(), source)]; while let Some((path, source)) = check.pop() { - for n in parser::parse(&source) { + for n in parse(&source, input.syntax) { match n { Node::Extends(Expr::StrLit(extends)) => { let extends = input.config.find_template(extends, Some(&path)); @@ -220,7 +223,7 @@ mod tests { #[test] fn get_source() { - let path = Config::new().find_template("b.html", None); + let path = Config::new("").find_template("b.html", None); assert_eq!(get_template_source(&path), "bar"); } } diff --git a/askama_derive/src/parser.rs b/askama_derive/src/parser.rs index 184dc8d..d5dd5dc 100644 --- a/askama_derive/src/parser.rs +++ b/askama_derive/src/parser.rs @@ -4,6 +4,8 @@ use nom; use std::str; +use shared::Syntax; + #[derive(Debug)] pub enum Expr<'a> { NumLit(&'a str), @@ -111,18 +113,33 @@ enum ContentState { End(usize), } -fn take_content(i: Input) -> Result<(Input, Node), nom::Err<Input>> { +fn take_content<'a>(i: Input<'a>, s: &'a Syntax<'a>) -> Result<(Input<'a>, Node<'a>), nom::Err<Input<'a>>>{ use parser::ContentState::*; + let bs = s.block_start.as_bytes()[0]; + let be = s.block_start.as_bytes()[1]; + let cs = s.comment_start.as_bytes()[0]; + let ce = s.comment_start.as_bytes()[1]; + let es = s.expr_start.as_bytes()[0]; + let ee = s.expr_start.as_bytes()[1]; + let mut state = Any; for (idx, c) in i.iter().enumerate() { - state = match (state, *c) { - (Any, b'{') => Brace(idx), - (Brace(start), b'{') | - (Brace(start), b'%') | - (Brace(start), b'#') => End(start), - (Any, _) | - (Brace(_), _) => Any, - (End(_), _) => panic!("cannot happen"), + state = match state { + Any => { + if *c == bs || *c == es || *c == cs { + Brace(idx) + } else { + Any + } + } + Brace(start) => { + if *c == be || *c == ee || *c == ce { + End(start) + } else { + Any + } + } + End(_) => panic!("cannot happen"), }; if let End(_) = state { break; @@ -444,12 +461,12 @@ named!(expr_any<Input, Expr>, alt!( expr_or )); -named!(expr_node<Input, Node>, do_parse!( - tag_s!("{{") >> +named_args!(expr_node<'a>(s: &'a Syntax<'a>) <Input<'a>, Node<'a>>, do_parse!( + call!(tag_expr_start, s) >> pws: opt!(tag_s!("-")) >> expr: ws!(expr_any) >> nws: opt!(tag_s!("-")) >> - tag_s!("}}") >> + call!(tag_expr_end, s) >> (Node::Expr(WS(pws.is_some(), nws.is_some()), expr)) )); @@ -473,25 +490,25 @@ named!(cond_if<Input, Expr>, do_parse!( (cond) )); -named!(cond_block<Input, Cond>, do_parse!( - tag_s!("{%") >> +named_args!(cond_block<'a>(s: &'a Syntax<'a>) <Input<'a>, Cond<'a>>, do_parse!( + call!(tag_block_start, s) >> pws: opt!(tag_s!("-")) >> ws!(tag_s!("else")) >> cond: opt!(cond_if) >> nws: opt!(tag_s!("-")) >> - tag_s!("%}") >> - block: parse_template >> + call!(tag_block_end, s) >> + block: call!(parse_template, s) >> (WS(pws.is_some(), nws.is_some()), cond, block) )); -named!(block_if<Input, Node>, do_parse!( +named_args!(block_if<'a>(s: &'a Syntax<'a>) <Input<'a>, Node<'a>>, do_parse!( pws1: opt!(tag_s!("-")) >> cond: ws!(cond_if) >> nws1: opt!(tag_s!("-")) >> - tag_s!("%}") >> - block: parse_template >> - elifs: many0!(cond_block) >> - tag_s!("{%") >> + call!(tag_block_end, s) >> + block: call!(parse_template, s) >> + elifs: many0!(call!(cond_block, s)) >> + call!(tag_block_start, s) >> pws2: opt!(tag_s!("-")) >> ws!(tag_s!("endif")) >> nws2: opt!(tag_s!("-")) >> @@ -503,38 +520,38 @@ named!(block_if<Input, Node>, do_parse!( }) )); -named!(match_else_block<Input, When>, do_parse!( - tag_s!("{%") >> +named_args!(match_else_block<'a>(s: &'a Syntax<'a>) <Input<'a>, When<'a>>, do_parse!( + call!(tag_block_start, s) >> pws: opt!(tag_s!("-")) >> ws!(tag_s!("else")) >> nws: opt!(tag_s!("-")) >> - tag_s!("%}") >> - block: parse_template >> + call!(tag_block_end, s) >> + block: call!(parse_template, s) >> (WS(pws.is_some(), nws.is_some()), None, vec![], block) )); -named!(when_block<Input, When>, do_parse!( - tag_s!("{%") >> +named_args!(when_block<'a>(s: &'a Syntax<'a>) <Input<'a>, When<'a>>, do_parse!( + call!(tag_block_start, s) >> pws: opt!(tag_s!("-")) >> ws!(tag_s!("when")) >> variant: ws!(match_variant) >> params: opt!(ws!(with_parameters)) >> nws: opt!(tag_s!("-")) >> - tag_s!("%}") >> - block: parse_template >> + call!(tag_block_end, s) >> + block: call!(parse_template, s) >> (WS(pws.is_some(), nws.is_some()), Some(variant), params.unwrap_or_default(), block) )); -named!(block_match<Input, Node>, do_parse!( +named_args!(block_match<'a>(s: &'a Syntax<'a>) <Input<'a>, Node<'a>>, do_parse!( pws1: opt!(tag_s!("-")) >> ws!(tag_s!("match")) >> expr: ws!(expr_any) >> nws1: opt!(tag_s!("-")) >> - tag_s!("%}") >> - inter: opt!(take_content) >> - arms: many1!(when_block) >> - else_arm: opt!(match_else_block) >> - ws!(tag_s!("{%")) >> + call!(tag_block_end, s) >> + inter: opt!(call!(take_content, s)) >> + arms: many1!(call!(when_block, s)) >> + else_arm: opt!(call!(match_else_block, s)) >> + ws!(call!(tag_block_start, s)) >> pws2: opt!(tag_s!("-")) >> ws!(tag_s!("endmatch")) >> nws2: opt!(tag_s!("-")) >> @@ -581,16 +598,16 @@ named!(block_let<Input, Node>, do_parse!( }) )); -named!(block_for<Input, Node>, do_parse!( +named_args!(block_for<'a>(s: &'a Syntax<'a>) <Input<'a>, Node<'a>>, do_parse!( pws1: opt!(tag_s!("-")) >> ws!(tag_s!("for")) >> var: ws!(target_single) >> ws!(tag_s!("in")) >> iter: ws!(expr_any) >> nws1: opt!(tag_s!("-")) >> - tag_s!("%}") >> - block: parse_template >> - tag_s!("{%") >> + call!(tag_block_end, s) >> + block: call!(parse_template, s) >> + call!(tag_block_start, s) >> pws2: opt!(tag_s!("-")) >> ws!(tag_s!("endfor")) >> nws2: opt!(tag_s!("-")) >> @@ -605,14 +622,14 @@ named!(block_extends<Input, Node>, do_parse!( (Node::Extends(name)) )); -named!(block_block<Input, Node>, do_parse!( +named_args!(block_block<'a>(s: &'a Syntax<'a>) <Input<'a>, Node<'a>>, do_parse!( pws1: opt!(tag_s!("-")) >> ws!(tag_s!("block")) >> name: ws!(identifier) >> nws1: opt!(tag_s!("-")) >> - tag_s!("%}") >> - contents: parse_template >> - tag_s!("{%") >> + call!(tag_block_end, s) >> + contents: call!(parse_template, s) >> + call!(tag_block_start, s) >> pws2: opt!(tag_s!("-")) >> ws!(tag_s!("endblock")) >> opt!(ws!(tag_s!(name))) >> @@ -646,15 +663,15 @@ named!(block_import<Input, Node>, do_parse!( }, scope)) )); -named!(block_macro<Input, Node>, do_parse!( +named_args!(block_macro<'a>(s: &'a Syntax<'a>) <Input<'a>, Node<'a>>, do_parse!( pws1: opt!(tag_s!("-")) >> ws!(tag_s!("macro")) >> name: ws!(identifier) >> params: ws!(parameters) >> nws1: opt!(tag_s!("-")) >> - tag_s!("%}") >> - contents: parse_template >> - tag_s!("{%") >> + call!(tag_block_end, s) >> + contents: call!(parse_template, s) >> + call!(tag_block_start, s) >> pws2: opt!(tag_s!("-")) >> ws!(tag_s!("endmacro")) >> nws2: opt!(tag_s!("-")) >> @@ -674,41 +691,48 @@ named!(block_macro<Input, Node>, do_parse!( }) )); -named!(block_node<Input, Node>, do_parse!( - tag_s!("{%") >> +named_args!(block_node<'a>(s: &'a Syntax<'a>) <Input<'a>, Node<'a>>, do_parse!( + call!(tag_block_start, s) >> contents: alt!( block_call | block_let | - block_if | - block_for | - block_match | + call!(block_if, s) | + call!(block_for, s) | + call!(block_match, s) | block_extends | block_include | block_import | - block_block | - block_macro + call!(block_block, s) | + call!(block_macro, s) ) >> - tag_s!("%}") >> + call!(tag_block_end, s) >> (contents) )); -named!(block_comment<Input, Node>, do_parse!( - tag_s!("{#") >> +named_args!(block_comment<'a>(s: &'a Syntax<'a>) <Input<'a>, Node<'a>>, do_parse!( + call!(tag_comment_start, s) >> pws: opt!(tag_s!("-")) >> - inner: take_until_s!("#}") >> - tag_s!("#}") >> + inner: take_until_s!(s.comment_end) >> + call!(tag_comment_end, s) >> (Node::Comment(WS(pws.is_some(), inner.len() > 1 && inner[inner.len() - 1] == b'-'))) )); -named!(parse_template<Input, Vec<Node>>, many0!(alt!( - take_content | - block_comment | - expr_node | - block_node +named_args!(parse_template<'a>(s: &'a Syntax<'a>)<Input<'a>, Vec<Node<'a>>>, many0!(alt!( + call!(take_content, s) | + call!(block_comment, s) | + call!(expr_node, s) | + call!(block_node, s) ))); -pub fn parse(src: &str) -> Vec<Node> { - match parse_template(Input(src.as_bytes())) { +named_args!(tag_block_start<'a>(s: &'a Syntax<'a>) <Input<'a>, Input<'a>>, tag!(s.block_start)); +named_args!(tag_block_end<'a>(s: &'a Syntax<'a>) <Input<'a>, Input<'a>>, tag!(s.block_end)); +named_args!(tag_comment_start<'a>(s: &'a Syntax<'a>) <Input<'a>, Input<'a>>, tag!(s.comment_start)); +named_args!(tag_comment_end<'a>(s: &'a Syntax<'a>) <Input<'a>, Input<'a>>, tag!(s.comment_end)); +named_args!(tag_expr_start<'a>(s: &'a Syntax<'a>) <Input<'a>, Input<'a>>, tag!(s.expr_start)); +named_args!(tag_expr_end<'a>(s: &'a Syntax<'a>) <Input<'a>, Input<'a>>, tag!(s.expr_end)); + +pub fn parse<'a>(src: &'a str, syntax: &'a Syntax<'a>) -> Vec<Node<'a>> { + match parse_template(Input(src.as_bytes()), syntax) { Ok((left, res)) => { if !left.is_empty() { let s = str::from_utf8(left.0).unwrap(); @@ -725,6 +749,8 @@ pub fn parse(src: &str) -> Vec<Node> { #[cfg(test)] mod tests { + use shared::Syntax; + fn check_ws_split(s: &str, res: &(&str, &str, &str)) { let node = super::split_ws_parts(s.as_bytes()); match node { @@ -747,11 +773,23 @@ mod tests { #[test] #[should_panic] fn test_invalid_block() { - super::parse("{% extend \"blah\" %}"); + super::parse("{% extend \"blah\" %}", &Syntax::default()); } #[test] fn test_parse_filter() { - super::parse("{{ strvar|e }}"); + super::parse("{{ strvar|e }}", &Syntax::default()); } + + #[test] + fn change_delimiters_parse_filter() { + let syntax = Syntax { + expr_start: "{~", + expr_end: "~}", + ..Syntax::default() + }; + + super::parse("{~ strvar|e ~}", &syntax); + } + } diff --git a/askama_shared/src/lib.rs b/askama_shared/src/lib.rs index 5df6998..46efea0 100644 --- a/askama_shared/src/lib.rs +++ b/askama_shared/src/lib.rs @@ -17,29 +17,74 @@ mod escaping; pub use error::{Error, Result}; pub use escaping::MarkupDisplay; + +use std::collections::BTreeMap; + pub mod filters; -pub struct Config { +#[derive(Debug)] +pub struct Config<'a> { pub dirs: Vec<PathBuf>, + pub syntaxes: BTreeMap<String, Syntax<'a>>, + pub default_syntax: &'a str, } -impl Config { - pub fn new() -> Config { - let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - let filename = root.join(CONFIG_FILE_NAME); - if filename.exists() { - Self::from_str( - &fs::read_to_string(&filename) - .expect(&format!("unable to read {}", filename.to_str().unwrap())), - ) - } else { - Self::from_str("") +#[derive(Debug)] +pub struct Syntax<'a> { + pub block_start: &'a str, + pub block_end: &'a str, + pub expr_start: &'a str, + pub expr_end: &'a str, + pub comment_start: &'a str, + pub comment_end: &'a str, +} + +impl<'a> Default for Syntax<'a> { + fn default() -> Self { + Self { + block_start: "{%", + block_end: "%}", + expr_start: "{{", + expr_end: "}}", + comment_start: "{#", + comment_end: "#}", } } +} - pub fn from_str(s: &str) -> Self { +impl<'a> From<RawSyntax<'a>> for Syntax<'a> { + fn from(raw: RawSyntax<'a>) -> Self { + let syntax = Self::default(); + Self { + block_start: raw.block_start.unwrap_or(syntax.block_start), + block_end: raw.block_end.unwrap_or(syntax.block_end), + expr_start: raw.expr_start.unwrap_or(syntax.expr_start), + expr_end: raw.expr_end.unwrap_or(syntax.expr_end), + comment_start: raw.comment_start.unwrap_or(syntax.comment_start), + comment_end: raw.comment_end.unwrap_or(syntax.comment_end), + } + } +} + +pub fn read_config_file() -> String { + let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + let filename = root.join(CONFIG_FILE_NAME); + if filename.exists() { + fs::read_to_string(&filename) + .expect(&format!("unable to read {}", filename.to_str().unwrap())) + } else { + "".to_string() + } +} + +impl<'a> Config<'a> { + pub fn new(s: &str) -> Config { let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); let default = vec![root.join("templates")]; + + let mut syntaxes: BTreeMap<String, Syntax> = BTreeMap::new(); + syntaxes.insert(DEFAULT_SYNTAX_NAME.to_string(), Syntax::default()); + let raw: RawConfig = toml::from_str(&s).expect(&format!("invalid TOML in {}", CONFIG_FILE_NAME)); @@ -50,7 +95,31 @@ impl Config { Some(General { dirs: None }) | None => default, }; - Self { dirs } + if let Some(raw_syntaxes) = raw.syntax { + for raw_s in raw_syntaxes { + let name = raw_s.name; + + if let Some(_) = syntaxes.insert(name.to_string(), Syntax::from(raw_s)) { + panic!("named syntax \"{}\" already exist", name) + } + } + } + + let default_syntax = if let Some(default_syntax) = raw.default_syntax { + if syntaxes.contains_key(default_syntax) { + default_syntax + } else { + panic!("default syntax {} not exist", default_syntax) + } + } else { + DEFAULT_SYNTAX_NAME + }; + + Config { + dirs, + syntaxes, + default_syntax, + } } pub fn find_template(&self, path: &str, start_at: Option<&Path>) -> PathBuf { @@ -76,8 +145,10 @@ impl Config { } #[derive(Deserialize)] -struct RawConfig { +struct RawConfig<'d> { general: Option<General>, + syntax: Option<Vec<RawSyntax<'d>>>, + default_syntax: Option<&'d str>, } #[derive(Deserialize)] @@ -85,11 +156,23 @@ struct General { dirs: Option<Vec<String>>, } +#[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>, +} + static CONFIG_FILE_NAME: &str = "askama.toml"; +static DEFAULT_SYNTAX_NAME: &str = "default"; #[cfg(test)] mod tests { - use super::Config; + use super::*; use std::env; use std::path::{Path, PathBuf}; @@ -97,7 +180,7 @@ mod tests { fn test_default_config() { let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); root.push("templates"); - let config = Config::from_str(""); + let config = Config::new(""); assert_eq!(config.dirs, vec![root]); } @@ -105,7 +188,7 @@ mod tests { fn test_config_dirs() { let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); root.push("tpl"); - let config = Config::from_str("[general]\ndirs = [\"tpl\"]"); + let config = Config::new("[general]\ndirs = [\"tpl\"]"); assert_eq!(config.dirs, vec![root]); } @@ -119,7 +202,7 @@ mod tests { #[test] fn find_absolute() { - let config = Config::new(); + let config = Config::new(""); let root = config.find_template("a.html", None); let path = config.find_template("sub/b.html", Some(&root)); assert_eq_rooted(&path, "sub/b.html"); @@ -128,14 +211,14 @@ mod tests { #[test] #[should_panic] fn find_relative_nonexistent() { - let config = Config::new(); + let config = Config::new(""); let root = config.find_template("a.html", None); config.find_template("b.html", Some(&root)); } #[test] fn find_relative() { - let config = Config::new(); + let config = Config::new(""); let root = config.find_template("sub/b.html", None); let path = config.find_template("c.html", Some(&root)); assert_eq_rooted(&path, "sub/c.html"); @@ -143,9 +226,105 @@ mod tests { #[test] fn find_relative_sub() { - let config = Config::new(); + let config = Config::new(""); let root = config.find_template("sub/b.html", None); let path = config.find_template("sub1/d.html", Some(&root)); assert_eq_rooted(&path, "sub/sub1/d.html"); } + + #[test] + fn add_syntax() { + let raw_config = r#" + default_syntax = "foo" + + [[syntax]] + name = "foo" + block_start = "~<" + + [[syntax]] + name = "bar" + expr_start = "%%" + "#; + + let default_syntax = Syntax::default(); + let config = Config::new(raw_config); + 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); + } + + #[test] + fn add_syntax_two() { + let raw_config = r#" + default_syntax = "foo" + + syntax = [{ name = "foo", block_start = "~<" }, + { name = "bar", expr_start = "%%" } ] + "#; + + let default_syntax = Syntax::default(); + let config = Config::new(raw_config); + 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); + } + + #[should_panic] + #[test] + fn use_default_at_syntax_name() { + let raw_config = r#" + syntax = [{ name = "default" }] + "#; + + let _config = Config::new(raw_config); + } + + #[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); + } + + #[should_panic] + #[test] + fn is_not_exist_default_syntax() { + let raw_config = r#" + default_syntax = "foo" + "#; + + let _config = Config::new(raw_config); + } } |