1use crate::ast::Node;
6use crate::errors::{Result, RunjucksError};
7use crate::extension::{
8 register_extension_inner, remove_extension_inner, CustomExtensionHandler, ExtensionTagMeta,
9};
10use crate::globals::{default_globals_map, value_is_callable, RJ_CALLABLE};
11use crate::lexer::{LexerOptions, Tags};
12use crate::loader::TemplateLoader;
13use crate::parser::is_reserved_tag_keyword;
14use crate::value::{
15 is_marked_safe, is_regexp_value, is_undefined_value, undefined_value, value_to_string,
16};
17use crate::{lexer, parser, renderer};
18use serde_json::{Map, Value};
19use std::borrow::Cow;
20use std::collections::{HashMap, HashSet};
21use std::hash::{Hash, Hasher};
22use std::sync::{Arc, Mutex};
23
24#[derive(Clone, Debug, PartialEq, Eq)]
25struct ParseSignature {
26 trim_blocks: bool,
27 lstrip_blocks: bool,
28 tags: Option<Tags>,
29 extension_tag_keys: Vec<String>,
30 extension_closing_names: Vec<String>,
31}
32
33struct CachedParse {
34 sig: ParseSignature,
35 ast: Arc<Node>,
36 source: Option<String>,
38}
39
40fn hash_source(s: &str) -> u64 {
41 use std::collections::hash_map::DefaultHasher;
42 let mut h = DefaultHasher::new();
43 s.hash(&mut h);
44 h.finish()
45}
46
47pub type CustomFilter = Arc<dyn Fn(&Value, &[Value]) -> Result<Value> + Send + Sync>;
51
52pub type CustomTest = Arc<dyn Fn(&Value, &[Value]) -> Result<bool> + Send + Sync>;
54
55pub type CustomGlobalFn = Arc<dyn Fn(&[Value], &[(String, Value)]) -> Result<Value> + Send + Sync>;
60
61#[cfg(feature = "async")]
63pub type AsyncCustomFilter = Arc<
64 dyn Fn(
65 &Value,
66 &[Value],
67 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Value>> + Send>>
68 + Send
69 + Sync,
70>;
71
72#[cfg(feature = "async")]
74pub type AsyncCustomGlobalFn = Arc<
75 dyn Fn(
76 &[Value],
77 &[(String, Value)],
78 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Value>> + Send>>
79 + Send
80 + Sync,
81>;
82
83#[derive(Clone, Debug, PartialEq, Eq)]
85pub struct ExtensionDescriptor {
86 pub name: String,
87 pub tags: Vec<String>,
88 pub blocks: HashMap<String, String>,
89}
90
91#[derive(Clone)]
117pub struct Environment {
118 pub autoescape: bool,
120 pub dev: bool,
122 pub loader: Option<Arc<dyn TemplateLoader + Send + Sync>>,
124 pub globals: HashMap<String, Value>,
126 pub throw_on_undefined: bool,
128 pub random_seed: Option<u64>,
130 pub trim_blocks: bool,
132 pub lstrip_blocks: bool,
134 pub tags: Option<Tags>,
136 pub(crate) custom_filters: HashMap<String, CustomFilter>,
137 pub(crate) custom_tests: HashMap<String, CustomTest>,
138 pub(crate) custom_globals: HashMap<String, CustomGlobalFn>,
140 pub(crate) extension_tags: HashMap<String, ExtensionTagMeta>,
142 pub(crate) extension_closing_tag_names: HashSet<String>,
143 pub(crate) custom_extensions: HashMap<String, CustomExtensionHandler>,
144 inline_parse_cache: Arc<Mutex<HashMap<u64, CachedParse>>>,
145 named_parse_cache: Arc<Mutex<HashMap<String, CachedParse>>>,
146 #[cfg(feature = "async")]
147 pub(crate) async_custom_filters: HashMap<String, AsyncCustomFilter>,
148 #[cfg(feature = "async")]
149 pub(crate) async_custom_globals: HashMap<String, AsyncCustomGlobalFn>,
150}
151
152impl std::fmt::Debug for Environment {
153 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154 f.debug_struct("Environment")
155 .field("autoescape", &self.autoescape)
156 .field("dev", &self.dev)
157 .field("loader", &self.loader.is_some())
158 .field("globals_len", &self.globals.len())
159 .field("custom_filters_len", &self.custom_filters.len())
160 .field("custom_tests_len", &self.custom_tests.len())
161 .field("custom_globals_len", &self.custom_globals.len())
162 .field("extension_tags_len", &self.extension_tags.len())
163 .field("throw_on_undefined", &self.throw_on_undefined)
164 .field("random_seed", &self.random_seed)
165 .finish()
166 }
167}
168
169fn is_truthy_value(v: &Value) -> bool {
170 if is_undefined_value(v) {
171 return false;
172 }
173 match v {
174 Value::Null | Value::Bool(false) => false,
175 Value::Bool(true) => true,
176 Value::Number(n) => n.as_f64().map(|x| x != 0.0 && !x.is_nan()).unwrap_or(true),
177 Value::String(s) => !s.is_empty(),
178 Value::Array(_) | Value::Object(_) => true,
179 }
180}
181
182fn as_is_test_integer(v: &Value) -> Result<i64> {
183 v.as_i64()
184 .or_else(|| v.as_f64().map(|x| x as i64))
185 .ok_or_else(|| RunjucksError::new("test expected a number"))
186}
187
188fn to_number_for_is_compare(v: &Value) -> Option<f64> {
190 if is_undefined_value(v) {
191 return None;
192 }
193 match v {
194 Value::Null => Some(0.0),
195 Value::Bool(b) => Some(if *b { 1.0 } else { 0.0 }),
196 Value::Number(n) => n.as_f64(),
197 Value::String(s) => s.trim().parse::<f64>().ok(),
198 _ => None,
199 }
200}
201
202fn relational_ordering(value: &Value, other: &Value) -> Option<std::cmp::Ordering> {
203 let (n1, n2) = (
204 to_number_for_is_compare(value),
205 to_number_for_is_compare(other),
206 );
207 if let (Some(a), Some(b)) = (n1, n2) {
208 if a.is_nan() || b.is_nan() {
209 return None;
210 }
211 return a.partial_cmp(&b);
212 }
213 if let (Value::String(s1), Value::String(s2)) = (value, other) {
214 return Some(s1.cmp(s2));
215 }
216 None
217}
218
219fn is_test_gt(value: &Value, arg_vals: &[Value]) -> bool {
220 let Some(other) = arg_vals.first() else {
221 return false;
222 };
223 relational_ordering(value, other)
224 .map(|o| o == std::cmp::Ordering::Greater)
225 .unwrap_or(false)
226}
227
228fn is_test_ge(value: &Value, arg_vals: &[Value]) -> bool {
229 let Some(other) = arg_vals.first() else {
230 return false;
231 };
232 relational_ordering(value, other)
233 .map(|o| o == std::cmp::Ordering::Greater || o == std::cmp::Ordering::Equal)
234 .unwrap_or(false)
235}
236
237fn is_test_lt(value: &Value, arg_vals: &[Value]) -> bool {
238 let Some(other) = arg_vals.first() else {
239 return false;
240 };
241 relational_ordering(value, other)
242 .map(|o| o == std::cmp::Ordering::Less)
243 .unwrap_or(false)
244}
245
246fn is_test_le(value: &Value, arg_vals: &[Value]) -> bool {
247 let Some(other) = arg_vals.first() else {
248 return false;
249 };
250 relational_ordering(value, other)
251 .map(|o| o == std::cmp::Ordering::Less || o == std::cmp::Ordering::Equal)
252 .unwrap_or(false)
253}
254
255fn is_test_iterable(v: &Value) -> bool {
256 match v {
257 Value::String(_) | Value::Array(_) => true,
258 Value::Object(_) if is_undefined_value(v) || is_marked_safe(v) || is_regexp_value(v) => {
259 false
260 }
261 Value::Object(_) => false,
262 _ => false,
263 }
264}
265
266fn is_test_mapping(v: &Value) -> bool {
267 if is_undefined_value(v) {
268 return false;
269 }
270 match v {
271 Value::Object(_) if is_marked_safe(v) || is_regexp_value(v) => false,
272 Value::Object(_) => true,
273 _ => false,
274 }
275}
276
277pub(crate) fn equalto_sameas_pair(
281 left: &Value,
282 right: &Value,
283 same_template_variable: bool,
284) -> bool {
285 if same_template_variable {
286 return true;
287 }
288 match (left, right) {
289 (Value::Object(_), Value::Object(_)) | (Value::Array(_), Value::Array(_)) => false,
290 _ => left == right,
291 }
292}
293
294impl Default for Environment {
295 fn default() -> Self {
297 Self {
298 autoescape: true,
299 dev: false,
300 loader: None,
301 globals: default_globals_map(),
302 throw_on_undefined: false,
303 random_seed: None,
304 trim_blocks: false,
305 lstrip_blocks: false,
306 tags: None,
307 custom_filters: HashMap::new(),
308 custom_tests: HashMap::new(),
309 custom_globals: HashMap::new(),
310 extension_tags: HashMap::new(),
311 extension_closing_tag_names: HashSet::new(),
312 custom_extensions: HashMap::new(),
313 inline_parse_cache: Arc::new(Mutex::new(HashMap::new())),
314 named_parse_cache: Arc::new(Mutex::new(HashMap::new())),
315 #[cfg(feature = "async")]
316 async_custom_filters: HashMap::new(),
317 #[cfg(feature = "async")]
318 async_custom_globals: HashMap::new(),
319 }
320 }
321}
322
323impl Environment {
324 fn current_parse_signature(&self) -> ParseSignature {
325 let mut keys: Vec<_> = self.extension_tags.keys().cloned().collect();
326 keys.sort();
327 let mut closing: Vec<_> = self.extension_closing_tag_names.iter().cloned().collect();
328 closing.sort();
329 ParseSignature {
330 trim_blocks: self.trim_blocks,
331 lstrip_blocks: self.lstrip_blocks,
332 tags: self.tags.clone(),
333 extension_tag_keys: keys,
334 extension_closing_names: closing,
335 }
336 }
337
338 fn parse_source_to_ast(&self, src: &str) -> Result<Node> {
339 let tokens = lexer::tokenize_with_options(src, self.lexer_options())?;
340 parser::parse_with_env(
341 &tokens,
342 &self.extension_tags,
343 &self.extension_closing_tag_names,
344 )
345 }
346
347 pub fn parse_or_cached_inline(&self, src: &str) -> Result<Arc<Node>> {
349 let sig = self.current_parse_signature();
350 let key = hash_source(src);
351 {
352 let cache = self.inline_parse_cache.lock().unwrap();
353 if let Some(c) = cache.get(&key) {
354 if c.sig == sig && c.source.as_deref() == Some(src) {
355 return Ok(Arc::clone(&c.ast));
356 }
357 }
358 }
359 let node = self.parse_source_to_ast(src)?;
360 let arc = Arc::new(node);
361 let mut cache = self.inline_parse_cache.lock().unwrap();
362 cache.insert(
363 key,
364 CachedParse {
365 sig,
366 ast: Arc::clone(&arc),
367 source: Some(src.to_string()),
368 },
369 );
370 Ok(arc)
371 }
372
373 pub(crate) fn load_and_parse_named(
375 &self,
376 name: &str,
377 loader: &(dyn TemplateLoader + Send + Sync),
378 ) -> Result<Arc<Node>> {
379 let src = loader.load(name)?;
380 self.parse_with_named_cache(name, loader, &src)
381 }
382
383 fn parse_with_named_cache(
384 &self,
385 name: &str,
386 loader: &(dyn TemplateLoader + Send + Sync),
387 src: &str,
388 ) -> Result<Arc<Node>> {
389 let sig = self.current_parse_signature();
390 if let Some(ref key) = loader.cache_key(name) {
391 {
392 let cache = self.named_parse_cache.lock().unwrap();
393 if let Some(c) = cache.get(key) {
394 if c.sig == sig && c.source.as_deref() == Some(src) {
395 return Ok(Arc::clone(&c.ast));
396 }
397 }
398 }
399 let node = self.parse_source_to_ast(src)?;
400 let arc = Arc::new(node);
401 let mut cache = self.named_parse_cache.lock().unwrap();
402 cache.insert(
403 key.clone(),
404 CachedParse {
405 sig,
406 ast: Arc::clone(&arc),
407 source: Some(src.to_string()),
408 },
409 );
410 Ok(arc)
411 } else {
412 let node = self.parse_source_to_ast(src)?;
413 Ok(Arc::new(node))
414 }
415 }
416
417 pub fn clear_named_parse_cache(&self) {
419 self.named_parse_cache.lock().unwrap().clear();
420 }
421
422 pub fn invalidate_cache(&self) {
425 self.named_parse_cache.lock().unwrap().clear();
426 self.inline_parse_cache.lock().unwrap().clear();
427 }
428
429 pub fn render_parsed(&self, ast: &Node, context: Value) -> Result<String> {
431 let root = match context {
432 Value::Object(m) => m,
433 _ => Map::new(),
434 };
435 let mut stack = renderer::CtxStack::from_root(root);
436 let loader = self.loader.as_ref().map(|arc| arc.as_ref());
437 renderer::render(self, loader, ast, &mut stack)
438 }
439
440 pub fn add_global(&mut self, name: impl Into<String>, value: Value) -> &mut Self {
444 let name = name.into();
445 self.custom_globals.remove(&name);
446 self.globals.insert(name, value);
447 self
448 }
449
450 pub fn add_global_callable(&mut self, name: impl Into<String>, f: CustomGlobalFn) -> &mut Self {
454 let name = name.into();
455 let mut m = Map::new();
456 m.insert(RJ_CALLABLE.to_string(), Value::Bool(true));
457 self.globals.insert(name.clone(), Value::Object(m));
458 self.custom_globals.insert(name, f);
459 self
460 }
461
462 pub fn add_filter(&mut self, name: impl Into<String>, filter: CustomFilter) -> &mut Self {
464 self.custom_filters.insert(name.into(), filter);
465 self
466 }
467
468 pub fn add_test(&mut self, name: impl Into<String>, test: CustomTest) -> &mut Self {
470 self.custom_tests.insert(name.into(), test);
471 self
472 }
473
474 pub fn register_extension(
476 &mut self,
477 extension_name: impl Into<String>,
478 tag_specs: Vec<(String, Option<String>)>,
479 handler: CustomExtensionHandler,
480 ) -> Result<()> {
481 let extension_name = extension_name.into();
482 register_extension_inner(
483 &mut self.extension_tags,
484 &mut self.extension_closing_tag_names,
485 &mut self.custom_extensions,
486 extension_name,
487 tag_specs,
488 handler,
489 is_reserved_tag_keyword,
490 )
491 }
492
493 pub fn has_extension(&self, name: &str) -> bool {
495 self.custom_extensions.contains_key(name)
496 }
497
498 pub fn get_extension_descriptor(&self, name: &str) -> Option<ExtensionDescriptor> {
500 if !self.custom_extensions.contains_key(name) {
501 return None;
502 }
503 let mut tags = Vec::new();
504 let mut blocks = HashMap::new();
505 for (tag, meta) in &self.extension_tags {
506 if meta.extension_name == name {
507 tags.push(tag.clone());
508 if let Some(end) = &meta.end_tag {
509 blocks.insert(tag.clone(), end.clone());
510 }
511 }
512 }
513 tags.sort();
514 Some(ExtensionDescriptor {
515 name: name.to_string(),
516 tags,
517 blocks,
518 })
519 }
520
521 pub fn remove_extension(&mut self, name: &str) -> bool {
523 remove_extension_inner(
524 &mut self.extension_tags,
525 &mut self.extension_closing_tag_names,
526 &mut self.custom_extensions,
527 name,
528 )
529 }
530
531 pub fn validate_lex_parse(&self, src: &str) -> Result<()> {
533 let tokens = lexer::tokenize_with_options(src, self.lexer_options())?;
534 let _ = parser::parse_with_env(
535 &tokens,
536 &self.extension_tags,
537 &self.extension_closing_tag_names,
538 )?;
539 Ok(())
540 }
541
542 pub(crate) fn eval_user_is_test(
543 &self,
544 name: &str,
545 value: &Value,
546 args: &[Value],
547 ) -> Result<bool> {
548 match self.custom_tests.get(name) {
549 Some(t) => t(value, args),
550 None => Err(RunjucksError::new(format!("unknown test: `{name}`"))),
551 }
552 }
553
554 pub(crate) fn apply_is_test(
556 &self,
557 test_name: &str,
558 value: &Value,
559 arg_vals: &[Value],
560 ) -> Result<bool> {
561 match test_name {
562 "equalto" | "eq" | "sameas" => Ok(match arg_vals.first() {
563 Some(a) => equalto_sameas_pair(value, a, false),
564 None => false,
565 }),
566 "null" | "none" => Ok(value.is_null()),
567 "undefined" => Ok(is_undefined_value(value)),
568 "escaped" => Ok(is_marked_safe(value)),
569 "falsy" => Ok(!is_truthy_value(value)),
570 "truthy" => Ok(is_truthy_value(value)),
571 "number" => Ok(value.is_number()),
572 "string" => Ok(value.is_string()),
573 "lower" => Ok(match value {
574 Value::String(s) => s.chars().all(|c| !c.is_uppercase()),
575 _ => false,
576 }),
577 "upper" => Ok(match value {
578 Value::String(s) => s.chars().all(|c| !c.is_lowercase()),
579 _ => false,
580 }),
581 "callable" => Ok(value_is_callable(value)),
582 "defined" => Ok(!is_undefined_value(value)),
583 "odd" => {
584 let n = as_is_test_integer(value)?;
585 Ok(n.rem_euclid(2) != 0)
586 }
587 "even" => {
588 let n = as_is_test_integer(value)?;
589 Ok(n.rem_euclid(2) == 0)
590 }
591 "divisibleby" => {
592 let denom = arg_vals
593 .first()
594 .and_then(|a| {
595 a.as_i64()
596 .or_else(|| a.as_f64().map(|x| x as i64))
597 .or_else(|| value_to_string(a).parse().ok())
598 })
599 .ok_or_else(|| RunjucksError::new("`divisibleby` test expects a divisor"))?;
600 if denom == 0 {
601 return Ok(false);
602 }
603 let n = as_is_test_integer(value)?;
604 Ok(n.rem_euclid(denom) == 0)
605 }
606 "greaterthan" | "gt" => Ok(is_test_gt(value, arg_vals)),
607 "lessthan" | "lt" => Ok(is_test_lt(value, arg_vals)),
608 "ge" => Ok(is_test_ge(value, arg_vals)),
609 "le" => Ok(is_test_le(value, arg_vals)),
610 "ne" => Ok(match arg_vals.first() {
611 Some(a) => value != a,
612 None => !is_undefined_value(value),
613 }),
614 "iterable" => Ok(is_test_iterable(value)),
615 "mapping" => Ok(is_test_mapping(value)),
616 _ => self.eval_user_is_test(test_name, value, arg_vals),
617 }
618 }
619
620 pub fn resolve_variable_ref<'a>(
626 &'a self,
627 stack: &'a renderer::CtxStack,
628 name: &str,
629 ) -> Result<Cow<'a, Value>> {
630 if stack.defined(name) {
631 Ok(Cow::Borrowed(stack.get_ref(name).expect(
632 "internal error: variable marked defined but missing from stack",
633 )))
634 } else if let Some(v) = self.globals.get(name) {
635 Ok(Cow::Borrowed(v))
636 } else if self.throw_on_undefined {
637 Err(RunjucksError::new(format!("undefined variable: `{name}`")))
638 } else {
639 Ok(Cow::Owned(undefined_value()))
640 }
641 }
642
643 pub fn resolve_variable(&self, stack: &renderer::CtxStack, name: &str) -> Result<Value> {
645 self.resolve_variable_ref(stack, name)
646 .map(|c| c.into_owned())
647 }
648
649 pub fn lexer_options(&self) -> LexerOptions {
651 LexerOptions {
652 trim_blocks: self.trim_blocks,
653 lstrip_blocks: self.lstrip_blocks,
654 tags: self.tags.clone(),
655 }
656 }
657
658 pub fn render_string(&self, template: String, context: Value) -> Result<String> {
681 let ast = self.parse_or_cached_inline(&template)?;
682 self.render_parsed(ast.as_ref(), context)
683 }
684
685 pub fn render_template(&self, name: &str, context: Value) -> Result<String> {
689 let loader = self
690 .loader
691 .as_ref()
692 .ok_or_else(|| RunjucksError::new("no template loader configured"))?;
693 let ast = self.load_and_parse_named(name, loader.as_ref())?;
694 let root = match context {
695 Value::Object(m) => m,
696 _ => Map::new(),
697 };
698 let mut stack = renderer::CtxStack::from_root(root);
699 let mut state = renderer::RenderState::new(Some(loader.as_ref()), self.random_seed);
700 state.push_template(name)?;
701 renderer::scan_literal_extends_graph(self, &mut state, ast.as_ref(), loader.as_ref())?;
702 let out = renderer::render_entry(self, &mut state, ast.as_ref(), &mut stack)?;
703 state.pop_template();
704 Ok(out)
705 }
706
707 #[cfg(feature = "async")]
709 pub fn add_async_filter(&mut self, name: String, filter: AsyncCustomFilter) -> &mut Self {
710 self.async_custom_filters.insert(name, filter);
711 self
712 }
713
714 #[cfg(feature = "async")]
716 pub fn add_async_global_callable(
717 &mut self,
718 name: String,
719 f: AsyncCustomGlobalFn,
720 ) -> &mut Self {
721 let mut m = serde_json::Map::new();
722 m.insert(RJ_CALLABLE.to_string(), Value::Bool(true));
723 self.globals.insert(name.clone(), Value::Object(m));
724 self.async_custom_globals.insert(name, f);
725 self
726 }
727
728 #[cfg(feature = "async")]
730 pub async fn render_string_async(&self, template: String, context: Value) -> Result<String> {
731 let ast = self.parse_or_cached_inline(&template)?;
732 crate::async_renderer::render_async(self, ast.as_ref(), context).await
733 }
734
735 #[cfg(feature = "async")]
737 pub async fn render_template_async(&self, name: &str, context: Value) -> Result<String> {
738 let loader = self
739 .loader
740 .as_ref()
741 .ok_or_else(|| RunjucksError::new("no template loader configured"))?;
742 let ast = self.load_and_parse_named(name, loader.as_ref())?;
743 let root = match context {
744 Value::Object(m) => m,
745 _ => serde_json::Map::new(),
746 };
747 let mut stack = renderer::CtxStack::from_root(root);
748 let loader_ref = self.loader.as_ref().map(|l| l.as_ref());
749 let mut state = renderer::RenderState::new(loader_ref, self.random_seed);
750 state.push_template(name)?;
751 renderer::scan_literal_extends_graph(self, &mut state, ast.as_ref(), loader.as_ref())?;
752 let out = crate::async_renderer::entry::render_entry_async(self, &mut state, ast.as_ref(), &mut stack).await?;
753 state.pop_template();
754 Ok(out)
755 }
756}