gchemol_readwrite/template/
jinja.rs1use gut::prelude::*;
3use std::path::{Path, PathBuf};
4
5use minijinja::Environment;
6use lazy_regex::*;
10
11static GLOBAL_REX: Lazy<Regex> = lazy_regex!("^ab+$"i);
12
13static FORMAT_SPEC: Lazy<Regex> = lazy_regex!(
14 r"(?x)
15(?P<align>[<>^])?
16(?P<sign>[\+\-])?
17(?P<width>\d+)?
18((?:\.)(?P<precision>\d+))?
19"
20);
21
22static FLOAT_NUM: Lazy<Regex> = lazy_regex!(r"^[\-\+]?[0-9]*\.[0-9]+$");
23static NUMBER: Lazy<Regex> = lazy_regex!(r"^[\-\+]?[0-9]+$");
24#[derive(Debug, Clone, Default)]
29struct Format {
30 align: Option<char>,
31 sign: Option<char>,
32 width: Option<usize>,
33 precision: Option<usize>,
34}
35
36impl Format {
37 fn parse_from(param: &str) -> Self {
38 let mut f = Format::default();
39
40 if let Some(captures) = FORMAT_SPEC.captures(param) {
41 f.align = captures.name("align").and_then(|x| x.as_str().chars().next());
42 f.sign = captures.name("sign").and_then(|x| x.as_str().chars().next());
43 f.width = captures.name("width").and_then(|x| x.as_str().parse().ok());
44 f.precision = captures.name("precision").and_then(|x| x.as_str().parse().ok());
45 }
46
47 f
48 }
49}
50
51#[test]
52fn test_parse_regex() {
53 let captures = FORMAT_SPEC.captures("-12.5").unwrap();
54 assert!(captures.name("align").is_none());
55 assert!(captures.name("sign").is_some());
56 assert!(captures.name("width").is_some());
57 assert!(captures.name("precision").is_some());
58
59 let captures = FORMAT_SPEC.captures("^12").unwrap();
60 assert!(captures.name("align").is_some());
61 assert!(captures.name("width").is_some());
62
63 let captures = FORMAT_SPEC.captures("-12").unwrap();
64 assert!(captures.name("align").is_none());
65 assert!(captures.name("sign").is_some());
66 assert!(captures.name("width").is_some());
67
68 let format = Format::parse_from("-12.5");
69 assert_eq!(format.sign, Some('-'));
70 assert_eq!(format.width, Some(12));
71 assert_eq!(format.precision, Some(5));
72
73 let format = Format::parse_from("-12");
74 assert_eq!(format.align, None);
75 assert_eq!(format.sign, Some('-'));
76 assert_eq!(format.width, Some(12));
77 assert_eq!(format.precision, None);
78
79 let format = Format::parse_from("^12");
80 assert_eq!(format.sign, None);
81 assert_eq!(format.align, Some('^'));
82 assert_eq!(format.width, Some(12));
83 assert_eq!(format.precision, None);
84
85 assert!(FLOAT_NUM.is_match("12.55"));
86 assert!(FLOAT_NUM.is_match("+.55"));
87 assert!(FLOAT_NUM.is_match("-1.55"));
88 assert!(!FLOAT_NUM.is_match("-1.55a"));
89 assert!(NUMBER.is_match("-1"));
90}
91pub fn format_user_value(value: String, code: String) -> String {
96 let f = Format::parse_from(&code);
97
98 let width = f.width.unwrap_or(0);
99 let precision = f.precision.unwrap_or(width);
100
101 macro_rules! format_value {
102 ($v:ident) => {{
103 match f.align {
104 Some('<') => format!("{value:<width$.precision$}", value = $v, width = width, precision = precision),
105 Some('>') => format!("{value:>width$.precision$}", value = $v, width = width, precision = precision),
106 Some('^') => format!("{value:^width$.precision$}", value = $v, width = width, precision = precision),
107 None => format!("{value:width$.precision$}", value = $v, width = width, precision = precision),
108 _ => todo!(),
109 }
110 }};
111 }
112 if FLOAT_NUM.is_match(&value) {
113 let value: f64 = value.parse().unwrap();
114 format_value!(value)
115 } else if NUMBER.is_match(&value) {
116 let value: usize = value.parse().unwrap();
117 format_value!(value)
118 } else {
119 format_value!(value)
120 }
121}
122pub fn render(json_source: &str, template: &str) -> Result<String> {
126 let mut env = Environment::new();
127 let ctx: serde_json::Value = serde_json::from_str(json_source)?;
129 let mut s = env.render_str(template, &ctx)?;
130 s.push_str("\n");
131 Ok(s)
132}
133
134pub struct Template<'a> {
136 env: Environment<'a>,
137 src: String,
138}
139
140impl<'a> Template<'a> {
141 pub fn render_json(&self, json: &str) -> Result<String> {
143 let ctx: serde_json::Value = serde_json::from_str(json)?;
144 let s = self.render(ctx)?;
145 Ok(s)
146 }
147
148 pub fn render<S: Serialize>(&self, ctx: S) -> Result<String> {
150 let mut s = self
151 .env
152 .render_str(&self.src, &ctx)
153 .map_err(|e| anyhow!("Render molecule failure in tera: {:?}", e))?;
154 s.push_str("\n");
155 Ok(s)
156 }
157
158 pub fn try_from_path(path: &Path) -> Result<Self> {
160 let src = gut::fs::read_file(path)?;
161 let t = Self::from_str(&src);
162 Ok(t)
163 }
164
165 pub fn from_str(src: &str) -> Self {
167 let mut env = Environment::new();
168 env.add_filter("format", format_user_value);
169 Self { env, src: src.to_owned() }
170 }
171}
172use super::Molecule;
176
177pub(super) fn render_molecule_with(mol: &Molecule, template: &str) -> Result<String> {
179 let s = Template::from_str(template).render(super::renderable(mol))?;
180 Ok(s)
181}
182