mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-25 10:51:26 +01:00
Template Tag Function Editor (#67)

This commit is contained in:
19
src-tauri/Cargo.lock
generated
19
src-tauri/Cargo.lock
generated
@@ -4919,9 +4919,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.205"
|
||||
version = "1.0.208"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150"
|
||||
checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -4949,9 +4949,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.205"
|
||||
version = "1.0.208"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1"
|
||||
checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4971,9 +4971,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.122"
|
||||
version = "1.0.125"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da"
|
||||
checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed"
|
||||
dependencies = [
|
||||
"itoa 1.0.11",
|
||||
"memchr",
|
||||
@@ -5844,9 +5844,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-clipboard-manager"
|
||||
version = "2.0.0-rc.0"
|
||||
version = "2.1.0-beta.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76a26868f7e05a09673e4172d23acb82cd48911cca092f0e8d06179a69e5024c"
|
||||
checksum = "becbc5a692e842f8d6a7ab5e490c3c36d267b5c3d5bf4b6a0cdd039d7df25569"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"image 0.24.9",
|
||||
@@ -7649,6 +7649,9 @@ name = "yaak_templates"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -45,7 +45,7 @@ serde_json = { version = "1.0.116", features = ["raw_value"] }
|
||||
serde_yaml = "0.9.34"
|
||||
tauri = { workspace = true, features = ["unstable"] }
|
||||
tauri-plugin-shell = { workspace = true }
|
||||
tauri-plugin-clipboard-manager = "2.0.0-rc.0"
|
||||
tauri-plugin-clipboard-manager = "2.1.0-beta.7"
|
||||
tauri-plugin-dialog = "2.0.0-rc.0"
|
||||
tauri-plugin-fs = "2.0.0-rc.0"
|
||||
tauri-plugin-log = { version = "2.0.0-rc.0", features = ["colored"] }
|
||||
|
||||
@@ -35,7 +35,7 @@ use crate::export_resources::{get_workspace_export_resources, WorkspaceExportRes
|
||||
use crate::grpc::metadata_to_map;
|
||||
use crate::http_request::send_http_request;
|
||||
use crate::notifications::YaakNotifier;
|
||||
use crate::render::{render_request, variables_from_environment};
|
||||
use crate::render::{render_request, render_template, variables_from_environment};
|
||||
use crate::updates::{UpdateMode, YaakUpdater};
|
||||
use crate::window_menu::app_menu;
|
||||
use yaak_models::models::{
|
||||
@@ -60,6 +60,7 @@ use yaak_plugin_runtime::events::{
|
||||
GetHttpRequestByIdResponse, InternalEvent, InternalEventPayload, RenderHttpRequestResponse,
|
||||
SendHttpRequestResponse,
|
||||
};
|
||||
use yaak_templates::{parse_and_render, Parser, Tokens};
|
||||
|
||||
mod analytics;
|
||||
mod export_resources;
|
||||
@@ -99,6 +100,38 @@ async fn cmd_metadata(app_handle: AppHandle) -> Result<AppMetaData, ()> {
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_parse_template(template: &str) -> Result<Tokens, String> {
|
||||
Ok(Parser::new(template).parse())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_template_tokens_to_string(tokens: Tokens) -> Result<String, String> {
|
||||
Ok(tokens.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_render_template(
|
||||
window: WebviewWindow,
|
||||
template: &str,
|
||||
workspace_id: &str,
|
||||
environment_id: Option<&str>,
|
||||
) -> Result<String, String> {
|
||||
let environment = match environment_id {
|
||||
Some(id) => Some(
|
||||
get_environment(&window, id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
let workspace = get_workspace(&window, &workspace_id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let rendered = render_template(template, &workspace, environment.as_ref());
|
||||
Ok(rendered)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_dismiss_notification(
|
||||
window: WebviewWindow,
|
||||
@@ -1641,6 +1674,9 @@ pub fn run() {
|
||||
cmd_delete_http_response,
|
||||
cmd_delete_workspace,
|
||||
cmd_dismiss_notification,
|
||||
cmd_parse_template,
|
||||
cmd_template_tokens_to_string,
|
||||
cmd_render_template,
|
||||
cmd_duplicate_grpc_request,
|
||||
cmd_duplicate_http_request,
|
||||
cmd_export_data,
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
use std::collections::HashMap;
|
||||
use serde_json::Value;
|
||||
use crate::template_fns::timestamp;
|
||||
use yaak_templates::parse_and_render;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use yaak_models::models::{
|
||||
Environment, EnvironmentVariable, HttpRequest, HttpRequestHeader, HttpUrlParameter, Workspace,
|
||||
};
|
||||
use yaak_templates::parse_and_render;
|
||||
|
||||
pub fn render_template(template: &str, w: &Workspace, e: Option<&Environment>) -> String {
|
||||
let vars = &variables_from_environment(w, e);
|
||||
render(template, vars)
|
||||
}
|
||||
|
||||
pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -> HttpRequest {
|
||||
let r = r.clone();
|
||||
|
||||
@@ -5,3 +5,6 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0.208", features = ["derive"] }
|
||||
serde_json = "1.0.125"
|
||||
ts-rs = { version = "9.0.1" }
|
||||
|
||||
6
src-tauri/yaak_templates/build.rs
Normal file
6
src-tauri/yaak_templates/build.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Tell ts-rs where to generate types to
|
||||
println!("cargo:rustc-env=TS_RS_EXPORT_DIR=../../src-web/gen");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -2,6 +2,4 @@ pub mod parser;
|
||||
pub mod renderer;
|
||||
|
||||
pub use parser::*;
|
||||
pub use renderer::*;
|
||||
|
||||
pub fn template_foo() {}
|
||||
pub use renderer::*;
|
||||
@@ -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" ]}"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{FnArg, Parser, Token, Val};
|
||||
use crate::{FnArg, Parser, Token, Tokens, Val};
|
||||
use log::warn;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -15,27 +15,27 @@ pub fn parse_and_render(
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
tokens: Vec<Token>,
|
||||
tokens: Tokens,
|
||||
vars: &HashMap<String, String>,
|
||||
cb: Option<TemplateCallback>,
|
||||
) -> String {
|
||||
let mut doc_str: Vec<String> = Vec::new();
|
||||
|
||||
for t in tokens {
|
||||
for t in tokens.tokens {
|
||||
match t {
|
||||
Token::Raw(s) => doc_str.push(s),
|
||||
Token::Tag(val) => doc_str.push(render_tag(val, &vars, cb)),
|
||||
Token::Raw { text } => doc_str.push(text),
|
||||
Token::Tag { val } => doc_str.push(render_tag(val, &vars, cb)),
|
||||
Token::Eof => {}
|
||||
}
|
||||
}
|
||||
|
||||
return doc_str.join("");
|
||||
doc_str.join("")
|
||||
}
|
||||
|
||||
fn render_tag(val: Val, vars: &HashMap<String, String>, cb: Option<TemplateCallback>) -> String {
|
||||
match val {
|
||||
Val::Str(s) => s.into(),
|
||||
Val::Var(name) => match vars.get(name.as_str()) {
|
||||
Val::Str { text } => text.into(),
|
||||
Val::Var { name } => match vars.get(name.as_str()) {
|
||||
Some(v) => v.to_string(),
|
||||
None => "".into(),
|
||||
},
|
||||
@@ -46,14 +46,14 @@ fn render_tag(val: Val, vars: &HashMap<String, String>, cb: Option<TemplateCallb
|
||||
.map(|a| match a {
|
||||
FnArg {
|
||||
name,
|
||||
value: Val::Str(s),
|
||||
} => (name.to_string(), s.to_string()),
|
||||
value: Val::Str { text },
|
||||
} => (name.to_string(), text.to_string()),
|
||||
FnArg {
|
||||
name,
|
||||
value: Val::Var(i),
|
||||
value: Val::Var { name: var_name },
|
||||
} => (
|
||||
name.to_string(),
|
||||
vars.get(i.as_str()).unwrap_or(&empty).to_string(),
|
||||
vars.get(var_name.as_str()).unwrap_or(&empty).to_string(),
|
||||
),
|
||||
FnArg { name, value: val } => {
|
||||
(name.to_string(), render_tag(val.clone(), vars, cb))
|
||||
@@ -64,13 +64,17 @@ fn render_tag(val: Val, vars: &HashMap<String, String>, cb: Option<TemplateCallb
|
||||
Some(cb) => match cb(name.as_str(), resolved_args.clone()) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
warn!("Failed to run template callback {}({:?}): {}", name, resolved_args, e);
|
||||
warn!(
|
||||
"Failed to run template callback {}({:?}): {}",
|
||||
name, resolved_args, e
|
||||
);
|
||||
"".to_string()
|
||||
}
|
||||
},
|
||||
None => "".into(),
|
||||
}
|
||||
}
|
||||
Val::Null => "".into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +151,7 @@ mod tests {
|
||||
result.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn render_fn_err() {
|
||||
let vars = HashMap::new();
|
||||
|
||||
Reference in New Issue
Block a user