[pve-devel] [PATCH proxmox v2 01/20] notify: switch to file-based templating system

Lukas Wagner l.wagner at proxmox.com
Fri Apr 19 16:17:04 CEST 2024


Instead of passing the template strings for subject and body when
constructing a notification, we pass only the name of a template.
When rendering the template, the name of the template is used to find
corresponding template files. For PVE, they are located at
/usr/share/proxmox-ve/templates/default. The `default` part is
the 'template namespace', which is a preparation for user-customizable
and/or translatable notifications.

Previously, the same template string was used to render HTML and
plaintext notifications. This was achieved by providing some template
helpers that 'abstract away' HTML/plaintext formatting. However,
in hindsight this turned out to be pretty finicky. Since the
current changes lay the foundations for user-customizable notification
templates, I ripped these abstractions out. Now there are simply two
templates, one for plaintext, one for HTML.

Signed-off-by: Lukas Wagner <l.wagner at proxmox.com>
Tested-by: Folke Gleumes <f.gleumes at proxmox.com>
Reviewed-by: Fiona Ebner <f.ebner at proxmox.com>
---
 proxmox-notify/examples/render.rs        |  63 --------
 proxmox-notify/src/context/mod.rs        |  10 +-
 proxmox-notify/src/context/pbs.rs        |  16 ++
 proxmox-notify/src/context/pve.rs        |  15 ++
 proxmox-notify/src/context/test.rs       |   9 ++
 proxmox-notify/src/endpoints/gotify.rs   |   9 +-
 proxmox-notify/src/endpoints/sendmail.rs |  13 +-
 proxmox-notify/src/endpoints/smtp.rs     |  11 +-
 proxmox-notify/src/lib.rs                |  27 ++--
 proxmox-notify/src/matcher.rs            |  24 +--
 proxmox-notify/src/renderer/html.rs      |  14 --
 proxmox-notify/src/renderer/mod.rs       | 197 +++++++----------------
 proxmox-notify/src/renderer/plaintext.rs |  39 -----
 13 files changed, 137 insertions(+), 310 deletions(-)
 delete mode 100644 proxmox-notify/examples/render.rs

diff --git a/proxmox-notify/examples/render.rs b/proxmox-notify/examples/render.rs
deleted file mode 100644
index d705fd0..0000000
--- a/proxmox-notify/examples/render.rs
+++ /dev/null
@@ -1,63 +0,0 @@
-use proxmox_notify::renderer::{render_template, TemplateRenderer};
-use proxmox_notify::Error;
-
-use serde_json::json;
-
-const TEMPLATE: &str = r#"
-{{ heading-1 "Backup Report"}}
-A backup job on host {{host}} was run.
-
-{{ heading-2 "Guests"}}
-{{ table table }}
-The total size of all backups is {{human-bytes total-size}}.
-
-The backup job took {{duration total-time}}.
-
-{{ heading-2 "Logs"}}
-{{ verbatim-monospaced logs}}
-
-{{ heading-2 "Objects"}}
-{{ object table }}
-"#;
-
-fn main() -> Result<(), Error> {
-    let properties = json!({
-        "host": "pali",
-        "logs": "100: starting backup\n100: backup failed",
-        "total-size": 1024 * 1024 + 2048 * 1024,
-        "total-time": 100,
-        "table": {
-            "schema": {
-                "columns": [
-                    {
-                        "label": "VMID",
-                        "id": "vmid"
-                    },
-                    {
-                        "label": "Size",
-                        "id": "size",
-                        "renderer": "human-bytes"
-                    }
-                ],
-            },
-            "data" : [
-                {
-                    "vmid": 1001,
-                    "size": "1048576"
-                },
-                {
-                    "vmid": 1002,
-                    "size": 2048 * 1024,
-                }
-            ]
-        }
-    });
-
-    let output = render_template(TemplateRenderer::Html, TEMPLATE, &properties)?;
-    println!("{output}");
-
-    let output = render_template(TemplateRenderer::Plaintext, TEMPLATE, &properties)?;
-    println!("{output}");
-
-    Ok(())
-}
diff --git a/proxmox-notify/src/context/mod.rs b/proxmox-notify/src/context/mod.rs
index cc68603..c0a5a13 100644
--- a/proxmox-notify/src/context/mod.rs
+++ b/proxmox-notify/src/context/mod.rs
@@ -1,6 +1,8 @@
 use std::fmt::Debug;
 use std::sync::Mutex;
 
