mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-24 01:28:35 +02:00
Named arguments in templating
This commit is contained in:
@@ -1,8 +1,14 @@
|
|||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub struct FnArg {
|
||||||
|
pub name: String,
|
||||||
|
pub value: Val,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub enum Val {
|
pub enum Val {
|
||||||
Str(String),
|
Str(String),
|
||||||
Var(String),
|
Var(String),
|
||||||
Fn { name: String, args: Vec<Val> },
|
Fn { name: String, args: Vec<FnArg> },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
@@ -85,7 +91,7 @@ impl Parser {
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn debug_pos(&self, x: &str) {
|
fn debug_pos(&self, x: &str) {
|
||||||
println!(
|
println!(
|
||||||
r#"Position: {x} -- [{}] = {} --> "{}" --> {:?}"#,
|
r#"Position: {x}: text[{}]='{}' → "{}" → {:?}"#,
|
||||||
self.pos,
|
self.pos,
|
||||||
self.chars[self.pos],
|
self.chars[self.pos],
|
||||||
self.chars.iter().collect::<String>(),
|
self.chars.iter().collect::<String>(),
|
||||||
@@ -105,7 +111,7 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_fn(&mut self) -> Option<(String, Vec<Val>)> {
|
fn parse_fn(&mut self) -> Option<(String, Vec<FnArg>)> {
|
||||||
let start_pos = self.pos;
|
let start_pos = self.pos;
|
||||||
|
|
||||||
let name = match self.parse_ident() {
|
let name = match self.parse_ident() {
|
||||||
@@ -127,21 +133,39 @@ impl Parser {
|
|||||||
Some((name, args))
|
Some((name, args))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_fn_args(&mut self) -> Option<Vec<Val>> {
|
fn parse_fn_args(&mut self) -> Option<Vec<FnArg>> {
|
||||||
if !self.match_str("(") {
|
if !self.match_str("(") {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let start_pos = self.pos;
|
let start_pos = self.pos;
|
||||||
|
|
||||||
let mut args: Vec<Val> = Vec::new();
|
let mut args: Vec<FnArg> = Vec::new();
|
||||||
|
|
||||||
|
// Fn closed immediately
|
||||||
|
self.skip_whitespace();
|
||||||
|
if self.match_str(")") {
|
||||||
|
return Some(args)
|
||||||
|
}
|
||||||
|
|
||||||
while self.pos < self.chars.len() {
|
while self.pos < self.chars.len() {
|
||||||
self.skip_whitespace();
|
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(")") {
|
if self.match_str(")") {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -339,13 +363,16 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fn_ident_arg() {
|
fn fn_ident_arg() {
|
||||||
let mut p = Parser::new("${[ foo(bar) ]}");
|
let mut p = Parser::new("${[ foo(a=bar) ]}");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
p.parse(),
|
p.parse(),
|
||||||
vec![
|
vec![
|
||||||
Token::Tag(Val::Fn {
|
Token::Tag(Val::Fn {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
args: vec![Val::Var("bar".into())],
|
args: vec![FnArg {
|
||||||
|
name: "a".into(),
|
||||||
|
value: Val::Var("bar".into())
|
||||||
|
}],
|
||||||
}),
|
}),
|
||||||
Token::Eof
|
Token::Eof
|
||||||
]
|
]
|
||||||
@@ -354,16 +381,25 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fn_ident_args() {
|
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!(
|
assert_eq!(
|
||||||
p.parse(),
|
p.parse(),
|
||||||
vec![
|
vec![
|
||||||
Token::Tag(Val::Fn {
|
Token::Tag(Val::Fn {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
args: vec![
|
args: vec![
|
||||||
Val::Var("bar".into()),
|
FnArg {
|
||||||
Val::Var("baz".into()),
|
name: "a".into(),
|
||||||
Val::Var("qux".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
|
Token::Eof
|
||||||
@@ -373,16 +409,25 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fn_mixed_args() {
|
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!(
|
assert_eq!(
|
||||||
p.parse(),
|
p.parse(),
|
||||||
vec![
|
vec![
|
||||||
Token::Tag(Val::Fn {
|
Token::Tag(Val::Fn {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
args: vec![
|
args: vec![
|
||||||
Val::Var("bar".into()),
|
FnArg {
|
||||||
Val::Str(r#"baz "hi""#.into()),
|
name: "aaa".into(),
|
||||||
Val::Var("qux".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
|
Token::Eof
|
||||||
@@ -392,18 +437,54 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fn_nested() {
|
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!(
|
assert_eq!(
|
||||||
p.parse(),
|
p.parse(),
|
||||||
vec![
|
vec![
|
||||||
Token::Tag(Val::Fn {
|
Token::Tag(Val::Fn {
|
||||||
name: "outer".into(),
|
name: "outer".into(),
|
||||||
args: vec![
|
args: vec![
|
||||||
Val::Fn {
|
FnArg {
|
||||||
name: "inner".into(),
|
name: "a".into(),
|
||||||
args: vec![Val::Var("foo".into()), Val::Str("i".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
|
Token::Eof
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::{Parser, Token, Val};
|
use crate::{FnArg, Parser, Token, Val};
|
||||||
|
|
||||||
type TemplateCallback = fn(name: &str, args: Vec<String>) -> String;
|
type TemplateCallback = fn(name: &str, args: HashMap<String, String>) -> String;
|
||||||
|
|
||||||
pub fn parse_and_render(
|
pub fn parse_and_render(
|
||||||
template: &str,
|
template: &str,
|
||||||
@@ -32,11 +32,7 @@ pub fn render(
|
|||||||
return doc_str.join("");
|
return doc_str.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_tag(
|
fn render_tag(val: Val, vars: &HashMap<String, String>, cb: Option<TemplateCallback>) -> String {
|
||||||
val: Val,
|
|
||||||
vars: &HashMap<String, String>,
|
|
||||||
cb: Option<TemplateCallback>,
|
|
||||||
) -> String {
|
|
||||||
match val {
|
match val {
|
||||||
Val::Str(s) => s.into(),
|
Val::Str(s) => s.into(),
|
||||||
Val::Var(name) => match vars.get(name.as_str()) {
|
Val::Var(name) => match vars.get(name.as_str()) {
|
||||||
@@ -48,11 +44,22 @@ fn render_tag(
|
|||||||
let resolved_args = args
|
let resolved_args = args
|
||||||
.iter()
|
.iter()
|
||||||
.map(|a| match a {
|
.map(|a| match a {
|
||||||
Val::Str(s) => s.to_string(),
|
FnArg {
|
||||||
Val::Var(i) => vars.get(i.as_str()).unwrap_or(&empty).to_string(),
|
name,
|
||||||
val => render_tag(val.clone(), vars, cb),
|
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::<Vec<String>>();
|
.collect::<HashMap<String, String>>();
|
||||||
match cb {
|
match cb {
|
||||||
Some(cb) => cb(name.as_str(), resolved_args),
|
Some(cb) => cb(name.as_str(), resolved_args),
|
||||||
None => "".into(),
|
None => "".into(),
|
||||||
@@ -102,11 +109,11 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn render_valid_fn() {
|
fn render_valid_fn() {
|
||||||
let vars = HashMap::new();
|
let vars = HashMap::new();
|
||||||
let template = r#"${[ say_hello("John", "Kate") ]}"#;
|
let template = r#"${[ say_hello(a="John", b="Kate") ]}"#;
|
||||||
let result = r#"say_hello: ["John", "Kate"]"#;
|
let result = r#"say_hello: 2, Some("John") Some("Kate")"#;
|
||||||
|
|
||||||
fn cb(name: &str, args: Vec<String>) -> String {
|
fn cb(name: &str, args: HashMap<String, String>) -> String {
|
||||||
format!("{name}: {:?}", args)
|
format!("{name}: {}, {:?} {:?}", args.len(), args.get("a"), args.get("b"))
|
||||||
}
|
}
|
||||||
assert_eq!(parse_and_render(template, &vars, Some(cb)), result);
|
assert_eq!(parse_and_render(template, &vars, Some(cb)), result);
|
||||||
}
|
}
|
||||||
@@ -114,12 +121,12 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn render_nested_fn() {
|
fn render_nested_fn() {
|
||||||
let vars = HashMap::new();
|
let vars = HashMap::new();
|
||||||
let template = r#"${[ upper(secret()) ]}"#;
|
let template = r#"${[ upper(foo=secret()) ]}"#;
|
||||||
let result = r#"ABC"#;
|
let result = r#"ABC"#;
|
||||||
fn cb(name: &str, args: Vec<String>) -> String {
|
fn cb(name: &str, args: HashMap<String, String>) -> String {
|
||||||
match name {
|
match name {
|
||||||
"secret" => "abc".to_string(),
|
"secret" => "abc".to_string(),
|
||||||
"upper" => args[0].to_string().to_uppercase(),
|
"upper" => args["foo"].to_string().to_uppercase(),
|
||||||
_ => "".to_string(),
|
_ => "".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user