mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-11 03:26:58 +02:00
Template Tag Function Editor (#67)

This commit is contained in:
@@ -1,23 +1,92 @@
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct Tokens {
|
||||
pub tokens: Vec<Token>,
|
||||
}
|
||||
|
||||
impl Display for Tokens {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let str = self
|
||||
.tokens
|
||||
.iter()
|
||||
.map(|t| t.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join("");
|
||||
write!(f, "{}", str)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
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<FnArg> },
|
||||
impl Display for FnArg {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let str = format!("{}={}", self.name, self.value);
|
||||
write!(f, "{}", str)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case", tag = "type")]
|
||||
#[ts(export)]
|
||||
pub enum Val {
|
||||
Str { text: String },
|
||||
Var { name: String },
|
||||
Fn { name: String, args: Vec<FnArg> },
|
||||
Null,
|
||||
}
|
||||
|
||||
impl Display for Val {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let str = match self {
|
||||
Val::Str { text } => format!(r#""{}""#, text.to_string().replace(r#"""#, r#"\""#)),
|
||||
Val::Var { name } => name.to_string(),
|
||||
Val::Fn { name, args } => {
|
||||
format!(
|
||||
"{name}({})",
|
||||
args.iter()
|
||||
.filter_map(|a| match a.value.clone() {
|
||||
Val::Null => None,
|
||||
_ => Some(a.to_string()),
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
Val::Null => "null".to_string(),
|
||||
};
|
||||
write!(f, "{}", str)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case", tag = "type")]
|
||||
#[ts(export)]
|
||||
pub enum Token {
|
||||
Raw(String),
|
||||
Tag(Val),
|
||||
Raw { text: String },
|
||||
Tag { val: Val },
|
||||
Eof,
|
||||
}
|
||||
|
||||
impl Display for Token {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let str = match self {
|
||||
Token::Raw { text } => text.to_string(),
|
||||
Token::Tag { val } => format!("${{[ {} ]}}", val.to_string()),
|
||||
Token::Eof => "".to_string(),
|
||||
};
|
||||
write!(f, "{}", str)
|
||||
}
|
||||
}
|
||||
|
||||
// Template Syntax
|
||||
//
|
||||
// ${[ my_var ]}
|
||||
@@ -42,7 +111,7 @@ impl Parser {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(&mut self) -> Vec<Token> {
|
||||
pub fn parse(&mut self) -> Tokens {
|
||||
let start_pos = self.pos;
|
||||
|
||||
while self.pos < self.chars.len() {
|
||||
@@ -65,7 +134,9 @@ impl Parser {
|
||||
}
|
||||
|
||||
self.push_token(Token::Eof);
|
||||
self.tokens.clone()
|
||||
Tokens {
|
||||
tokens: self.tokens.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_tag(&mut self) -> Option<Token> {
|
||||
@@ -85,7 +156,7 @@ impl Parser {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Token::Tag(val))
|
||||
Some(Token::Tag { val })
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -103,9 +174,13 @@ impl Parser {
|
||||
if let Some((name, args)) = self.parse_fn() {
|
||||
Some(Val::Fn { name, args })
|
||||
} else if let Some(v) = self.parse_ident() {
|
||||
Some(Val::Var(v))
|
||||
if v == "null" {
|
||||
Some(Val::Null)
|
||||
} else {
|
||||
Some(Val::Var { name: v })
|
||||
}
|
||||
} else if let Some(v) = self.parse_string() {
|
||||
Some(Val::Str(v))
|
||||
Some(Val::Str { text: v })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -145,7 +220,7 @@ impl Parser {
|
||||
// Fn closed immediately
|
||||
self.skip_whitespace();
|
||||
if self.match_str(")") {
|
||||
return Some(args)
|
||||
return Some(args);
|
||||
}
|
||||
|
||||
while self.pos < self.chars.len() {
|
||||
@@ -183,7 +258,7 @@ impl Parser {
|
||||
}
|
||||
}
|
||||
|
||||
return Some(args);
|
||||
Some(args)
|
||||
}
|
||||
|
||||
fn parse_ident(&mut self) -> Option<String> {
|
||||
@@ -209,7 +284,7 @@ impl Parser {
|
||||
return None;
|
||||
}
|
||||
|
||||
return Some(text);
|
||||
Some(text)
|
||||
}
|
||||
|
||||
fn parse_string(&mut self) -> Option<String> {
|
||||
@@ -246,7 +321,7 @@ impl Parser {
|
||||
return None;
|
||||
}
|
||||
|
||||
return Some(text);
|
||||
Some(text)
|
||||
}
|
||||
|
||||
fn skip_whitespace(&mut self) {
|
||||
@@ -274,7 +349,9 @@ impl Parser {
|
||||
fn push_token(&mut self, token: Token) {
|
||||
// Push any text we've accumulated
|
||||
if !self.curr_text.is_empty() {
|
||||
let text_token = Token::Raw(self.curr_text.clone());
|
||||
let text_token = Token::Raw {
|
||||
text: self.curr_text.clone(),
|
||||
};
|
||||
self.tokens.push(text_token);
|
||||
self.curr_text.clear();
|
||||
}
|
||||
@@ -303,14 +380,20 @@ impl Parser {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::Val::Null;
|
||||
use crate::*;
|
||||
|
||||
#[test]
|
||||
fn var_simple() {
|
||||
let mut p = Parser::new("${[ foo ]}");
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
vec![Token::Tag(Val::Var("foo".into())), Token::Eof]
|
||||
p.parse().tokens,
|
||||
vec![
|
||||
Token::Tag {
|
||||
val: Val::Var { name: "foo".into() }
|
||||
},
|
||||
Token::Eof
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -318,8 +401,13 @@ mod tests {
|
||||
fn var_multiple_names_invalid() {
|
||||
let mut p = Parser::new("${[ foo bar ]}");
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
vec![Token::Raw("${[ foo bar ]}".into()), Token::Eof]
|
||||
p.parse().tokens,
|
||||
vec![
|
||||
Token::Raw {
|
||||
text: "${[ foo bar ]}".into()
|
||||
},
|
||||
Token::Eof
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -327,8 +415,15 @@ mod tests {
|
||||
fn tag_string() {
|
||||
let mut p = Parser::new(r#"${[ "foo \"bar\" baz" ]}"#);
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
vec![Token::Tag(Val::Str(r#"foo "bar" baz"#.into())), Token::Eof]
|
||||
p.parse().tokens,
|
||||
vec![
|
||||
Token::Tag {
|
||||
val: Val::Str {
|
||||
text: r#"foo "bar" baz"#.into()
|
||||
}
|
||||
},
|
||||
Token::Eof
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -336,11 +431,17 @@ mod tests {
|
||||
fn var_surrounded() {
|
||||
let mut p = Parser::new("Hello ${[ foo ]}!");
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
p.parse().tokens,
|
||||
vec![
|
||||
Token::Raw("Hello ".to_string()),
|
||||
Token::Tag(Val::Var("foo".into())),
|
||||
Token::Raw("!".to_string()),
|
||||
Token::Raw {
|
||||
text: "Hello ".to_string()
|
||||
},
|
||||
Token::Tag {
|
||||
val: Val::Var { name: "foo".into() }
|
||||
},
|
||||
Token::Raw {
|
||||
text: "!".to_string()
|
||||
},
|
||||
Token::Eof,
|
||||
]
|
||||
);
|
||||
@@ -350,12 +451,14 @@ mod tests {
|
||||
fn fn_simple() {
|
||||
let mut p = Parser::new("${[ foo() ]}");
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
p.parse().tokens,
|
||||
vec![
|
||||
Token::Tag(Val::Fn {
|
||||
name: "foo".into(),
|
||||
args: Vec::new(),
|
||||
}),
|
||||
Token::Tag {
|
||||
val: Val::Fn {
|
||||
name: "foo".into(),
|
||||
args: Vec::new(),
|
||||
}
|
||||
},
|
||||
Token::Eof
|
||||
]
|
||||
);
|
||||
@@ -365,15 +468,17 @@ mod tests {
|
||||
fn fn_ident_arg() {
|
||||
let mut p = Parser::new("${[ foo(a=bar) ]}");
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
p.parse().tokens,
|
||||
vec![
|
||||
Token::Tag(Val::Fn {
|
||||
name: "foo".into(),
|
||||
args: vec![FnArg {
|
||||
name: "a".into(),
|
||||
value: Val::Var("bar".into())
|
||||
}],
|
||||
}),
|
||||
Token::Tag {
|
||||
val: Val::Fn {
|
||||
name: "foo".into(),
|
||||
args: vec![FnArg {
|
||||
name: "a".into(),
|
||||
value: Val::Var { name: "bar".into() }
|
||||
}],
|
||||
}
|
||||
},
|
||||
Token::Eof
|
||||
]
|
||||
);
|
||||
@@ -383,25 +488,27 @@ mod tests {
|
||||
fn fn_ident_args() {
|
||||
let mut p = Parser::new("${[ foo(a=bar,b = baz, c =qux ) ]}");
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
p.parse().tokens,
|
||||
vec![
|
||||
Token::Tag(Val::Fn {
|
||||
name: "foo".into(),
|
||||
args: vec![
|
||||
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::Tag {
|
||||
val: Val::Fn {
|
||||
name: "foo".into(),
|
||||
args: vec![
|
||||
FnArg {
|
||||
name: "a".into(),
|
||||
value: Val::Var { name: "bar".into() }
|
||||
},
|
||||
FnArg {
|
||||
name: "b".into(),
|
||||
value: Val::Var { name: "baz".into() }
|
||||
},
|
||||
FnArg {
|
||||
name: "c".into(),
|
||||
value: Val::Var { name: "qux".into() }
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
Token::Eof
|
||||
]
|
||||
);
|
||||
@@ -411,25 +518,29 @@ mod tests {
|
||||
fn fn_mixed_args() {
|
||||
let mut p = Parser::new(r#"${[ foo(aaa=bar,bb="baz \"hi\"", c=qux ) ]}"#);
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
p.parse().tokens,
|
||||
vec![
|
||||
Token::Tag(Val::Fn {
|
||||
name: "foo".into(),
|
||||
args: vec![
|
||||
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::Tag {
|
||||
val: Val::Fn {
|
||||
name: "foo".into(),
|
||||
args: vec![
|
||||
FnArg {
|
||||
name: "aaa".into(),
|
||||
value: Val::Var { name: "bar".into() }
|
||||
},
|
||||
FnArg {
|
||||
name: "bb".into(),
|
||||
value: Val::Str {
|
||||
text: r#"baz "hi""#.into()
|
||||
}
|
||||
},
|
||||
FnArg {
|
||||
name: "c".into(),
|
||||
value: Val::Var { name: "qux".into() }
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
Token::Eof
|
||||
]
|
||||
);
|
||||
@@ -439,18 +550,20 @@ mod tests {
|
||||
fn fn_nested() {
|
||||
let mut p = Parser::new("${[ foo(b=bar()) ]}");
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
p.parse().tokens,
|
||||
vec![
|
||||
Token::Tag(Val::Fn {
|
||||
name: "foo".into(),
|
||||
args: vec![FnArg {
|
||||
name: "b".into(),
|
||||
value: Val::Fn {
|
||||
name: "bar".into(),
|
||||
args: vec![],
|
||||
}
|
||||
}],
|
||||
}),
|
||||
Token::Tag {
|
||||
val: Val::Fn {
|
||||
name: "foo".into(),
|
||||
args: vec![FnArg {
|
||||
name: "b".into(),
|
||||
value: Val::Fn {
|
||||
name: "bar".into(),
|
||||
args: vec![],
|
||||
}
|
||||
}],
|
||||
}
|
||||
},
|
||||
Token::Eof
|
||||
]
|
||||
);
|
||||
@@ -460,35 +573,134 @@ mod tests {
|
||||
fn fn_nested_args() {
|
||||
let mut p = Parser::new(r#"${[ outer(a=inner(a=foo, b="i"), c="o") ]}"#);
|
||||
assert_eq!(
|
||||
p.parse(),
|
||||
p.parse().tokens,
|
||||
vec![
|
||||
Token::Tag(Val::Fn {
|
||||
name: "outer".into(),
|
||||
args: vec![
|
||||
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())
|
||||
},
|
||||
],
|
||||
}),
|
||||
Token::Tag {
|
||||
val: Val::Fn {
|
||||
name: "outer".into(),
|
||||
args: vec![
|
||||
FnArg {
|
||||
name: "a".into(),
|
||||
value: Val::Fn {
|
||||
name: "inner".into(),
|
||||
args: vec![
|
||||
FnArg {
|
||||
name: "a".into(),
|
||||
value: Val::Var { name: "foo".into() }
|
||||
},
|
||||
FnArg {
|
||||
name: "b".into(),
|
||||
value: Val::Str { text: "i".into() },
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
FnArg {
|
||||
name: "c".into(),
|
||||
value: Val::Str { text: "o".into() }
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
Token::Eof
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_display_var() {
|
||||
assert_eq!(
|
||||
Val::Var {
|
||||
name: "foo".to_string()
|
||||
}
|
||||
.to_string(),
|
||||
"foo"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_display_str() {
|
||||
assert_eq!(
|
||||
Val::Str {
|
||||
text: r#"Hello "You""#.to_string()
|
||||
}
|
||||
.to_string(),
|
||||
r#""Hello \"You\"""#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_null_fn_arg() {
|
||||
assert_eq!(
|
||||
Val::Fn {
|
||||
name: "fn".to_string(),
|
||||
args: vec![
|
||||
FnArg {
|
||||
name: "n".to_string(),
|
||||
value: Null,
|
||||
},
|
||||
FnArg {
|
||||
name: "a".to_string(),
|
||||
value: Val::Str {
|
||||
text: "aaa".to_string()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
.to_string(),
|
||||
r#"fn(a="aaa")"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_display_fn() {
|
||||
assert_eq!(
|
||||
Token::Tag {
|
||||
val: Val::Fn {
|
||||
name: "foo".to_string(),
|
||||
args: vec![
|
||||
FnArg {
|
||||
name: "arg".to_string(),
|
||||
value: Val::Str {
|
||||
text: "v".to_string()
|
||||
}
|
||||
},
|
||||
FnArg {
|
||||
name: "arg2".to_string(),
|
||||
value: Val::Var {
|
||||
name: "my_var".to_string()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
.to_string(),
|
||||
r#"${[ foo(arg="v", arg2=my_var) ]}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tokens_display() {
|
||||
assert_eq!(
|
||||
Tokens {
|
||||
tokens: vec![
|
||||
Token::Tag {
|
||||
val: Val::Var {
|
||||
name: "my_var".to_string()
|
||||
}
|
||||
},
|
||||
Token::Raw {
|
||||
text: " Some cool text ".to_string(),
|
||||
},
|
||||
Token::Tag {
|
||||
val: Val::Str {
|
||||
text: "Hello World".to_string()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
.to_string(),
|
||||
r#"${[ my_var ]} Some cool text ${[ "Hello World" ]}"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user