+use crate::Error;
+
 #[cfg(any(feature = "pve-context", feature = "pbs-context"))]
 pub mod common;
 #[cfg(feature = "pbs-context")]
@@ -20,8 +22,14 @@ pub trait Context: Send + Sync + Debug {
     fn default_sendmail_from(&self) -> String;
     /// Proxy configuration for the current node
     fn http_proxy_config(&self) -> Option<String>;
-    // Return default config for built-in targets/matchers.
+    /// Return default config for built-in targets/matchers.
     fn default_config(&self) -> &'static str;
+    /// Lookup a template in a certain (optional) namespace
+    fn lookup_template(
+        &self,
+        filename: &str,
+        namespace: Option<&str>,
+    ) -> Result<Option<String>, Error>;
 }
 
 #[cfg(not(test))]
diff --git a/proxmox-notify/src/context/pbs.rs b/proxmox-notify/src/context/pbs.rs
index 5b97af7..70e993f 100644
--- a/proxmox-notify/src/context/pbs.rs
+++ b/proxmox-notify/src/context/pbs.rs
@@ -1,9 +1,11 @@
 use serde::Deserialize;
+use std::path::Path;
 
 use proxmox_schema::{ObjectSchema, Schema, StringSchema};
 use proxmox_section_config::{SectionConfig, SectionConfigPlugin};
 
 use crate::context::{common, Context};
+use crate::Error;
 
 const PBS_USER_CFG_FILENAME: &str = "/etc/proxmox-backup/user.cfg";
 const PBS_NODE_CFG_FILENAME: &str = "/etc/proxmox-backup/node.cfg";
@@ -98,6 +100,20 @@ impl Context for PBSContext {
     fn default_config(&self) -> &'static str {
         return DEFAULT_CONFIG;
     }
+
+    fn lookup_template(
+        &self,
+        filename: &str,
+        namespace: Option<&str>,
+    ) -> Result<Option<String>, Error> {
+        let path = Path::new("/usr/share/proxmox-backup/templates")
+            .join(namespace.unwrap_or("default"))
+            .join(filename);
+
+        let template_string = proxmox_sys::fs::file_read_optional_string(path)
+            .map_err(|err| Error::Generic(format!("could not load template: {err}")))?;
+        Ok(template_string)
+    }
 }
 
 #[cfg(test)]
diff --git a/proxmox-notify/src/context/pve.rs b/proxmox-notify/src/context/pve.rs
index 39e0e4a..647f7c8 100644
--- a/proxmox-notify/src/context/pve.rs
+++ b/proxmox-notify/src/context/pve.rs
@@ -1,4 +1,6 @@
 use crate::context::{common, Context};
