diff --git a/.gitignore b/.gitignore index 2763cd604118ac7bc2655b3c3d4013777b53d9ad..10a0d4bb8636b6947e6c3f929be1d97b6c190d47 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -tests/ +local_tests/ Cargo.lock target/ utils_macro/ \ No newline at end of file diff --git a/README.md b/README.md index d2196441d36b92497c1f2c103bdb8439f2386090..e60aa899f4732f78478a90f591ae8b4fab5015e5 100644 --- a/README.md +++ b/README.md @@ -27,3 +27,12 @@ lto = true codegen-units = 1 panic = "abort" ``` + +### Divers + +``` +clear ; clear ; DEBUG=1 ./install.sh +clear ; clear ; cargo build --release && DEBUG=1 ./tests/tests.sh "target/release/moustachet" "./tests/*.test" +clear ; clear ; RUST_BACKTRACE=1 cargo run -- -r --input ./local_tests/source4.test -v myvar="very" +``` + diff --git a/install.sh b/install.sh index 6dd28b29effd9b94ca155c42c95482e63980f1e9..80e0bba59c8d0e3f777ad8a03e31bf30a0c2d2dd 100755 --- a/install.sh +++ b/install.sh @@ -25,8 +25,19 @@ else echo "See : https://linux.die.net/man/1/upx" fi +echo "--> TESTS step" +./tests/tests.sh "target/$ARCHI/release/moustache" "./tests/*.test" +if [ $? != 0 ] +then + exit 1 +fi + echo "--> INSTALLATION step" -# sudo cp "target/$ARCHI/release/moustache" /usr/bin/moustache -# sudo chmod 775 /usr/bin/moustache cp "target/$ARCHI/release/moustache" ~/.local/bin/moustache echo "Installation in your local bin directory" +if [ "$1" = "+sudo" ] +then + sudo cp "target/$ARCHI/release/moustache" /usr/bin/moustache + sudo chmod 775 /usr/bin/moustache + echo "Installation in your general bin directory" +fi diff --git a/src/engine/document.rs b/src/engine/document.rs index 8fdae58ad1ea003034468e4a1f6793eebde5a524..e64369e7c9c738daa534c3f76a0dfeb8224975f7 100644 --- a/src/engine/document.rs +++ b/src/engine/document.rs @@ -2,6 +2,7 @@ use std::fs; use crate::create_internal_error; use crate::engine::environment; +use crate::engine::Environment; use crate::engine::resolver; use crate::utils::conf::Configuration; use crate::utils::error::InternalError; @@ -172,7 +173,8 @@ impl<'c> Document<'c> { } Ok(true) } - pub fn transform(&mut self) { + pub fn transform(&mut self, env: &mut Environment) { + env.transform(self); let mut destination: String = "".to_string(); for p in &self.stack { match p { diff --git a/src/engine/environment.rs b/src/engine/environment.rs index 577de49048eb23c7f9b0284d0d8ba170fd7581f0..63495273e79535706114db046e37708aff1dd7ec 100644 --- a/src/engine/environment.rs +++ b/src/engine/environment.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use crate::engine::document::Part; use crate::utils::conf::Configuration; +use crate::engine::Document; #[derive(Debug)] pub struct Environment { @@ -35,4 +36,19 @@ impl Environment { pub fn get_block(&self, key: &String) -> Option<&Vec<Part>> { self.blocks.get(key) } + pub fn transform(&mut self, doc: &Document) { + for block in self.blocks.values_mut() { + let mut destination: String = "".to_string(); + for value in &mut *block { + match value { + &mut Part::StaticText(s, e) => destination.push_str(&doc.source[s..e]), + Part::GeneratedText(s) => destination.push_str(&s[..]), + &mut Part::Statement(s, e) => destination.push_str(&doc.source[s..e]), + &mut Part::Expression(s, e) => destination.push_str(&doc.source[s..e]), + Part::Comment(_, _) => (), + } + } + *block = vec!(Part::GeneratedText(destination)); + } + } } diff --git a/src/engine/extensions/default.rs b/src/engine/extensions/ext_default.rs similarity index 96% rename from src/engine/extensions/default.rs rename to src/engine/extensions/ext_default.rs index 9f89425b486feea03859f442364c2939ce19e4fa..3abe8781bbda768c51180c2d9496d401e383fdd0 100644 --- a/src/engine/extensions/default.rs +++ b/src/engine/extensions/ext_default.rs @@ -3,7 +3,7 @@ use crate::engine::extensions::Helper; use crate::engine::extensions::HelperFunction; use crate::engine::extensions::Value; -static MODULE_NAME: &'static str = "default"; +pub static MODULE_NAME: &'static str = "default"; fn recursive_uppercase(value: Value) -> Value { match value { @@ -92,7 +92,6 @@ pub fn help() -> Helper { Helper { module_name: MODULE_NAME, module_description: "Generic functions (UTF-8 compatibility)", - module_autor: "Julien Garderon <julien.garderon@gmail.com>", functions: vec![ HelperFunction { function_name: "uppercase", diff --git a/src/engine/extensions/ext_macro.rs b/src/engine/extensions/ext_macro.rs new file mode 100644 index 0000000000000000000000000000000000000000..b35ad7d683342374d294e36101b3f011d9249b37 --- /dev/null +++ b/src/engine/extensions/ext_macro.rs @@ -0,0 +1,97 @@ +use crate::engine::extensions::Context; +use crate::engine::extensions::Helper; +use crate::engine::extensions::HelperFunction; +use crate::engine::extensions::Value; + +pub static MODULE_NAME: &'static str = "macro"; + +fn transform_to_text(v: &Value) -> String { + match v { + Value::Text(t) => t.to_string(), + Value::Symbol(s) => s.to_string(), + Value::Number(n) => n.to_string(), + Value::Vector(v) => { + if v.len() == 0 { + "".to_string() + } else { + v.iter() + .map(|s| transform_to_text(s)) + .reduce(|acc: String, next: String| acc + &next) + .unwrap() + } + } + Value::True => "true".to_string(), + Value::False | Value::Void => "".to_string(), + } +} + +fn execute_convert_to_text(context: &mut Context) -> Option<String> { + let v = match context.result.take() { + Some(v) => v, + None => match context.args.len() { + 0 => return Some("void pipe and arg".to_string()), + 1 => match context.args.remove(0) { + Value::Text(t) => Value::Text(t.to_string()), + Value::Symbol(s) => Value::Text(s.to_string()), + v => return Some(format!("invalid token {:?}", v)), + } + _ => return Some("too much args".to_string()), + } + }; + let r = transform_to_text(&v); + context.result = Some(Value::Text(r)); + None +} + +fn execute_convert_to_symbol(context: &mut Context) -> Option<String> { + let v = match context.result.take() { + Some(v) => v, + None => match context.args.len() { + 0 => return Some("void pipe and arg".to_string()), + 1 => match context.args.remove(0) { + Value::Text(t) => Value::Text(t.to_string()), + Value::Symbol(s) => Value::Text(s.to_string()), + v => return Some(format!("invalid token {:?}", v)), + } + _ => return Some("too much args".to_string()), + } + }; + let r = transform_to_text(&v); + if r.is_empty() { + return Some("the conversion produced an empty symbol (not allowed)".to_string()) + } + context.result = Some(Value::Symbol(r)); + None +} + +pub fn execute(context: &mut Context) -> Option<String> { + match &context.fct_name[..] { + "convert_to_text" => execute_convert_to_text(context), + "convert_to_symbol" => execute_convert_to_symbol(context), + fct_name => Some(format!( + "module {} : unknow function name '{}'", + MODULE_NAME, fct_name + )), + } +} + +pub fn help() -> Helper { + Helper { + module_name: MODULE_NAME, + module_description: "Macro functions", + functions: vec![ + HelperFunction { + function_name: "convert_to_text", + function_description: "Convert any token or arg to a text", + function_can_pipe: true, + function_args: "Token or only one arg (priority to pipe)", + }, + HelperFunction { + function_name: "convert_to_symbol", + function_description: "Convert any token or arg to a non-null symbol", + function_can_pipe: true, + function_args: "Token or only one arg (priority to pipe)", + }, + ], + } +} diff --git a/src/engine/extensions/mod.rs b/src/engine/extensions/mod.rs index d516f5edff340fe45dc92f17eac2c6c21ebab556..3d6c594b5b08e4077b67ba8ee974aff9871cda63 100644 --- a/src/engine/extensions/mod.rs +++ b/src/engine/extensions/mod.rs @@ -1,4 +1,5 @@ -pub mod default; +pub mod ext_default; +pub mod ext_macro; use crate::engine::Document; use crate::engine::Environment; @@ -23,7 +24,7 @@ pub struct Context<'a> { pub doc_position: usize, pub env: &'a mut Environment, pub source: &'a str, - pub fct_name: String, + pub fct_name: &'a str, pub args: Vec<Value>, } @@ -41,7 +42,7 @@ impl<'a> Context<'a> { doc_position: doc_position, env: env, source: source, - fct_name: "".to_string(), + fct_name: &source[0..0], args: vec![], } } @@ -51,7 +52,6 @@ impl<'a> Context<'a> { pub struct Helper { module_name: &'static str, module_description: &'static str, - module_autor: &'static str, functions: Vec<HelperFunction>, } @@ -59,9 +59,9 @@ impl Helper { pub fn display(&self) { println!( " - ♦ Extension '{}' - by {} + ♦ Extension '{}' {}", - self.module_name, self.module_autor, self.module_description + self.module_name, self.module_description ); for f in self.functions.iter() { f.display(); @@ -92,3 +92,19 @@ impl HelperFunction { ); } } + +pub fn execute<'a>(module: &str, context: &mut Context) -> Option<String> { + match module { + m if m == ext_default::MODULE_NAME => ext_default::execute(context), + m if m == ext_macro::MODULE_NAME => ext_macro::execute(context), + m => Some(format!( + "Extension '{}' not found (--help-extensions argument may assist you)", + m + )), + } +} + +pub fn help() { + ext_default::help().display(); + ext_macro::help().display(); +} diff --git a/src/engine/parser.rs b/src/engine/parser.rs index 137e175996d537d3a2e5217d4f5ec18eef61aec5..677b8b6acd597d8e302eca6fffb0d4b67ab1707d 100644 --- a/src/engine/parser.rs +++ b/src/engine/parser.rs @@ -100,10 +100,6 @@ pub fn parse<'a>(source: &'a str) -> Result<Vec<Token>, InternalError> { let mut portion_start: usize = 0; let mut is_escaping: bool = false; for (i, c) in source.char_indices() { - // println!( - // "(boucle) i = {:?} ; c = {:?} ; is_text = {:?} ; portion_start = {:?} ; is_escaping = {:?}", - // i, c, is_text, portion_start, is_escaping - // ); if is_text == true && is_escaping == true { is_escaping = false; continue; @@ -112,7 +108,7 @@ pub fn parse<'a>(source: &'a str) -> Result<Vec<Token>, InternalError> { } match c { ' ' | '\t' | '\n' | '\r' if is_text == false => { - if i > 0 && portion_start < i - 1 { + if i > 0 && portion_start < i { stack.push(Token::Symbol(portion_start, i)); } let space_type = match c { @@ -132,70 +128,70 @@ pub fn parse<'a>(source: &'a str) -> Result<Vec<Token>, InternalError> { portion_start = i + 1; } '(' if is_text == false => { - if portion_start < i - 1 { + if portion_start < i { stack.push(Token::Symbol(portion_start, i)); } stack.push(Token::ParenthesisOpening); portion_start = i + 1; } ')' if is_text == false => { - if portion_start < i - 1 { + if portion_start < i { stack.push(Token::Symbol(portion_start, i)); } stack.push(Token::ParenthesisEnding); portion_start = i + 1; } '+' if is_text == false => { - if portion_start < i - 1 { + if portion_start < i { stack.push(Token::Symbol(portion_start, i)); } stack.push(Token::Plus); portion_start = i + 1; } '-' if is_text == false => { - if portion_start < i - 1 { + if portion_start < i { stack.push(Token::Symbol(portion_start, i)); } stack.push(Token::Minus); portion_start = i + 1; } '=' if is_text == false => { - if portion_start < i - 1 { + if portion_start < i { stack.push(Token::Symbol(portion_start, i)); } stack.push(Token::Equal); portion_start = i + 1; } '/' if is_text == false => { - if portion_start < i - 1 { + if portion_start < i { stack.push(Token::Symbol(portion_start, i)); } stack.push(Token::Divide); portion_start = i + 1; } '*' if is_text == false => { - if portion_start < i - 1 { + if portion_start < i { stack.push(Token::Symbol(portion_start, i)); } stack.push(Token::Multiply); portion_start = i + 1; } '|' if is_text == false => { - if portion_start < i - 1 { + if portion_start < i { stack.push(Token::Symbol(portion_start, i)); } stack.push(Token::Pipe); portion_start = i + 1; } '&' if is_text == false => { - if portion_start < i - 1 { + if portion_start < i { stack.push(Token::Symbol(portion_start, i)); } stack.push(Token::Ampersand); portion_start = i + 1; } '!' if is_text == false => { - if portion_start < i - 1 { + if portion_start < i { stack.push(Token::Symbol(portion_start, i)); } stack.push(Token::Exclamation); diff --git a/src/engine/resolver/statement/mod.rs b/src/engine/resolver/statement/mod.rs index ec4d6384e8a39e004c3d7b1ff892f852b1c478a4..06122ac89214c5ebe13cd73d796ec4d89343b4ce 100644 --- a/src/engine/resolver/statement/mod.rs +++ b/src/engine/resolver/statement/mod.rs @@ -109,7 +109,8 @@ pub fn resolve_statement<'a>( return Err(add_step_internal_error!( err, "error in 'set' statement", - format!("source = '\x1b[3m{}\x1b[0m'", source.trim()) + format!("source = '\x1b[3m{}\x1b[0m'", source.trim()), + "must be = '\x1b[3mset [symbol] = [text or symbol (+ text or symbol (+ ...))]\x1b[0m'" )) } }, @@ -118,8 +119,7 @@ pub fn resolve_statement<'a>( Err(mut err) => { return Err(add_step_internal_error!( err, - "error in 'execute' statement", - format!("source = '\x1b[3m{}\x1b[0m'", source.trim()) + "error in 'execute' statement" )) } }, diff --git a/src/engine/resolver/statement/unit_execute.rs b/src/engine/resolver/statement/unit_execute.rs index bd66f19023b746965282fa70ec60c2c53c877ed7..a53f807d491ee1cca8b88cded18460a59891a17b 100644 --- a/src/engine/resolver/statement/unit_execute.rs +++ b/src/engine/resolver/statement/unit_execute.rs @@ -3,7 +3,7 @@ use std::iter::Peekable; use crate::add_step_internal_error; use crate::create_internal_error; -use crate::engine::extensions::default; +use crate::engine::extensions; use crate::engine::extensions::Context; use crate::engine::extensions::Value; use crate::engine::resolver::statement::Token; @@ -15,7 +15,7 @@ fn resolve_fct<'a>( context: &mut Context, iter_tokens: &mut Peekable<Iter<'_, Token>>, ) -> Option<String> { - let fct: String; + let fct: &str; loop { let token = match iter_tokens.next() { Some(t) => t, @@ -24,7 +24,7 @@ fn resolve_fct<'a>( match token { Token::Space(_) => (), &Token::Symbol(s, e) => { - fct = context.source[s..e].to_string(); + fct = &context.source[s..e]; break; } t => { @@ -83,15 +83,12 @@ fn resolve_fct<'a>( fct )); } - context.fct_name = f.get(1).unwrap().to_string(); + context.fct_name = f.get(1).unwrap(); context.args = args; - match *f.get(0).unwrap() { - "default" => default::execute(context), - n => Some(format!( - "Extension '{}' not found (--help-extensions argument may assist you)", - n - )), - } + extensions::execute( + f.get(0).unwrap(), + context, + ) } fn cast(env: &mut Environment, results: Option<Value>) -> Result<String, String> { @@ -143,7 +140,6 @@ pub fn resolve_unit<'a>( let token = match iter_tokens.next() { Some(t) => t, None => { - println!("---------- > none"); return Err(create_internal_error!( "The statement can't be empty".to_string() )); diff --git a/src/engine/resolver/statement/unit_set.rs b/src/engine/resolver/statement/unit_set.rs index be07cf305fb681152e319cba8db480aa4fdeca52..df510bf9b94959b3adee9fe59c2e2513a74434f6 100644 --- a/src/engine/resolver/statement/unit_set.rs +++ b/src/engine/resolver/statement/unit_set.rs @@ -16,10 +16,7 @@ pub fn resolve_unit<'a>( let token = match iter_tokens.next() { Some(t) => t, None => { - return Err(create_internal_error!( - "Statement can't be empty", - "must be = '\x1b[3mset [symbol] = [text or symbol (+ text or symbol (+ ...))]\x1b[0m'" - )) + return Err(create_internal_error!("Statement can't be empty")) } }; match token { @@ -30,12 +27,7 @@ pub fn resolve_unit<'a>( } t => { return Err(create_internal_error!( - format!("Found '{}' in first part (must be Token::Symbol)", t), - "must be = '\x1b[3mset [symbol] = [text or symbol (+ text or symbol (+ ...))]\x1b[0m'", - format!( - "found statement (here with trim !) = '\x1b[3m{}\x1b[0m'", - source.trim() - ) + format!("Found '{}' in first part (must be Token::Symbol)", t) )); } } @@ -45,12 +37,7 @@ pub fn resolve_unit<'a>( Some(t) => t, None => { return Err(create_internal_error!( - "Statement must be complete (token 'equal' not found, premature end)", - "must be = '\x1b[3mset [symbol] = [text or symbol (+ text or symbol (+ ...))]\x1b[0m'", - format!( - "found statement (here with trim !) = '\x1b[3m{}\x1b[0m'", - source.trim() - ) + "Statement must be complete (token 'equal' not found, premature end)" )) } }; @@ -60,12 +47,7 @@ pub fn resolve_unit<'a>( t => { return Err(create_internal_error!( "Incorrect token after the first part (must be Token::Equal)", - "must be = '\x1b[3mset [symbol] = [text or symbol (+ text or symbol (+ ...))]\x1b[0m'", - format!( - "found statement (here with trim !) = '\x1b[3m{}\x1b[0m' (incorrect token : {})", - source.trim(), - t - ) + format!("token found: {})", t) )) } } @@ -73,6 +55,7 @@ pub fn resolve_unit<'a>( let mut value: Vec<String> = vec![]; let mut begining: bool = true; let mut operator: bool = false; + let mut if_part: bool = false; loop { let token = match iter_tokens.next() { @@ -80,14 +63,11 @@ pub fn resolve_unit<'a>( None => { if begining { return Err(create_internal_error!( - "The second part cannot be empty", - "must be = '\x1b[3mset [symbol] = [text or symbol (+ text or symbol (+ ...))]\x1b[0m'" + "The second part cannot be empty" )); } else if operator == false { return Err(create_internal_error!( - "Invalid ending : an operator without symbol or text after", - "must be = '\x1b[3mset [symbol] = [text or symbol (+ text or symbol (+ ...))]\x1b[0m'", - format!("found statement (here with trim !) = '\x1b[3m{}\x1b[0m'", source.trim()) + "Invalid ending : an operator without symbol or text after" )); } else { break; @@ -113,10 +93,8 @@ pub fn resolve_unit<'a>( } else { return Err(create_internal_error!( "Operator missing between symbol or text", - "must be = '\x1b[3mset [symbol] = [text or symbol (+ text or symbol (+ ...))]\x1b[0m'", format!( - "found statement (here with trim !) = '\x1b[3m{}\x1b[0m' (error on symbol : '{}' - position {} ~> {})", - source.trim(), + "error on symbol : '{}' - position {} ~> {}", key, s, e @@ -131,10 +109,8 @@ pub fn resolve_unit<'a>( } else { return Err(create_internal_error!( "Operator missing between symbol or text", - "must be = '\x1b[3mset [symbol] = [text or symbol (+ text or symbol (+ ...))]\x1b[0m'", format!( - "found statement (here with trim !) = '\x1b[3m{}\x1b[0m' (error on text : '{}' - position {} ~> {})", - source.trim(), + "error on text : '{}' - position {} ~> {}", source[s..e].to_string(), s, e @@ -142,28 +118,112 @@ pub fn resolve_unit<'a>( )); } } - &Token::Plus => { + Token::Plus => { if operator == true { operator = false; } else { return Err(create_internal_error!( - "Symbol or text missing before operator", - "must be = '\x1b[3mset [symbol] = [text or symbol (+ text or symbol (+ ...))]\x1b[0m'" + "Symbol or text missing before operator" + )); + } + } + Token::Exclamation => { + if operator == true { + if_part = true; + break; + } else { + return Err(create_internal_error!( + "an 'if' part cannot directly follow an operator" )); } } t => { return Err(create_internal_error!( - format!("Found '{}' in first part (must be Token::Symbol)", t), - "must be = '\x1b[3mset [symbol] = [text or symbol (+ text or symbol (+ ...))]\x1b[0m'", - format!( - "found statement (here with trim !) = '\x1b[3m{}\x1b[0m'", - source.trim() - ) + format!("Found '{}' in second part (must be Token::Symbol)", t) )) } } } - env.set(key, value.into_iter().collect::<String>()); + let mut empty: bool = true; + if if_part { + loop { + let token = match iter_tokens.next() { + Some(t) => t, + None => { + return Err(create_internal_error!("'if' part can't be empty (first token)")) + } + }; + match token { + Token::Space(_) => (), + &Token::Symbol(s, e) => { + let s: &str = &source[s..e]; + if s == "if" { + break; + } else { + return Err(create_internal_error!( + format!("Found '{}' in 'if' part of statement (must be Token::Symbol['if'])", s) + )); + } + } + t => { + return Err(create_internal_error!( + format!("Found '{}' in 'if' part of statement (must be Token::Symbol['if'])", t) + )); + } + } + } + loop { + let token = match iter_tokens.next() { + Some(t) => t, + None => { + return Err(create_internal_error!("'if' part can't be empty (second token)")) + } + }; + match token { + Token::Space(_) => (), + &Token::Symbol(s, e) => { + match &source[s..e] { + "empty" => break, + "setted" => { + empty = false; + break; + }, + s => return Err(create_internal_error!( + format!("Found '{}' in 'if' part of statement (must be Token::Symbol['empty' or 'setted'])", s) + )) + } + } + t => { + return Err(create_internal_error!( + format!("Found '{}' in 'if' part of statement (must be Token::Symbol['if'])", t) + )); + } + } + } + } + let setting: bool = if if_part { + let exists = match env.get(&key) { + Some(_) => true, + None => false, + }; + if empty { + if exists { + false + } else { + true + } + } else { + if exists { + true + } else { + false + } + } + } else { + true + }; + if setting { + env.set(key, value.into_iter().collect::<String>()); + } Ok(()) } diff --git a/src/main.rs b/src/main.rs index a0fd05bea1feed455f7f2d717dcbc2c38015eb0a..097de536b2d106c1020d7723bc1a49b0926d2082 100644 --- a/src/main.rs +++ b/src/main.rs @@ -108,7 +108,7 @@ fn main() { Ok(changed) => { if changed { let _ = display_debug_block!(conf, "Resolve parts", "Document is changed"); - doc.transform(); + doc.transform(&mut env); } else { if reentrance > 0 { let _ = display_debug_block!(conf, "Resolve parts", "Document is not changed"); @@ -142,7 +142,7 @@ fn main() { let _ = display_debug_block!(conf, "Write output", "Path = {:?}", path); } }, - None => println!("{}", doc.source), + None => print!("{}", doc.source), } display_debug_title!(conf, "End of program, no errors"); diff --git a/src/utils/args.rs b/src/utils/args.rs index f6cac7f4a24cc265d4c4340bd91ec144287c4aae..1b393112f955e32fdd66ac3bdbf8f0c991766f26 100644 --- a/src/utils/args.rs +++ b/src/utils/args.rs @@ -1,7 +1,7 @@ use std::env; // use utils_macro::modifier_item; -use crate::engine::extensions::default; +use crate::engine::extensions; use crate::utils::APP_AUTHOR; use crate::utils::APP_DATE; use crate::utils::APP_NAME; @@ -104,5 +104,5 @@ Extensions documentation ------------------------ " ); - default::help().display(); + extensions::help(); } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 81fc84df3ad7dc1bee61951fc98917d77f1077ea..e0f6c2ffe32f6afb562482dfe4be78e5cfaffa28 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -3,7 +3,7 @@ pub mod conf; pub mod error; pub static APP_NAME: &'static str = "Moustache"; -pub static APP_VERSION: &'static str = "v1.0.0"; +pub static APP_VERSION: &'static str = "v1.0.0-release-candidate"; pub static APP_DATE: &'static str = "april 2024"; pub static APP_AUTHOR: &'static str = "Julien Garderon <julien.garderon@gmail.com>"; diff --git a/tests/t1.test b/tests/t1.test new file mode 100644 index 0000000000000000000000000000000000000000..a6fd94f0807435b69ed2bfa9259a2148533104a2 --- /dev/null +++ b/tests/t1.test @@ -0,0 +1,29 @@ +---separator +$moustache -v myvar="all fine" -v my_another_var="!" +---separator +{{ myvar }} +{{myvar}} +{{ myvar}} +{{myvar }} +{{ myvar + my_another_var }} +{{ myvar+my_another_var }} +{{ myvar+ my_another_var }} +{{ myvar +my_another_var }} +{{myvar+my_another_var}} +{{ myvar+my_another_var}} +{{myvar+my_another_var }} +{{ myvar + my_another_var + my_another_var }} +---separator +all fine +all fine +all fine +all fine +all fine! +all fine! +all fine! +all fine! +all fine! +all fine! +all fine! +all fine!! +---separator diff --git a/tests/t2.test b/tests/t2.test new file mode 100644 index 0000000000000000000000000000000000000000..1b65a79964183251395c8198081337711a6df8e9 --- /dev/null +++ b/tests/t2.test @@ -0,0 +1,13 @@ +---separator +$moustache -v myvar="all fine" +---separator +{% set myvar = "all fine but another" %}{{ myvar }} +{% set a = "0" ! if empty %}{{ a }} +{% set a = "1" %}{{ a }} +{% set a = "2" ! if setted %}{{ a }} +---separator +all fine but another +0 +1 +2 +---separator diff --git a/tests/t3.test b/tests/t3.test new file mode 100644 index 0000000000000000000000000000000000000000..b77fc588e8cda25fb4d3bdd4429811e6bfbd6cfe --- /dev/null +++ b/tests/t3.test @@ -0,0 +1,22 @@ +---separator +$moustache 2>&1 || exit 0 +---separator +{{ undefined_var }} +---separator + +-- ERROR FOUND + +[0] >> Error during resolving + v1.0.0-release-candidate/src/main.rs#120) + +[1] >> Error in expression + v1.0.0-release-candidate/src/engine/resolver/mod.rs#33) + real position of expression in document = 0 -> 19 + target expression (here with trim !) = 'undefined_var' + must be in the following form = '{{ text or symbol (+ text or symbol (+ ...)) }}' + +[2] >> Undefined variable 'undefined_var' in environment + v1.0.0-release-candidate/src/engine/resolver/expression/mod.rs#68) + +-- +---separator diff --git a/tests/t4.test b/tests/t4.test new file mode 100644 index 0000000000000000000000000000000000000000..b6352310d7fd270e0352bdbedecd55fba2c77f90 --- /dev/null +++ b/tests/t4.test @@ -0,0 +1,19 @@ +---separator +$moustache -r -v myvar=very +---separator +{% block "myblock" %}all is {{ myvar }} fine!{% endblock %} +{% call "myblock" %} +{% call "myblock" %} +{% set name_of_block = "b2" %} +{% block name_of_block %}no{% endblock %} +{% call name_of_block %} +{% block "b2" %}nothing is {{ myvar }} better.{% endblock %} +{% if myvar == "very" %} +{% call name_of_block %} +{% endif %} +---separator +all is very fine! +all is very fine! +no +nothing is very better. +---separator diff --git a/tests/tests.sh b/tests/tests.sh new file mode 100755 index 0000000000000000000000000000000000000000..f2c1719911f4696f57550cb1b5e0e0ac7e622af7 --- /dev/null +++ b/tests/tests.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash + +function display_title() { + [[ $QUIET == "" ]] && echo "$1" +} + +function display_result() { + [[ $QUIET == "" ]] && echo " --> $1" +} + +function debug() { + [ $DEBUG ] && echo " >> $1" +} + +function retrieve_part() { + TESTFILE_PATH=$1 + TESTFILE_SEPARATOR=$2 + ITER=0 + FOUND= + debug "try retrieve $3..." + while read -r line + do + debug "read line n°$ITER : $line" + if [ "$line" == "$TESTFILE_SEPARATOR" ] + then + FOUND=$ITER + break + fi + ITER=$(expr $ITER + 1) + done < <(tail -n "+$GLOBAL_ITER" "$TESTFILE_PATH") + `echo declare -g "$3"_START=$GLOBAL_ITER` + `echo declare -g "$3"_END=$ITER` + GLOBAL_ITER=$(expr $GLOBAL_ITER + $ITER + 1) + if [ "$FOUND" ] + then + debug "$3 found ($FOUND line[s]) ! next step..." + else + debug "$3 not found ! incorrect test file" + exit 1 + fi +} + +function execute_test () { + # --- step 0 + EXEC_PATH=$1 + TESTFILE_PATH=$2 + debug "process file : '$TESTFILE_PATH'" + TESTFILE_SEPARATOR=`head -n 1 "$TESTFILE_PATH"` + debug "found separator : '$TESTFILE_SEPARATOR'" + GLOBAL_ITER=2 + # --- step 1 + retrieve_part $TESTFILE_PATH $TESTFILE_SEPARATOR "CONF" + TEST_COMMAND=`tail -n "+$CONF_START" "$TESTFILE_PATH" | head -n "$CONF_END" | tr -d "\n"` + # --- step 2 + retrieve_part $TESTFILE_PATH $TESTFILE_SEPARATOR "SOURCE_IN" + # --- step 3 + retrieve_part $TESTFILE_PATH $TESTFILE_SEPARATOR "SOURCE_OUT" + # --- step 4 + display_result "process command : '$TEST_COMMAND'" + RESULT="`tail -n "+$SOURCE_IN_START" "$TESTFILE_PATH" | head -n "$SOURCE_IN_END" | sh -c "moustache='$EXEC_PATH'; $TEST_COMMAND 2>&1"`" + EXPECTED_RESULT="`tail -n "+$SOURCE_OUT_START" "$TESTFILE_PATH" | head -n "$SOURCE_OUT_END"`" + RESULT=$(echo $RESULT | LC_ALL=C.UTF8 sed -E "s/\x1B\[[\x30-\x3F]*[\x20-\x20F]*[\x40-\x7E]//g") + EXPECTED_RESULT=$(echo $EXPECTED_RESULT | LC_ALL=C.UTF8 sed -E "s/\x1B\[[\x30-\x3F]*[\x20-\x20F]*[\x40-\x7E]//g") + debug "return : !EOF" + debug "$RESULT" + debug "EOF!" + debug "expected : !EOF" + debug "$EXPECTED_RESULT" + debug "EOF!" + echo "return code $RETURNCODE" + if [ "$EXPECTED_RESULT" = "$RESULT" ] + then + debug "expected result found" + return 0 + else + debug "expected result not found" + return 1 + fi +} + +function execute_tests() { + if [[ $2 == "" ]] + then + display_title "tests path not defined" + exit 0 + fi + for path in `ls $2` + do + display_title "Found '$path' test file" + execute_test "$1" "$path" + RETURNCODE="$?" + if [ "$RETURNCODE" == 0 ] + then + display_result "test passed !" + else + display_result "test failed !" + KO=$(expr $KO + 1) + fi + SUM=$(expr $SUM + 1) + done +} + +if [ "$DEBUG" == "0" ] +then + DEBUG= +fi + +display_title "EXECUTE PATH = $1" + +SUM=0 +KO=0 +execute_tests "$1" "$2" +if [ $KO == 0 ] +then + display_title "All tests passed ($SUM test[s])" + exit 0 +else + display_title "$KO test[s] failed (/ $SUM test[s])" + exit 1 +fi +