jpst/
lib.rs

1// Copyright (c) The Starcoin Core Contributors
2// SPDX-License-Identifier: Apache-2.0
3
4use 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;