+use crate::Error;
+use std::path::Path;
 
 fn lookup_mail_address(content: &str, user: &str) -> Option<String> {
     common::normalize_for_return(content.lines().find_map(|line| {
@@ -51,6 +53,19 @@ impl Context for PVEContext {
     fn default_config(&self) -> &'static str {
         return DEFAULT_CONFIG;
     }
+
+    fn lookup_template(
+        &self,
+        filename: &str,
+        namespace: Option<&str>,
+    ) -> Result<Option<String>, Error> {
+        let path = Path::new("/usr/share/pve-manager/templates")
+            .join(namespace.unwrap_or("default"))
+            .join(filename);
+        let template_string = proxmox_sys::fs::file_read_optional_string(path)
+            .map_err(|err| Error::Generic(format!("could not load template: {err}")))?;
+        Ok(template_string)
+    }
 }
 
 pub static PVE_CONTEXT: PVEContext = PVEContext;
diff --git a/proxmox-notify/src/context/test.rs b/proxmox-notify/src/context/test.rs
index 61f794a..5df25d0 100644
--- a/proxmox-notify/src/context/test.rs
+++ b/proxmox-notify/src/context/test.rs
@@ -1,4 +1,5 @@
 use crate::context::Context;
+use crate::Error;
 
 #[derive(Debug)]
 pub struct TestContext;
@@ -23,4 +24,12 @@ impl Context for TestContext {
     fn default_config(&self) -> &'static str {
         ""
     }
+
+    fn lookup_template(
+        &self,
+        _filename: &str,
+        _namespace: Option<&str>,
+    ) -> Result<Option<String>, Error> {
+        Ok(Some(String::new()))
+    }
 }
diff --git a/proxmox-notify/src/endpoints/gotify.rs b/proxmox-notify/src/endpoints/gotify.rs
index 20c83bf..afdfabc 100644
--- a/proxmox-notify/src/endpoints/gotify.rs
+++ b/proxmox-notify/src/endpoints/gotify.rs
@@ -9,7 +9,7 @@ use proxmox_schema::api_types::COMMENT_SCHEMA;
 use proxmox_schema::{api, Updater};
 
 use crate::context::context;
-use crate::renderer::TemplateRenderer;
+use crate::renderer::TemplateType;
 use crate::schema::ENTITY_NAME_SCHEMA;
 use crate::{renderer, Content, Endpoint, Error, Notification, Origin, Severity};
 
@@ -92,14 +92,13 @@ impl Endpoint for GotifyEndpoint {
     fn send(&self, notification: &Notification) -> Result<(), Error> {
         let (title, message) = match &notification.content {
             Content::Template {
-                title_template,
-                body_template,
+                template_name,
                 data,
             } => {
                 let rendered_title =
-                    renderer::render_template(TemplateRenderer::Plaintext, title_template, data)?;
+                    renderer::render_template(TemplateType::Subject, template_name, data)?;
                 let rendered_message =
-                    renderer::render_template(TemplateRenderer::Plaintext, body_template, data)?;
+                    renderer::render_template(TemplateType::PlaintextBody, template_name, data)?;
 
                 (rendered_title, rendered_message)
             }
diff --git a/proxmox-notify/src/endpoints/sendmail.rs b/proxmox-notify/src/endpoints/sendmail.rs
index 4fc92b4..6178b5e 100644
--- a/proxmox-notify/src/endpoints/sendmail.rs
+++ b/proxmox-notify/src/endpoints/sendmail.rs
@@ -3,9 +3,9 @@ use serde::{Deserialize, Serialize};
 use proxmox_schema::api_types::COMMENT_SCHEMA;
 use proxmox_schema::{api, Updater};
 
-use crate::context::context;
+use crate::context;
 use crate::endpoints::common::mail;
-use crate::renderer::TemplateRenderer;
+use crate::renderer::TemplateType;
 use crate::schema::{EMAIL_SCHEMA, ENTITY_NAME_SCHEMA, USER_SCHEMA};
 use crate::{renderer, Content, Endpoint, Error, Notification, Origin};
 
@@ -103,16 +103,15 @@ impl Endpoint for SendmailEndpoint {
 
         match &notification.content {
             Content::Template {
-                title_template,
-                body_template,
+                template_name,
                 data,
             } => {
                 let subject =
-                    renderer::render_template(TemplateRenderer::Plaintext, title_template, data)?;
+                    renderer::render_template(TemplateType::Subject, template_name, data)?;
                 let html_part =
-                    renderer::render_template(TemplateRenderer::Html, body_template, data)?;
+                    renderer::render_template(TemplateType::HtmlBody, template_name, data)?;
                 let text_part =
-                    renderer::render_template(TemplateRenderer::Plaintext, body_template, data)?;
+                    renderer::render_template(TemplateType::PlaintextBody, template_name, data)?;
 
                 let author = self
                     .config
diff --git a/proxmox-notify/src/endpoints/smtp.rs b/proxmox-notify/src/endpoints/smtp.rs
index 5470257..f0c836a 100644
--- a/proxmox-notify/src/endpoints/smtp.rs
+++ b/proxmox-notify/src/endpoints/smtp.rs
@@ -11,7 +11,7 @@ use proxmox_schema::{api, Updater};
 
 use crate::context::context;
 use crate::endpoints::common::mail;
-use crate::renderer::TemplateRenderer;
+use crate::renderer::TemplateType;
 use crate::schema::{EMAIL_SCHEMA, ENTITY_NAME_SCHEMA, USER_SCHEMA};
 use crate::{renderer, Content, Endpoint, Error, Notification, Origin};
 
@@ -202,16 +202,15 @@ impl Endpoint for SmtpEndpoint {
 
         let mut email = match &notification.content {
             Content::Template {
-                title_template,
-                body_template,
+                template_name,
                 data,
             } => {
                 let subject =
-                    renderer::render_template(TemplateRenderer::Plaintext, title_template, data)?;
+                    renderer::render_template(TemplateType::Subject, template_name, data)?;
                 let html_part =
-                    renderer::render_template(TemplateRenderer::Html, body_template, data)?;
+                    renderer::render_template(TemplateType::HtmlBody, template_name, data)?;
                 let text_part =
-                    renderer::render_template(TemplateRenderer::Plaintext, body_template, data)?;
+                    renderer::render_template(TemplateType::PlaintextBody, template_name, data)?;
 
                 email_builder = email_builder.subject(subject);
 
diff --git a/proxmox-notify/src/lib.rs b/proxmox-notify/src/lib.rs
index ee1e445..9f8683f 100644
--- a/proxmox-notify/src/lib.rs
+++ b/proxmox-notify/src/lib.rs
@@ -162,10 +162,8 @@ pub trait Endpoint {
 pub enum Content {
     /// Title and body will be rendered as a template
     Template {
-        /// Template for the notification title.
-        title_template: String,
-        /// Template for the notification body.
-        body_template: String,
+        /// Name of the used template
+        template_name: String,
         /// Data that can be used for template rendering.
         data: Value,
     },
@@ -203,10 +201,9 @@ pub struct Notification {
 }
 
 impl Notification {
-    pub fn new_templated<S: AsRef<str>>(
+    pub fn from_template<S: AsRef<str>>(
         severity: Severity,
-        title: S,
-        body: S,
+        template_name: S,
         template_data: Value,
         fields: HashMap<String, String>,
     ) -> Self {
@@ -217,8 +214,7 @@ impl Notification {
                 timestamp: proxmox_time::epoch_i64(),
             },
             content: Content::Template {
-                title_template: title.as_ref().to_string(),
-                body_template: body.as_ref().to_string(),
+                template_name: template_name.as_ref().to_string(),
                 data: template_data,
             },
         }
@@ -549,8 +545,7 @@ impl Bus {
                 timestamp: proxmox_time::epoch_i64(),
             },
             content: Content::Template {
-                title_template: "Test notification".into(),
-                body_template: "This is a test of the notification target '{{ target }}'".into(),
+                template_name: "test".to_string(),
                 data: json!({ "target": target }),
             },
         };
@@ -623,10 +618,9 @@ mod tests {
         bus.add_matcher(matcher);
 
         // Send directly to endpoint
-        bus.send(&Notification::new_templated(
+        bus.send(&Notification::from_template(
             Severity::Info,
-            "Title",
-            "Body",
+            "test",
             Default::default(),
             Default::default(),
         ));
@@ -661,10 +655,9 @@ mod tests {
         });
 
         let send_with_severity = |severity| {
-            let notification = Notification::new_templated(
+            let notification = Notification::from_template(
                 severity,
-                "Title",
-                "Body",
+                "test",
                 Default::default(),
                 Default::default(),
             );
diff --git a/proxmox-notify/src/matcher.rs b/proxmox-notify/src/matcher.rs
index 0f86445..d6051ac 100644
--- a/proxmox-notify/src/matcher.rs
+++ b/proxmox-notify/src/matcher.rs
@@ -456,7 +456,7 @@ mod tests {
         fields.insert("foo".into(), "bar".into());
 
         let notification =
-            Notification::new_templated(Severity::Notice, "test", "test", Value::Null, fields);
+            Notification::from_template(Severity::Notice, "test", Value::Null, fields);
 
         let matcher: FieldMatcher = "exact:foo=bar".parse().unwrap();
         assert!(matcher.matches(&notification).unwrap());
@@ -474,14 +474,14 @@ mod tests {
         fields.insert("foo".into(), "test".into());
 
         let notification =
-            Notification::new_templated(Severity::Notice, "test", "test", Value::Null, fields);
+            Notification::from_template(Severity::Notice, "test", Value::Null, fields);
         assert!(matcher.matches(&notification).unwrap());
 
         let mut fields = HashMap::new();
         fields.insert("foo".into(), "notthere".into());
 
         let notification =
-            Notification::new_templated(Severity::Notice, "test", "test", Value::Null, fields);
+            Notification::from_template(Severity::Notice, "test", Value::Null, fields);
         assert!(!matcher.matches(&notification).unwrap());
 
         assert!("regex:'3=b.*".parse::<FieldMatcher>().is_err());
@@ -489,13 +489,8 @@ mod tests {
     }
     #[test]
     fn test_severities() {
-        let notification = Notification::new_templated(
-            Severity::Notice,
-            "test",
-            "test",
-            Value::Null,
-            Default::default(),
-        );
+        let notification =
+            Notification::from_template(Severity::Notice, "test", Value::Null, Default::default());
 
         let matcher: SeverityMatcher = "info,notice,warning,error".parse().unwrap();
         assert!(matcher.matches(&notification).unwrap());
@@ -503,13 +498,8 @@ mod tests {
 
     #[test]
     fn test_empty_matcher_matches_always() {
-        let notification = Notification::new_templated(
-            Severity::Notice,
-            "test",
-            "test",
-            Value::Null,
-            Default::default(),
-        );
+        let notification =
+            Notification::from_template(Severity::Notice, "test", Value::Null, Default::default());
 
         for mode in [MatchModeOperator::All, MatchModeOperator::Any] {
             let config = MatcherConfig {
diff --git a/proxmox-notify/src/renderer/html.rs b/proxmox-notify/src/renderer/html.rs
index f794902..77545f7 100644
--- a/proxmox-notify/src/renderer/html.rs
+++ b/proxmox-notify/src/renderer/html.rs
@@ -5,7 +5,6 @@ use handlebars::{
 use serde_json::Value;
 
 use super::{table::Table, value_to_string};
-use crate::define_helper_with_prefix_and_postfix;
 use crate::renderer::BlockRenderFunctions;
 
 fn render_html_table(
@@ -79,22 +78,9 @@ fn render_object(
     Ok(())
 }
 
-define_helper_with_prefix_and_postfix!(verbatim_monospaced, "<pre>", "</pre>");
-define_helper_with_prefix_and_postfix!(heading_1, "<h1 style=\"font-size: 1.2em\">", "</h1>");
-define_helper_with_prefix_and_postfix!(heading_2, "<h2 style=\"font-size: 1em\">", "</h2>");
-define_helper_with_prefix_and_postfix!(
-    verbatim,
-    "<pre style=\"font-family: sans-serif\">",
-    "</pre>"
-);
-
 pub(super) fn block_render_functions() -> BlockRenderFunctions {
     BlockRenderFunctions {
         table: Box::new(render_html_table),
-        verbatim_monospaced: Box::new(verbatim_monospaced),
         object: Box::new(render_object),
-        heading_1: Box::new(heading_1),
-        heading_2: Box::new(heading_2),
-        verbatim: Box::new(verbatim),
     }
 }
diff --git a/proxmox-notify/src/renderer/mod.rs b/proxmox-notify/src/renderer/mod.rs
index e9f36e6..a51ece6 100644
--- a/proxmox-notify/src/renderer/mod.rs
+++ b/proxmox-notify/src/renderer/mod.rs
@@ -12,7 +12,7 @@ use serde_json::Value;
 use proxmox_human_byte::HumanByte;
 use proxmox_time::TimeSpan;
 
-use crate::Error;
+use crate::{context, Error};
 
 mod html;
 mod plaintext;
@@ -165,41 +165,47 @@ impl ValueRenderFunction {
     }
 }
 
-/// Available renderers for notification templates.
+/// Available template types
 #[derive(Copy, Clone)]
-pub enum TemplateRenderer {
-    /// Render to HTML code
-    Html,
-    /// Render to plain text
-    Plaintext,
+pub enum TemplateType {
+    /// HTML body template
+    HtmlBody,
+    /// Plaintext body template
+    PlaintextBody,
+    /// Plaintext body template
+    Subject,
 }
 
-impl TemplateRenderer {
-    fn prefix(&self) -> &str {
+impl TemplateType {
+    fn file_suffix(&self) -> &'static str {
         match self {
-            TemplateRenderer::Html => "<html>\n<body>\n",
-            TemplateRenderer::Plaintext => "",
+            TemplateType::HtmlBody => "body.html.hbs",
+            TemplateType::PlaintextBody => "body.txt.hbs",
+            TemplateType::Subject => "subject.txt.hbs",
         }
     }
 
-    fn postfix(&self) -> &str {
-        match self {
-            TemplateRenderer::Html => "\n</body>\n</html>",
-            TemplateRenderer::Plaintext => "",
+    fn postprocess(&self, mut rendered: String) -> String {
+        if let Self::Subject = self {
+            rendered = rendered.replace('\n', " ");
         }
+
+        rendered
     }
 
     fn block_render_fns(&self) -> BlockRenderFunctions {
         match self {
-            TemplateRenderer::Html => html::block_render_functions(),
-            TemplateRenderer::Plaintext => plaintext::block_render_functions(),
+            TemplateType::HtmlBody => html::block_render_functions(),
+            TemplateType::Subject => plaintext::block_render_functions(),
+            TemplateType::PlaintextBody => plaintext::block_render_functions(),
         }
     }
 
     fn escape_fn(&self) -> fn(&str) -> String {
         match self {
-            TemplateRenderer::Html => handlebars::html_escape,
-            TemplateRenderer::Plaintext => handlebars::no_escape,
+            TemplateType::PlaintextBody => handlebars::no_escape,
+            TemplateType::Subject => handlebars::no_escape,
+            TemplateType::HtmlBody => handlebars::html_escape,
         }
     }
 }
@@ -208,28 +214,20 @@ type HelperFn = dyn HelperDef + Send + Sync;
 
 struct BlockRenderFunctions {
     table: Box<HelperFn>,
-    verbatim_monospaced: Box<HelperFn>,
     object: Box<HelperFn>,
-    heading_1: Box<HelperFn>,
-    heading_2: Box<HelperFn>,
-    verbatim: Box<HelperFn>,
 }
 
 impl BlockRenderFunctions {
     fn register_helpers(self, handlebars: &mut Handlebars) {
         handlebars.register_helper("table", self.table);
-        handlebars.register_helper("verbatim", self.verbatim);
-        handlebars.register_helper("verbatim-monospaced", self.verbatim_monospaced);
         handlebars.register_helper("object", self.object);
-        handlebars.register_helper("heading-1", self.heading_1);
-        handlebars.register_helper("heading-2", self.heading_2);
     }
 }
 
 fn render_template_impl(
     template: &str,
     data: &Value,
-    renderer: TemplateRenderer,
+    renderer: TemplateType,
 ) -> Result<String, Error> {
     let mut handlebars = Handlebars::new();
     handlebars.register_escape_fn(renderer.escape_fn());
@@ -248,61 +246,45 @@ fn render_template_impl(
 
 /// Render a template string.
 ///
-/// The output format can be chosen via the `renderer` parameter (see [TemplateRenderer]
+/// The output format can be chosen via the `renderer` parameter (see [TemplateType]
 /// for available options).
 pub fn render_template(
-    renderer: TemplateRenderer,
+    mut ty: TemplateType,
     template: &str,
     data: &Value,
 ) -> Result<String, Error> {
-    let mut rendered_template = String::from(renderer.prefix());
+    let filename = format!("{template}-{suffix}", suffix = ty.file_suffix());
+
+    let template_string = context::context().lookup_template(&filename, None)?;
+
+    let (template_string, fallback) = match (template_string, ty) {
+        (None, TemplateType::HtmlBody) => {
+            ty = TemplateType::PlaintextBody;
+            let plaintext_filename = format!("{template}-{suffix}", suffix = ty.file_suffix());
+            log::info!("html template '{filename}' not found, falling back to plain text template '{plaintext_filename}'");
+            (
+                context::context().lookup_template(&plaintext_filename, None)?,
+                true,
+            )
+        }
+        (template_string, _) => (template_string, false),
+    };
 
-    rendered_template.push_str(&render_template_impl(template, data, renderer)?);
-    rendered_template.push_str(renderer.postfix());
+    let template_string = template_string.ok_or(Error::Generic(format!(
+        "could not load template '{template}'"
+    )))?;
 
-    Ok(rendered_template)
-}
+    let mut rendered = render_template_impl(&template_string, data, ty)?;
+    rendered = ty.postprocess(rendered);
 
-#[macro_export]
-macro_rules! define_helper_with_prefix_and_postfix {
-    ($name:ident, $pre:expr, $post:expr) => {
-        fn $name<'reg, 'rc>(
-            h: &Helper<'reg, 'rc>,
-            handlebars: &'reg Handlebars,
-            context: &'rc Context,
-            render_context: &mut RenderContext<'reg, 'rc>,
-            out: &mut dyn Output,
-        ) -> HelperResult {
-            use handlebars::Renderable;
-
-            let block_text = h.template();
-            let param = h.param(0);
-
-            out.write($pre)?;
-            match (param, block_text) {
-                (None, Some(block_text)) => {
-                    block_text.render(handlebars, context, render_context, out)
-                }
-                (Some(param), None) => {
-                    let value = param.value();
-                    let text = value.as_str().ok_or_else(|| {
-                        HandlebarsRenderError::new(format!("value {value} is not a string"))
-                    })?;
+    if fallback {
+        rendered = format!(
+            "<html><body><pre>{}</pre></body></html>",
+            handlebars::html_escape(&rendered)
+        );
+    }
 
-                    out.write(text)?;
-                    Ok(())
-                }
-                (Some(_), Some(_)) => Err(HandlebarsRenderError::new(
-                    "Cannot use parameter and template at the same time",
-                )),
-                (None, None) => Err(HandlebarsRenderError::new(
-                    "Neither parameter nor template was provided",
-                )),
-            }?;
-            out.write($post)?;
-            Ok(())
-        }
-    };
+    Ok(rendered)
 }
 
 #[cfg(test)]
@@ -310,73 +292,6 @@ mod tests {
     use super::*;
     use serde_json::json;
 
-    #[test]
-    fn test_render_template() -> Result<(), Error> {
-        let data = json!({
-            "dur": 12345,
-            "size": 1024 * 15,
-
-            "table": {
-                "schema": {
-                    "columns": [
-                        {
-                            "id": "col1",
-                            "label": "Column 1"
-                        },
-                        {
-                            "id": "col2",
-                            "label": "Column 2"
-                        }
-                    ]
-                },
-                "data": [
-                    {
-                        "col1": "val1",
-                        "col2": "val2"
-                    },
-                    {
-                        "col1": "val3",
-                        "col2": "val4"
-                    },
-                ]
-            }
-
-        });
-
-        let template = r#"
-{{heading-1 "Hello World"}}
-
-{{heading-2 "Hello World"}}
-
-{{human-bytes size}}
-{{duration dur}}
-
-{{table table}}"#;
-
-        let expected_plaintext = r#"
-Hello World
-===========
-
-Hello World
------------
-
-15 KiB
-3h 25min 45s
-
-Column 1    Column 2    
-val1        val2        
-val3        val4        
-"#;
-
-        let rendered_plaintext = render_template(TemplateRenderer::Plaintext, template, &data)?;
-
-        // Let's not bother about testing the HTML output, too fragile.
-
-        assert_eq!(rendered_plaintext, expected_plaintext);
-
-        Ok(())
-    }
-
     #[test]
     fn test_helpers() {
         assert_eq!(value_to_byte_size(&json!(1024)), Some("1 KiB".to_string()));
diff --git a/proxmox-notify/src/renderer/plaintext.rs b/proxmox-notify/src/renderer/plaintext.rs
index 437e412..59e917a 100644
--- a/proxmox-notify/src/renderer/plaintext.rs
+++ b/proxmox-notify/src/renderer/plaintext.rs
@@ -7,7 +7,6 @@ use handlebars::{
 use serde_json::Value;
 
 use super::{table::Table, value_to_string};
-use crate::define_helper_with_prefix_and_postfix;
 use crate::renderer::BlockRenderFunctions;
 
 fn optimal_column_widths(table: &Table) -> HashMap<&str, usize> {
@@ -76,40 +75,6 @@ fn render_plaintext_table(
     Ok(())
 }
 
-macro_rules! define_underlining_heading_fn {
-    ($name:ident, $underline:expr) => {
-        fn $name<'reg, 'rc>(
-            h: &Helper<'reg, 'rc>,
-            _handlebars: &'reg Handlebars,
-            _context: &'rc Context,
-            _render_context: &mut RenderContext<'reg, 'rc>,
-            out: &mut dyn Output,
-        ) -> HelperResult {
-            let param = h
-                .param(0)
-                .ok_or_else(|| HandlebarsRenderError::new("No parameter provided"))?;
-
-            let value = param.value();
-            let text = value.as_str().ok_or_else(|| {
-                HandlebarsRenderError::new(format!("value {value} is not a string"))
-            })?;
-
-            out.write(text)?;
-            out.write("\n")?;
-
-            for _ in 0..text.len() {
-                out.write($underline)?;
-            }
-            Ok(())
-        }
-    };
-}
-
-define_helper_with_prefix_and_postfix!(verbatim_monospaced, "", "");
-define_underlining_heading_fn!(heading_1, "=");
-define_underlining_heading_fn!(heading_2, "-");
-define_helper_with_prefix_and_postfix!(verbatim, "", "");
-
 fn render_object(
     h: &Helper,
     _: &Handlebars,
@@ -133,10 +98,6 @@ fn render_object(
 pub(super) fn block_render_functions() -> BlockRenderFunctions {
     BlockRenderFunctions {
         table: Box::new(render_plaintext_table),
-        verbatim_monospaced: Box::new(verbatim_monospaced),
-        verbatim: Box::new(verbatim),
         object: Box::new(render_object),
-        heading_1: Box::new(heading_1),
-        heading_2: Box::new(heading_2),
     }
 }
-- 
2.39.2




More information about the pve-devel mailing list