[pve-devel] [PATCH proxmox 01/19] notify: switch to file-based templating system
Lukas Wagner
l.wagner at proxmox.com
Tue Apr 9 15:25:37 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>
---
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..321f6f9 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/proxmox-ve/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 90ae959..4c1f9e0 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<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(¬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::<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(¬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, "<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