|
1 | 1 | use crate::python::cross::{CLRepr, CLReprObject}; |
2 | 2 | use crate::python::template::mj_value::to_minijinja_value; |
3 | | -use log::{error, trace}; |
| 3 | +use crate::utils::bind_method; |
| 4 | +use log::trace; |
4 | 5 | use minijinja as mj; |
5 | 6 | use neon::context::Context; |
6 | 7 | use neon::prelude::*; |
7 | | -use once_cell::sync::OnceCell; |
| 8 | +use std::cell::RefCell; |
8 | 9 | use std::error::Error; |
9 | | -use std::sync::Mutex; |
10 | 10 |
|
11 | 11 | trait NeonMiniJinjaContext { |
12 | 12 | fn throw_from_mj_error<T>(&mut self, err: mj::Error) -> NeonResult<T>; |
@@ -60,130 +60,128 @@ impl<'a> NeonMiniJinjaContext for FunctionContext<'a> { |
60 | 60 | } |
61 | 61 | } |
62 | 62 |
|
63 | | -#[derive(Debug)] |
64 | | -struct EngineOptions { |
65 | | - debug_info: bool, |
| 63 | +struct JinjaEngine { |
| 64 | + inner: mj::Environment<'static>, |
66 | 65 | } |
67 | 66 |
|
68 | | -static TEMPLATE_ENGINE: OnceCell<Mutex<mj::Environment>> = OnceCell::new(); |
69 | | - |
70 | | -fn init_template_engine<'a, C: Context<'a>>(_cx: &mut C, opts: EngineOptions) -> NeonResult<()> { |
71 | | - let mut engine = mj::Environment::new(); |
72 | | - engine.set_debug(opts.debug_info); |
73 | | - engine.add_function( |
74 | | - "env_var", |
75 | | - |var_name: String, var_default: Option<String>, _state: &minijinja::State| { |
76 | | - if let Ok(value) = std::env::var(&var_name) { |
77 | | - return Ok(mj::value::Value::from(value)); |
78 | | - } |
79 | | - |
80 | | - if let Some(var_default) = var_default { |
81 | | - return Ok(mj::value::Value::from(var_default)); |
82 | | - } |
83 | | - |
84 | | - let err = minijinja::Error::new( |
85 | | - mj::ErrorKind::InvalidOperation, |
86 | | - format!("unknown env variable {}", var_name), |
87 | | - ); |
| 67 | +impl Finalize for JinjaEngine {} |
| 68 | + |
| 69 | +impl JinjaEngine { |
| 70 | + fn new(cx: &mut FunctionContext) -> NeonResult<Self> { |
| 71 | + let options = cx.argument::<JsObject>(0)?; |
| 72 | + |
| 73 | + let debug_info = options |
| 74 | + .get_value(cx, "debugInfo")? |
| 75 | + .downcast_or_throw::<JsBoolean, _>(cx)? |
| 76 | + .value(cx); |
| 77 | + |
| 78 | + let mut engine = mj::Environment::new(); |
| 79 | + engine.set_debug(debug_info); |
| 80 | + engine.add_function( |
| 81 | + "env_var", |
| 82 | + |var_name: String, var_default: Option<String>, _state: &minijinja::State| { |
| 83 | + if let Ok(value) = std::env::var(&var_name) { |
| 84 | + return Ok(mj::value::Value::from(value)); |
| 85 | + } |
| 86 | + |
| 87 | + if let Some(var_default) = var_default { |
| 88 | + return Ok(mj::value::Value::from(var_default)); |
| 89 | + } |
| 90 | + |
| 91 | + let err = minijinja::Error::new( |
| 92 | + mj::ErrorKind::InvalidOperation, |
| 93 | + format!("unknown env variable {}", var_name), |
| 94 | + ); |
88 | 95 |
|
89 | | - Err(err) |
90 | | - }, |
91 | | - ); |
| 96 | + Err(err) |
| 97 | + }, |
| 98 | + ); |
92 | 99 |
|
93 | | - if let Err(_) = TEMPLATE_ENGINE.set(Mutex::new(engine)) { |
94 | | - // I am working on a new jinja engine implementation on top of isolated instances per tenant |
95 | | - // to support multi tenancy |
96 | | - #[cfg(debug_assertions)] |
97 | | - error!("Unable to init jinja engine, it was already started"); |
| 100 | + Ok(Self { inner: engine }) |
98 | 101 | } |
99 | | - |
100 | | - Ok(()) |
101 | 102 | } |
102 | 103 |
|
103 | | -fn template_engine<'a, C: Context<'a>>( |
104 | | - cx: &mut C, |
105 | | -) -> NeonResult<&'static Mutex<mj::Environment<'static>>> { |
106 | | - if let Some(engine) = TEMPLATE_ENGINE.get() { |
107 | | - Ok(engine) |
108 | | - } else { |
109 | | - cx.throw_error("Unable to get jinja engine: It was not initialized".to_string()) |
110 | | - } |
111 | | -} |
| 104 | +type BoxedJinjaEngine = JsBox<RefCell<JinjaEngine>>; |
112 | 105 |
|
113 | | -fn init_jinja_engine(mut cx: FunctionContext) -> JsResult<JsUndefined> { |
114 | | - let options = cx.argument::<JsObject>(0)?; |
| 106 | +impl JinjaEngine { |
| 107 | + fn render_template(mut cx: FunctionContext) -> JsResult<JsString> { |
| 108 | + #[cfg(build = "debug")] |
| 109 | + trace!("JinjaEngine.render_template"); |
115 | 110 |
|
116 | | - let debug_info: Handle<JsBoolean> = options |
117 | | - .get_value(&mut cx, "debugInfo")? |
118 | | - .downcast_or_throw(&mut cx)?; |
| 111 | + let this = cx |
| 112 | + .this() |
| 113 | + .downcast_or_throw::<BoxedJinjaEngine, _>(&mut cx)?; |
119 | 114 |
|
120 | | - let options = EngineOptions { |
121 | | - debug_info: debug_info.value(&mut cx), |
122 | | - }; |
123 | | - init_template_engine(&mut cx, options)?; |
| 115 | + let template_name = cx.argument::<JsString>(0)?; |
| 116 | + let template_ctx = CLRepr::from_js_ref(cx.argument::<JsValue>(1)?, &mut cx)?; |
124 | 117 |
|
125 | | - Ok(cx.undefined()) |
126 | | -} |
| 118 | + let engine = &this.borrow().inner; |
| 119 | + let template = match engine.get_template(&template_name.value(&mut cx)) { |
| 120 | + Ok(t) => t, |
| 121 | + Err(err) => { |
| 122 | + trace!("jinja get template error: {:?}", err); |
127 | 123 |
|
128 | | -fn load_template(mut cx: FunctionContext) -> JsResult<JsUndefined> { |
129 | | - let template_name = cx.argument::<JsString>(0)?; |
130 | | - let template_content = cx.argument::<JsString>(1)?; |
| 124 | + return cx.throw_from_mj_error(err); |
| 125 | + } |
| 126 | + }; |
131 | 127 |
|
132 | | - let mut engine = template_engine(&mut cx)?.lock().unwrap(); |
| 128 | + let mut ctx = CLReprObject::new(); |
| 129 | + ctx.insert("COMPILE_CONTEXT".to_string(), template_ctx); |
133 | 130 |
|
134 | | - if let Err(err) = engine.add_template_owned( |
135 | | - template_name.value(&mut cx), |
136 | | - template_content.value(&mut cx), |
137 | | - ) { |
138 | | - trace!("jinja load error: {:?}", err); |
| 131 | + let compile_context = to_minijinja_value(CLRepr::Object(ctx)); |
| 132 | + match template.render(compile_context) { |
| 133 | + Ok(r) => Ok(cx.string(r)), |
| 134 | + Err(err) => { |
| 135 | + trace!("jinja render template error: {:?}", err); |
139 | 136 |
|
140 | | - return cx.throw_from_mj_error(err); |
| 137 | + cx.throw_from_mj_error(err) |
| 138 | + } |
| 139 | + } |
141 | 140 | } |
142 | 141 |
|
143 | | - Ok(cx.undefined()) |
144 | | -} |
145 | | - |
146 | | -fn clear_templates(mut cx: FunctionContext) -> JsResult<JsUndefined> { |
147 | | - let mut engine = template_engine(&mut cx)?.lock().unwrap(); |
148 | | - engine.clear_templates(); |
149 | | - |
150 | | - Ok(cx.undefined()) |
151 | | -} |
| 142 | + fn load_template(mut cx: FunctionContext) -> JsResult<JsUndefined> { |
| 143 | + #[cfg(build = "debug")] |
| 144 | + trace!("JinjaEngine.load_template"); |
152 | 145 |
|
153 | | -fn render_template(mut cx: FunctionContext) -> JsResult<JsString> { |
154 | | - let template_name = cx.argument::<JsString>(0)?; |
155 | | - let template_ctx = CLRepr::from_js_ref(cx.argument::<JsValue>(1)?, &mut cx)?; |
| 146 | + let this = cx |
| 147 | + .this() |
| 148 | + .downcast_or_throw::<BoxedJinjaEngine, _>(&mut cx)?; |
156 | 149 |
|
157 | | - let engine = template_engine(&mut cx)?.lock().unwrap(); |
| 150 | + let template_name = cx.argument::<JsString>(0)?; |
| 151 | + let template_content = cx.argument::<JsString>(1)?; |
158 | 152 |
|
159 | | - let template = match engine.get_template(&template_name.value(&mut cx)) { |
160 | | - Ok(t) => t, |
161 | | - Err(err) => { |
162 | | - trace!("jinja get template error: {:?}", err); |
| 153 | + if let Err(err) = this.borrow_mut().inner.add_template_owned( |
| 154 | + template_name.value(&mut cx), |
| 155 | + template_content.value(&mut cx), |
| 156 | + ) { |
| 157 | + trace!("jinja load error: {:?}", err); |
163 | 158 |
|
164 | 159 | return cx.throw_from_mj_error(err); |
165 | 160 | } |
166 | | - }; |
167 | 161 |
|
168 | | - let mut ctx = CLReprObject::new(); |
169 | | - ctx.insert("COMPILE_CONTEXT".to_string(), template_ctx); |
| 162 | + Ok(cx.undefined()) |
| 163 | + } |
170 | 164 |
|
171 | | - let compile_context = to_minijinja_value(CLRepr::Object(ctx)); |
172 | | - match template.render(compile_context) { |
173 | | - Ok(r) => Ok(cx.string(r)), |
174 | | - Err(err) => { |
175 | | - trace!("jinja render template error: {:?}", err); |
| 165 | + fn js_new(mut cx: FunctionContext) -> JsResult<JsObject> { |
| 166 | + let engine = Self::new(&mut cx).or_else(|err| cx.throw_error(err.to_string()))?; |
176 | 167 |
|
177 | | - cx.throw_from_mj_error(err) |
178 | | - } |
| 168 | + let obj = cx.empty_object(); |
| 169 | + let obj_this = cx.boxed(RefCell::new(engine)).upcast::<JsValue>(); |
| 170 | + |
| 171 | + let render_template_fn = JsFunction::new(&mut cx, JinjaEngine::render_template)?; |
| 172 | + let render_template_fn = bind_method(&mut cx, render_template_fn, obj_this)?; |
| 173 | + obj.set(&mut cx, "renderTemplate", render_template_fn)?; |
| 174 | + |
| 175 | + let load_template_fn = JsFunction::new(&mut cx, JinjaEngine::load_template)?; |
| 176 | + let load_template_fn = bind_method(&mut cx, load_template_fn, obj_this)?; |
| 177 | + obj.set(&mut cx, "loadTemplate", load_template_fn)?; |
| 178 | + |
| 179 | + Ok(obj) |
179 | 180 | } |
180 | 181 | } |
181 | 182 |
|
182 | 183 | pub fn template_register_module(cx: &mut ModuleContext) -> NeonResult<()> { |
183 | | - cx.export_function("initJinjaEngine", init_jinja_engine)?; |
184 | | - cx.export_function("loadTemplate", load_template)?; |
185 | | - cx.export_function("clearTemplates", clear_templates)?; |
186 | | - cx.export_function("renderTemplate", render_template)?; |
| 184 | + cx.export_function("newJinjaEngine", JinjaEngine::js_new)?; |
187 | 185 |
|
188 | 186 | Ok(()) |
189 | 187 | } |
0 commit comments