Skip to content

Commit

Permalink
add template
Browse files Browse the repository at this point in the history
  • Loading branch information
DougAnderson444 committed Nov 4, 2024
1 parent dfd4dd0 commit a70728c
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ pub use error::Error;
pub mod pest;

mod plugins;
mod template;
mod utils;
122 changes: 122 additions & 0 deletions src/template.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use std::collections::HashMap;

Check failure on line 1 in src/template.rs

View workflow job for this annotation

GitHub Actions / Check

unused import: `std::collections::HashMap`

Check failure on line 1 in src/template.rs

View workflow job for this annotation

GitHub Actions / Clippy

unused import: `std::collections::HashMap`

use rhai::Dynamic;

/// Holds the template parts (Static and Dynamic).
///
/// Renders the template with the provided values.
#[derive(Debug, Clone, PartialEq)]
pub struct Template {
pub(crate) parts: Vec<TemplatePart>,
}

/// Represents a part of the template.
///
/// Static parts are just strings.
/// Dynamic parts are placeholders that will be replaced with values.
#[derive(Debug, Clone, PartialEq)]
pub enum TemplatePart {
Static(String),

Check failure on line 19 in src/template.rs

View workflow job for this annotation

GitHub Actions / Check

variants `Static` and `Dynamic` are never constructed

Check failure on line 19 in src/template.rs

View workflow job for this annotation

GitHub Actions / Clippy

variants `Static` and `Dynamic` are never constructed
Dynamic(String),
}

impl Template {
/// Create a new Template from the provided string.
pub(crate) fn new(template: &str) -> Self {

Check failure on line 25 in src/template.rs

View workflow job for this annotation

GitHub Actions / Check

associated items `new` and `render` are never used

Check failure on line 25 in src/template.rs

View workflow job for this annotation

GitHub Actions / Clippy

associated items `new` and `render` are never used
let mut parts = Vec::new();
let mut current = String::new();

let mut chars = template.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '{' && chars.peek() == Some(&'{') {
chars.next(); // consume second '{'
if !current.is_empty() {
parts.push(TemplatePart::Static(current));
current = String::new();
}
while let Some(ch) = chars.next() {
if ch == '}' && chars.peek() == Some(&'}') {
chars.next(); // consume second '}'
break;
}
current.push(ch);
}
parts.push(TemplatePart::Dynamic(current.trim().to_string()));
current = String::new();
} else {
current.push(ch);
}
}

if !current.is_empty() {
parts.push(TemplatePart::Static(current));
}

Template { parts }
}

/// Render the template with the provided values.
///
/// Pass it an object that implements Iterator so it can perform lookups on the values given
/// the key.
pub(crate) fn render<'a>(
&self,
mut values: impl Iterator<Item = (&'a str, bool, &'a Dynamic)>,
) -> String {
let mut result = String::new();
for part in &self.parts {
match part {
TemplatePart::Static(s) => result.push_str(s),
TemplatePart::Dynamic(key) => {
if let Some(value) =
values.find_map(
|(k, _, v)| {
if k == key {
Some(v.to_string())
} else {
None
}
},
)
{
result.push_str(&value);
} else {
result.push_str(&format!("{{{{{}}}}}", key));
}
}
}
}
result
}
}

#[cfg(test)]
mod tests {
use std::str::FromStr as _;

use super::*;

#[test]
fn test_template() {
let template = Template::new("This {{word_var}} is replaced with {{a_value}}");
let mut values = HashMap::new();
values.insert(
"word_var".to_string(),
Dynamic::from_str("template").unwrap(),
);
values.insert("a_value".to_string(), Dynamic::from_str("content").unwrap());

let result = template.render(values.iter().map(|(k, v)| (k.as_str(), false, v)));
assert_eq!(result, "This template is replaced with content");
}

// handle the case where a placeholder in the template doesn't have a corresponding value in the provided HashMap.
#[test]
fn test_missing_value() {
let template = Template::new("This {{word_var}} is replaced with {{a_value}}");
let values: HashMap<_, Dynamic> = HashMap::<String, rhai::Dynamic>::new();

let result = template.render(values.iter().map(|(k, v)| (k.as_str(), false, v)));
assert_eq!(result, "This {{word_var}} is replaced with {{a_value}}");
}
}

0 comments on commit a70728c

Please sign in to comment.