3 releases
0.1.3 | Jun 30, 2024 |
---|---|
0.1.2 | Jun 29, 2024 |
0.1.1 | Jun 29, 2024 |
#78 in Template engine
42KB
762 lines
String Templater
A little string templater library to write down some string templates and injecting datas in it.
Syntax
The syntax is easy and requires you to know only some rules.
- The
\
symbol followed by{
,}
or\
will always escape the next character, making\
ignored in the output. - To access the value of a key, you'll use the notation
{{my_key_name}}
. - The
*
symbol (mirror operator) will only work right after{{
and before the key name. - When parsing the key name, the symbol
\
followed by*
will result in the character*
being outputed. - Escaping the
*
symbol is only useful right after a{{
. - The mirror operator can be applied as many times as it exist on the key name.
- To use the value of a key as a template, you'll use the notation
{{{my_key_name}}}
. - You can have as many template as you want inside other template, but be careful of infinite loops.
- The path for key names when serializing datas look as close as
JS
accessors as possible (with the exception of array). - To access an array index, use the dot notation instead of the bracket notation from
JS
(ex:my_field.2.name
).
How to use
Create your object and define it's fields.
use serde::Serialize;
#[derive(Debug, Serialize)]
struct Person {
pub name: String,
pub child: Option<Box<Person>>,
}
The object will be used specifically for data injection, so if you have optional data, make sure it has some data.
In the None
case, a StringTemplaterError::UnknownField
will be returned.
For problematic curvy bracket, you'll get a StringTemplaterError::MissingCurvyBracket
error and in the case of a serialize error, a StringTemplaterError::SerializeError
will be returned.
Now, write down some string template and enjoy:
use string_templater::parse;
fn main() {
let someone = Person {
name: "Roger".to_string(),
child: Some(Box::new(Person {
name: "Betty".to_string(),
child: Some(Box::new(Person {
name: "Kenny".to_string(),
child: None,
})),
})),
};
let result = parse("Hello {{name}}! Is {{child.name}} your child? So your grandchild must be {{child.child.name}}.", &someone).unwrap();
println!("{}", result); // Display : Hello Roger! Is Betty your child? So your grandchild must be Kenny.
}
It support JSON
object and array for the path notation, making templating text easy.
use serde::Serialize;
use string_templater::parse;
...
#[derive(Debug, Serialize)]
struct Classroom {
pub students: Vec<Child>,
}
#[derive(Debug, Serialize)]
struct Child {
pub name: String,
}
...
let classroom = Classroom {
students: vec![
Child {
name: "john".to_string(),
},
Child {
name: "janne".to_string(),
},
Child {
name: "alice".to_string(),
},
Child {
name: "bob".to_string(),
},
]
};
let template_str = "You should be {{students.2.name}}.";
println!("{}", parse(template_str, &classroom).unwrap()); // Display : You should be alice.
Mirroring datas
For some reason, it might be possible that you need to access the value of a key in the hashmap based on the value of a field. Here, we're calling this behaviour mirroring since it's similar to a laser pointing towards a mirror then being redirected.
First of, let's create a set of datas to show mirroring in action (you can also parse a structure if you want, but it'll be faster to just think of our final datas instead of the full structure).
use std::collections::HashMap;
use string_templater::generate;
...
let mut data: HashMap<String, String> = HashMap::new();
data.insert("name".to_string(), "Doe".to_string());
data.insert("age".to_string(), "35".to_string());
data.insert("key_name".to_string(), "age".to_string());
To mirror a value, you'll need to use the *
symbol before the path of your variable. This will use the value of the selected key as a new key to search a value. You can also use multiple mirroring into another using multiples *
.
You can see what it does in the following example:
let template_str = "Hello {{name}}! Is it true that you're {{*key_name}} years old?";
println!("{}", generate(template_str, &data).unwrap()); // Display : Hello Doe! Is it true that you're 35 years old?
Nested templates
There might be reasons where we would like to use multiple templates, nested inside each other. For this very reason, you'll need to write down your string interpolation using the triple curly bracket notation.
use std::collections::HashMap;
use string_templater::generate;
...
let mut data: HashMap<String, String> = HashMap::new();
data.insert("time".to_string(), "today".to_string());
data.insert("name".to_string(), "Doe".to_string());
data.insert("beers_count".to_string(), "2".to_string());
data.insert(
"dialog".to_string(),
"I only have {{beers_count}} beers for you {{time}}".to_string(),
);
data.insert(
"bye".to_string(),
"Well, bye {{name}}! {{{polite}}}".to_string(),
);
data.insert(
"polite".to_string(),
"Enjoy your {{beers_count}} beers.".to_string(),
);
let template_str = "Hello {{name}}! {{{dialog}}}... {{{bye}}}";
println!("{}", generate(template_str, &data).unwrap()); // Display : Hello Doe! I only have 2 beers for you today... Well, bye Doe! Enjoy your 2 beers.
Nested templates works with mirroring so you can use a mirroring template using {{{*my_key}}}
pattern.
Be warned: you need to be careful when using nesting with templates to avoid creating an infinite loop by searching a template in a template calling an already called template.
Options
You can use generate_with_options
and parse_with_options
if you want to modify the way the template generation behaves.
By default generate
and parse
have safe_parse
set to false
, so to use options you must enable this boolean.
StringTemplaterOptions
also contains the fields display_missing_keys
and override_missing_keys
, thoses are used when safe_parse
in StringTemplaterOptions
is set to true
.
You can now choose to display the missing keys, and if you do, you can override the default display using the override_missing_keys
with some custom text generated by your function.
use std::collections::HashMap;
use string_templater::{generate_with_options, StringTemplaterOptions};
...
let mut data: HashMap<String, String> = HashMap::new();
data.insert("name".to_string(), "Doe".to_string());
data.insert("age".to_string(), "35".to_string());
let template_str = "Hello {{name}}! Is it true that you're {{current_age}} years old?";
println!(
"{}",
generate_with_options(
template_str,
&data,
&StringTemplaterOptions {
safe_parse: true,
display_missing_keys: false,
override_missing_keys: None,
},
)
.unwrap()
); // Display : Hello Doe! Is it true that you're years old?
println!(
"{}",
generate_with_options(
template_str,
&data,
&StringTemplaterOptions {
safe_parse: true,
display_missing_keys: true,
override_missing_keys: Some(Box::new(move |s| format!("[key `{}` is missing]", s))),
},
)
.unwrap()
); // Display : Hello Doe! Is it true that you're [key `current_age` is missing] years old?
Parse to HashMap
It may be possible that you need to transform your datas into a hashmap to do something on it before consuming it.
For this very reason, you can access the parse_to_hashmap
function.
fn parse_to_hashmap<T: ?Sized + Serialize>(value: &T) -> Result<HashMap<String, String>, StringTemplaterError>;
The keys of the hashmap contain the parent/child field path of a value using the dot notation.
Template builder
You could also simply use the template builder to handle your datas. Here's an example of the template builder in action:
let mut template_builder = TemplateBuilder::new();
template_builder.insert("name", "Doe");
template_builder.insert("surname", "Doey");
let result: String = template_builder.build("Hello {{name}}! Or should I call you {{surname}}?").unwrap();
There's multiple methods available in the builder to simplify the work for you.
Dependencies
~0.7–1.6MB
~35K SLoC