From: Lukas Wagner <l.wagner@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH proxmox v2 01/20] notify: switch to file-based templating system
Date: Fri, 19 Apr 2024 16:17:04 +0200 [thread overview]
Message-ID: <20240419141723.377507-2-l.wagner@proxmox.com> (raw)
In-Reply-To: <20240419141723.377507-1-l.wagner@proxmox.com>
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@proxmox.com>
Tested-by: Folke Gleumes <f.gleumes@proxmox.com>
Reviewed-by: Fiona Ebner <f.ebner@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 ¬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
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
next prev parent reply other threads:[~2024-04-19 14:19 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-04-19 14:17 [pve-devel] [PATCH many v2 00/20] notifications: move template strings to template files; PBS preparations Lukas Wagner
2024-04-19 14:17 ` Lukas Wagner [this message]
2024-04-19 14:17 ` [pve-devel] [PATCH proxmox v2 02/20] notify: make api methods take config struct ownership Lukas Wagner
2024-04-19 14:17 ` [pve-devel] [PATCH proxmox v2 03/20] notify: convert Option<Vec<T>> -> Vec<T> in config structs Lukas Wagner
2024-04-19 14:17 ` [pve-devel] [PATCH proxmox v2 04/20] notify: don't make tests require pve-context Lukas Wagner
2024-04-19 14:17 ` [pve-devel] [PATCH proxmox v2 05/20] notify: make the `mail-forwarder` feature depend on proxmox-sys Lukas Wagner
2024-04-19 14:17 ` [pve-devel] [PATCH proxmox v2 06/20] notify: cargo.toml: add spaces before curly braces Lukas Wagner
2024-04-19 14:17 ` [pve-devel] [PATCH proxmox v2 07/20] notify: give each notification a unique ID Lukas Wagner
2024-04-19 14:17 ` [pve-devel] [PATCH proxmox v2 08/20] notify: api: add get_targets Lukas Wagner
2024-04-19 14:17 ` [pve-devel] [PATCH proxmox v2 09/20] notify: derive `api` for Deleteable*Property Lukas Wagner
2024-04-19 14:17 ` [pve-devel] [PATCH proxmox v2 10/20] notify: derive Deserialize/Serialize for Notification struct Lukas Wagner
2024-04-19 14:17 ` [pve-devel] [PATCH proxmox v2 11/20] notify: pbs context: include nodename in default sendmail author Lukas Wagner
2024-04-19 14:17 ` [pve-devel] [PATCH proxmox v2 12/20] notify: renderer: add relative-percentage helper from PBS Lukas Wagner
2024-04-19 14:17 ` [pve-devel] [PATCH proxmox-perl-rs v2 13/20] notify: use file based notification templates Lukas Wagner
2024-04-19 14:17 ` [pve-devel] [PATCH proxmox-perl-rs v2 14/20] notify: don't pass config structs by reference Lukas Wagner
2024-04-19 14:17 ` [pve-devel] [PATCH proxmox-perl-rs v2 15/20] notify: adapt to Option<Vec<T>> to Vec<T> changes in proxmox_notify Lukas Wagner
2024-04-19 14:17 ` [pve-devel] [PATCH cluster v2 16/20] notify: use named template instead of passing template strings Lukas Wagner
2024-04-19 14:17 ` [pve-devel] [PATCH pve-ha-manager v2 17/20] env: notify: use named templates " Lukas Wagner
2024-04-19 14:17 ` [pve-devel] [PATCH manager v2 18/20] gitignore: ignore any test artifacts Lukas Wagner
2024-04-19 14:17 ` [pve-devel] [PATCH manager v2 19/20] tests: remove vzdump_notification test Lukas Wagner
2024-04-19 14:17 ` [pve-devel] [PATCH manager v2 20/20] notifications: use named templates instead of in-code templates Lukas Wagner
2024-04-23 22:31 ` [pve-devel] partially-applied-series: [PATCH many v2 00/20] notifications: move template strings to template files; PBS preparations Thomas Lamprecht
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20240419141723.377507-2-l.wagner@proxmox.com \
--to=l.wagner@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal