1use jsonpath_plus::JsonPath;
5use once_cell::sync::Lazy;
6use regex::Regex;
7use serde::Serialize;
8use serde_json::{json, map::Entry, Map, Value};
9
10#[derive(Debug, Clone, PartialEq)]
11struct Match {
12 name: String,
13 start: usize,
14 end: usize,
15}
16
17#[derive(Debug, Clone, PartialEq)]
18pub struct Template {
19 src: String,
20 matches: Vec<Match>,
21}
22
23impl Template {
24 pub(crate) fn new(template: &str, matchs: Vec<Match>) -> Self {
25 Template {
26 src: template.to_string(),
27 matches: matchs,
28 }
29 }
30}
31
32pub trait TemplateEngine {
33 fn parse(tpl: &str) -> Template;
34 fn render(ctx: &TemplateContext, tpl: &Template) -> String {
35 let mut src = tpl.src.clone();
36 let root = ctx.as_value();
37 for m in &tpl.matches {
38 let path = match JsonPath::compile(m.name.as_str()) {
39 Ok(path) => path,
40 Err(e) => {
41 eprintln!("Parse Error: {:?}", e);
42 continue;
43 }
44 };
45
46 let value = path.find(&root).pop().cloned().unwrap_or_else(|| json!(""));
47 let value = value_to_str(&value);
48 src = src.replace(&tpl.src[m.start..m.end], value.as_str());
49 }
50 src
51 }
52}
53
54fn value_to_str(v: &Value) -> String {
55 match v {
56 Value::String(s) => s.clone(),
57 _ => v.to_string(),
58 }
59}
60
61pub struct ContextEntry<'a> {
62 entry: Entry<'a>,
63}
64
65impl<'a> ContextEntry<'a> {
66 pub fn set<V>(self, value: V)
67 where
68 V: Serialize,
69 {
70 let json_value = serde_json::to_value(value).expect("must be json value");
71 match self.entry {
72 Entry::Occupied(mut o) => {
73 o.insert(json_value);
74 }
75 Entry::Vacant(v) => {
76 v.insert(json_value);
77 }
78 }
79 }
80
81 pub fn append<V>(self, value: V)
82 where
83 V: Into<Value>,
84 {
85 match self.entry {
86 Entry::Occupied(mut o) => {
87 if o.get().is_array() {
88 o.get_mut()
89 .as_array_mut()
90 .expect("must be json array")
91 .push(value.into());
92 } else {
93 o.insert(json!([o.get().clone(), value.into()]));
94 }
95 }
96 Entry::Vacant(v) => {
97 v.insert(json!([value.into()]));
98 }
99 }
100 }
101}
102
103#[derive(Debug, Clone, PartialEq)]
104pub struct TemplateContext {
105 root: Map<String, Value>,
106}
107
108impl Default for TemplateContext {
109 fn default() -> Self {
110 Self::new()
111 }
112}
113
114impl TemplateContext {
115 pub fn new() -> Self {
116 TemplateContext { root: Map::new() }
117 }
118
119 pub fn new_with_root<V>(root_value: V) -> Self
120 where
121 V: Serialize,
122 {
123 let value: Value = serde_json::to_value(root_value).expect("must be json value");
124 let root = match value {
125 Value::Object(m) => m,
126 _ => {
127 let mut m = Map::new();
128 m.insert("value".to_string(), value);
129 m
130 }
131 };
132
133 TemplateContext { root }
134 }
135
136 pub fn entry<S>(&mut self, key: S) -> ContextEntry
137 where
138 S: Into<String>,
139 {
140 ContextEntry {
141 entry: self.root.entry(key),
142 }
143 }
144
145 pub fn as_value(&self) -> Value {
146 Value::Object(self.root.clone())
147 }
148}
149
150impl<T> From<T> for TemplateContext
151where
152 T: Serialize,
153{
154 fn from(t: T) -> Self {
155 Self::new_with_root(t)
156 }
157}
158
159impl From<&TemplateContext> for TemplateContext {
160 fn from(c: &TemplateContext) -> Self {
161 c.clone()
162 }
163}
164
165static G_EXPR_REGEX: Lazy<Regex> =
166 Lazy::new(|| Regex::new(r"\{\{(.*?)\}\}").expect("build expr regex should success."));
167
168pub struct RegexTemplateEngine {}
169
170impl TemplateEngine for RegexTemplateEngine {
171 fn parse(tpl: &str) -> Template {
172 Template::new(
173 tpl,
174 G_EXPR_REGEX
175 .find_iter(tpl)
176 .map(|m| Match {
177 name: m
178 .as_str()
179 .trim_start_matches('{')
180 .trim_end_matches('}')
181 .trim()
182 .to_string(),
183 start: m.start(),
184 end: m.end(),
185 })
186 .collect(),
187 )
188 }
189}
190
191#[macro_export]
192macro_rules! format_str {
193 ($fmt:expr, $val:expr) => {{
194 use $crate::TemplateEngine;
195 let ctx = $val.into();
196 let tpl = $crate::RegexTemplateEngine::parse($fmt);
197 let s = $crate::RegexTemplateEngine::render(&ctx, &tpl);
198 s
199 }};
200}
201
202#[cfg(test)]
203mod tests;