diff --git a/src-tauri/templates/src/parser.rs b/src-tauri/templates/src/parser.rs index 98b3b303..7970e3c7 100644 --- a/src-tauri/templates/src/parser.rs +++ b/src-tauri/templates/src/parser.rs @@ -1,8 +1,14 @@ +#[derive(Clone, PartialEq, Debug)] +pub struct FnArg { + pub name: String, + pub value: Val, +} + #[derive(Clone, PartialEq, Debug)] pub enum Val { Str(String), Var(String), - Fn { name: String, args: Vec }, + Fn { name: String, args: Vec }, } #[derive(Clone, PartialEq, Debug)] @@ -85,7 +91,7 @@ impl Parser { #[allow(dead_code)] fn debug_pos(&self, x: &str) { println!( - r#"Position: {x} -- [{}] = {} --> "{}" --> {:?}"#, + r#"Position: {x}: text[{}]='{}' → "{}" → {:?}"#, self.pos, self.chars[self.pos], self.chars.iter().collect::(), @@ -105,7 +111,7 @@ impl Parser { } } - fn parse_fn(&mut self) -> Option<(String, Vec)> { + fn parse_fn(&mut self) -> Option<(String, Vec)> { let start_pos = self.pos; let name = match self.parse_ident() { @@ -127,21 +133,39 @@ impl Parser { Some((name, args)) } - fn parse_fn_args(&mut self) -> Option> { + fn parse_fn_args(&mut self) -> Option> { if !self.match_str("(") { return None; } let start_pos = self.pos; - let mut args: Vec = Vec::new(); + let mut args: Vec = Vec::new(); + + // Fn closed immediately + self.skip_whitespace(); + if self.match_str(")") { + return Some(args) + } + while self.pos < self.chars.len() { self.skip_whitespace(); - if let Some(v) = self.parse_value() { - args.push(v); + + let name = self.parse_ident(); + self.skip_whitespace(); + self.match_str("="); + self.skip_whitespace(); + let value = self.parse_value(); + self.skip_whitespace(); + + if let (Some(name), Some(value)) = (name.clone(), value.clone()) { + args.push(FnArg { name, value }); + } else { + // Didn't find valid thing, so return + self.pos = start_pos; + return None; } - self.skip_whitespace(); if self.match_str(")") { break; } @@ -339,13 +363,16 @@ mod tests { #[test] fn fn_ident_arg() { - let mut p = Parser::new("${[ foo(bar) ]}"); + let mut p = Parser::new("${[ foo(a=bar) ]}"); assert_eq!( p.parse(), vec![ Token::Tag(Val::Fn { name: "foo".into(), - args: vec![Val::Var("bar".into())], + args: vec![FnArg { + name: "a".into(), + value: Val::Var("bar".into()) + }], }), Token::Eof ] @@ -354,16 +381,25 @@ mod tests { #[test] fn fn_ident_args() { - let mut p = Parser::new("${[ foo(bar,baz, qux ) ]}"); + let mut p = Parser::new("${[ foo(a=bar,b = baz, c =qux ) ]}"); assert_eq!( p.parse(), vec![ Token::Tag(Val::Fn { name: "foo".into(), args: vec![ - Val::Var("bar".into()), - Val::Var("baz".into()), - Val::Var("qux".into()), + FnArg { + name: "a".into(), + value: Val::Var("bar".into()) + }, + FnArg { + name: "b".into(), + value: Val::Var("baz".into()) + }, + FnArg { + name: "c".into(), + value: Val::Var("qux".into()) + }, ], }), Token::Eof @@ -373,16 +409,25 @@ mod tests { #[test] fn fn_mixed_args() { - let mut p = Parser::new(r#"${[ foo(bar,"baz \"hi\"", qux ) ]}"#); + let mut p = Parser::new(r#"${[ foo(aaa=bar,bb="baz \"hi\"", c=qux ) ]}"#); assert_eq!( p.parse(), vec![ Token::Tag(Val::Fn { name: "foo".into(), args: vec![ - Val::Var("bar".into()), - Val::Str(r#"baz "hi""#.into()), - Val::Var("qux".into()), + FnArg { + name: "aaa".into(), + value: Val::Var("bar".into()) + }, + FnArg { + name: "bb".into(), + value: Val::Str(r#"baz "hi""#.into()) + }, + FnArg { + name: "c".into(), + value: Val::Var("qux".into()) + }, ], }), Token::Eof @@ -392,18 +437,54 @@ mod tests { #[test] fn fn_nested() { - let mut p = Parser::new(r#"${[ outer(inner(foo, "i"), "o") ]}"#); + let mut p = Parser::new("${[ foo(b=bar()) ]}"); + assert_eq!( + p.parse(), + vec![ + Token::Tag(Val::Fn { + name: "foo".into(), + args: vec![FnArg { + name: "b".into(), + value: Val::Fn { + name: "bar".into(), + args: vec![], + } + }], + }), + Token::Eof + ] + ); + } + + #[test] + fn fn_nested_args() { + let mut p = Parser::new(r#"${[ outer(a=inner(a=foo, b="i"), c="o") ]}"#); assert_eq!( p.parse(), vec![ Token::Tag(Val::Fn { name: "outer".into(), args: vec![ - Val::Fn { - name: "inner".into(), - args: vec![Val::Var("foo".into()), Val::Str("i".into()),], + FnArg { + name: "a".into(), + value: Val::Fn { + name: "inner".into(), + args: vec![ + FnArg { + name: "a".into(), + value: Val::Var("foo".into()) + }, + FnArg { + name: "b".into(), + value: Val::Str("i".into()), + }, + ], + } + }, + FnArg { + name: "c".into(), + value: Val::Str("o".into()) }, - Val::Str("o".into()) ], }), Token::Eof diff --git a/src-tauri/templates/src/renderer.rs b/src-tauri/templates/src/renderer.rs index 779a58e5..d3ac8989 100644 --- a/src-tauri/templates/src/renderer.rs +++ b/src-tauri/templates/src/renderer.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; -use crate::{Parser, Token, Val}; +use crate::{FnArg, Parser, Token, Val}; -type TemplateCallback = fn(name: &str, args: Vec) -> String; +type TemplateCallback = fn(name: &str, args: HashMap) -> String; pub fn parse_and_render( template: &str, @@ -32,11 +32,7 @@ pub fn render( return doc_str.join(""); } -fn render_tag( - val: Val, - vars: &HashMap, - cb: Option, -) -> String { +fn render_tag(val: Val, vars: &HashMap, cb: Option) -> String { match val { Val::Str(s) => s.into(), Val::Var(name) => match vars.get(name.as_str()) { @@ -48,11 +44,22 @@ fn render_tag( let resolved_args = args .iter() .map(|a| match a { - Val::Str(s) => s.to_string(), - Val::Var(i) => vars.get(i.as_str()).unwrap_or(&empty).to_string(), - val => render_tag(val.clone(), vars, cb), + FnArg { + name, + value: Val::Str(s), + } => (name.to_string(), s.to_string()), + FnArg { + name, + value: Val::Var(i), + } => ( + name.to_string(), + vars.get(i.as_str()).unwrap_or(&empty).to_string(), + ), + FnArg { name, value: val } => { + (name.to_string(), render_tag(val.clone(), vars, cb)) + } }) - .collect::>(); + .collect::>(); match cb { Some(cb) => cb(name.as_str(), resolved_args), None => "".into(), @@ -102,11 +109,11 @@ mod tests { #[test] fn render_valid_fn() { let vars = HashMap::new(); - let template = r#"${[ say_hello("John", "Kate") ]}"#; - let result = r#"say_hello: ["John", "Kate"]"#; + let template = r#"${[ say_hello(a="John", b="Kate") ]}"#; + let result = r#"say_hello: 2, Some("John") Some("Kate")"#; - fn cb(name: &str, args: Vec) -> String { - format!("{name}: {:?}", args) + fn cb(name: &str, args: HashMap) -> String { + format!("{name}: {}, {:?} {:?}", args.len(), args.get("a"), args.get("b")) } assert_eq!(parse_and_render(template, &vars, Some(cb)), result); } @@ -114,12 +121,12 @@ mod tests { #[test] fn render_nested_fn() { let vars = HashMap::new(); - let template = r#"${[ upper(secret()) ]}"#; + let template = r#"${[ upper(foo=secret()) ]}"#; let result = r#"ABC"#; - fn cb(name: &str, args: Vec) -> String { + fn cb(name: &str, args: HashMap) -> String { match name { "secret" => "abc".to_string(), - "upper" => args[0].to_string().to_uppercase(), + "upper" => args["foo"].to_string().to_uppercase(), _ => "".to_string(), } }