From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 7BC071FF380 for ; Fri, 19 Apr 2024 16:19:25 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 4B095B569; Fri, 19 Apr 2024 16:18:29 +0200 (CEST) From: Lukas Wagner To: pve-devel@lists.proxmox.com Date: Fri, 19 Apr 2024 16:17:04 +0200 Message-Id: <20240419141723.377507-2-l.wagner@proxmox.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240419141723.377507-1-l.wagner@proxmox.com> References: <20240419141723.377507-1-l.wagner@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.001 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pve-devel] [PATCH proxmox v2 01/20] notify: switch to file-based templating system X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox VE development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pve-devel-bounces@lists.proxmox.com Sender: "pve-devel" 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 Tested-by: Folke Gleumes Reviewed-by: Fiona Ebner --- 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; - // 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, 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, 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 { 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, 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, 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 ¬ification.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 ¬ification.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 ¬ification.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>( + pub fn from_template>( severity: Severity, - title: S, - body: S, + template_name: S, template_data: Value, fields: HashMap, ) -> 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(¬ification).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(¬ification).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(¬ification).unwrap()); assert!("regex:'3=b.*".parse::().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(¬ification).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, "
", "
"); -define_helper_with_prefix_and_postfix!(heading_1, "

", "

"); -define_helper_with_prefix_and_postfix!(heading_2, "

", "

"); -define_helper_with_prefix_and_postfix!( - verbatim, - "
",
-    "
" -); - 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 => "\n\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\n", - 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, - verbatim_monospaced: Box, object: Box, - heading_1: Box, - heading_2: Box, - verbatim: Box, } 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 { 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 { - 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!( + "
{}
", + 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 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel