runjucks_core/
extension.rs1use crate::errors::{Result, RunjucksError};
4use serde_json::Value;
5use std::collections::{HashMap, HashSet};
6use std::sync::Arc;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct ExtensionTagMeta {
11 pub extension_name: String,
13 pub end_tag: Option<String>,
15}
16
17pub type CustomExtensionHandler =
19 Arc<dyn Fn(&Value, &str, Option<String>) -> Result<String> + Send + Sync>;
20
21pub(crate) fn rebuild_extension_closing_tags(
23 extension_tags: &HashMap<String, ExtensionTagMeta>,
24 out: &mut HashSet<String>,
25) {
26 out.clear();
27 for m in extension_tags.values() {
28 if let Some(e) = &m.end_tag {
29 out.insert(e.clone());
30 }
31 }
32}
33
34pub(crate) fn register_extension_inner(
36 extension_tags: &mut HashMap<String, ExtensionTagMeta>,
37 extension_closing: &mut HashSet<String>,
38 custom_extensions: &mut HashMap<String, CustomExtensionHandler>,
39 extension_name: String,
40 tag_specs: Vec<(String, Option<String>)>,
41 handler: CustomExtensionHandler,
42 is_reserved: impl Fn(&str) -> bool,
43) -> Result<()> {
44 if tag_specs.is_empty() {
45 return Err(RunjucksError::new(
46 "extension must declare at least one tag in `tags`",
47 ));
48 }
49 let mut seen_tags = HashSet::new();
50 for (tag, _) in &tag_specs {
51 if !seen_tags.insert(tag.clone()) {
52 return Err(RunjucksError::new(format!(
53 "duplicate extension tag `{tag}` in registration"
54 )));
55 }
56 }
57 for (tag, end) in &tag_specs {
58 if is_reserved(tag) {
59 return Err(RunjucksError::new(format!(
60 "extension tag `{tag}` conflicts with a built-in tag"
61 )));
62 }
63 if let Some(e) = end {
64 if is_reserved(e) {
65 return Err(RunjucksError::new(format!(
66 "extension end tag `{e}` conflicts with a built-in tag"
67 )));
68 }
69 }
70 }
71 for (tag, _) in &tag_specs {
72 if let Some(existing) = extension_tags.get(tag) {
73 if existing.extension_name != extension_name {
74 return Err(RunjucksError::new(format!(
75 "extension tag `{tag}` is already registered by extension `{}`",
76 existing.extension_name
77 )));
78 }
79 }
80 }
81 extension_tags.retain(|_, v| v.extension_name != extension_name);
82 custom_extensions.remove(&extension_name);
83 for (tag, end) in tag_specs {
84 extension_tags.insert(
85 tag,
86 ExtensionTagMeta {
87 extension_name: extension_name.clone(),
88 end_tag: end,
89 },
90 );
91 }
92 custom_extensions.insert(extension_name, handler);
93 rebuild_extension_closing_tags(extension_tags, extension_closing);
94 Ok(())
95}
96
97pub fn remove_extension_inner(
99 extension_tags: &mut HashMap<String, ExtensionTagMeta>,
100 extension_closing: &mut HashSet<String>,
101 custom_extensions: &mut HashMap<String, CustomExtensionHandler>,
102 extension_name: &str,
103) -> bool {
104 let removed = custom_extensions.remove(extension_name).is_some();
105 extension_tags.retain(|_, v| v.extension_name != extension_name);
106 rebuild_extension_closing_tags(extension_tags, extension_closing);
107 removed
108}