rubble_templates/
lib.rs

1//! General purpose API for rubble-templates.
2//!
3//! Allows to quickly compile a text output of out a template.
4//!
5//! ### Syntax
6//!
7//! By default, rubble-templates while parsing a template looks for all blocks starting `{{` and ending with `}}` those are marked as an evaluation spots with code that can be evaluated by an `Evaluator`.
8//!
9//! Given the following example:
10//! ```text
11//! Hello there, {{ name }}!
12//! ```
13//!
14//! This template contains three parts:
15//! * `Hello there, ` - raw text
16//! * `{{ name }}` - code
17//! * `!` - raw text
18//!
19//! The second part will be passed to a given `Evaluator` by the `Compiler` in order to get a String output.
20//! The code fragment will be substituted with the output.
21//!
22//! The rubble-templates library can also evaluate more sophisticated code.
23//! You can pass your own functions that can enrich the template.
24//!
25//! This library uses Lisp-like syntax during evaluation.
26//! All function calls look like the following:
27//! ```text
28//! function_name arg0 arg1 arg2
29//! ```
30//!
31//! To call a `plus` function (that can be hypothetically implemented) to calculate the result of 2 + 2 you will need to write
32//! ```text
33//! The result is: {{ plus 2 2 }}
34//! ```
35//!
36//! The parameters can also be grouped using parenthesis. This can be helpful in certain cases.
37//! For example, given `plus` and `multiply` functions, you will need to use the following code to calculate (1 + 2) * 3:
38//!
39//! ```text
40//! The result is: {{ multiply (plus 1 2) 3 }}
41//! ```
42//!
43//! ### Sample
44//! ```rust
45//! use rubble_templates::std_fun::std_functions;
46//! use std::collections::HashMap;
47//!
48//! use rubble_templates::compile_template_from_string;
49//!
50//! let template = "{{ hello }}. 2 + 2 = {{ + 2 2 }}".to_string();
51//!
52//! let mut variables: HashMap<String, String> = HashMap::new();
53//! variables.insert("hello".to_string(), "Hello world!".to_string());
54//!
55//! let result = compile_template_from_string(template, variables, std_functions());
56//!
57//! assert_eq!(
58//!     result.ok(),
59//!     Some("Hello world!. 2 + 2 = 4".to_string())
60//! );
61//! ```
62//!
63//! ### Customizing
64//!
65//! Compilation contains three phases:
66//! * `parsing` - done by the template iterator, which can extract template parts (eg. raw text, code, etc) that can be further interpreted,
67//! * `evaluation` - done by the [`Evaluator`](rubble_templates_core::evaluator::Evaluator), which evaluates all code found by the iterator,
68//! * `compiling` - done by the [`Compiler`](rubble_templates_core::compiler::Compiler), which uses iterator to parse the content, feeds the [`Evaluator`](rubble_templates_core::evaluator::Evaluator) and then joins everything into output text.
69//!
70//! You can implement your own iterators, evaluators or compilers.
71//! To modify the compilation process, you just need to use your own trait implementations instead of the default ones.
72//!
73//! To show you how it works, here is what [`compile_template_from_string`] does in practice:
74//!
75//! ```rust
76//! use std::collections::HashMap;
77//! use rubble_templates::std_fun::std_functions;
78//! use rubble_templates_evaluators::simple::template::Template;
79//! use rubble_templates_evaluators::simple::evaluator::SimpleEvaluationEngine;
80//! use rubble_templates_evaluators::simple::compiler::TemplateCompiler;
81//! use rubble_templates_core::compiler::Compiler;
82//! use rubble_templates_core::evaluator::Context;
83//!
84//! // compile_template_from_string parameters
85//! let raw_input = "2 + 3 = {{ + 2 3 }}".to_string();
86//! let functions = std_functions();
87//! let variables = HashMap::new();
88//!
89//! // compilation process
90//! let template = Template::from(raw_input);
91//! let engine = SimpleEvaluationEngine::from(functions);
92//! let compiler = TemplateCompiler::new(engine);
93//!
94//! // the result
95//! let result = compiler.compile(&template, Context::with_variables(variables));
96//!
97//! assert_eq!(result.unwrap(), "2 + 3 = 5".to_string());
98//! ```
99
100use std::path::PathBuf;
101use std::collections::HashMap;
102use rubble_templates_core::evaluator::{Function, Context};
103use std::error::Error;
104use rubble_templates_core::compiler::{CompilationError, Compiler};
105use rubble_templates_evaluators::simple::template::Template;
106use rubble_templates_evaluators::simple::evaluator::SimpleEvaluationEngine;
107use rubble_templates_evaluators::simple::compiler::TemplateCompiler;
108
109pub mod std_fun;
110
111/// Compiles template from file.
112///
113/// This function reads a file and uses supplied variables and functions to compile a template.
114/// It is a quick way to get a compiled template, as it initializes Engine and Compiler with each invocation.
115///
116/// For some special cases consider using [SimpleEvaluationEngine], [TemplateCompiler] or other specific
117/// [Evaluator](rubble_templates_core::evaluator::Evaluator) and [Compiler] traits implementations.
118///
119/// Template can look like the following: `Some template {{ variable }} - or something`.
120/// Code that will be evaluated should be put between `{{` and `}}`.
121pub fn compile_template_from_file(file: PathBuf, variables: HashMap<String, String>, functions: HashMap<String, Box<dyn Function>>) -> Result<String, Box<dyn Error>> {
122    let template = Template::read_from(&file)?;
123
124    compile_template_from(template, variables, functions)
125        .map_err(|error| Box::new(error) as Box<dyn Error>)
126}
127
128/// Compiles template from String.
129///
130/// It creates a [Template] instance on the fly and then compiles it.
131///
132/// For some special cases consider using [SimpleEvaluationEngine], [TemplateCompiler] or other specific
133/// [Evaluator](rubble_templates_core::evaluator::Evaluator) and [Compiler] traits implementations.
134///
135/// Template can look like the following: `Some template {{ variable }} - or something`.
136/// Code that will be evaluated should be put between `{{` and `}}`.
137pub fn compile_template_from_string(template: String, variables: HashMap<String, String>, functions: HashMap<String, Box<dyn Function>>) -> Result<String, CompilationError> {
138    compile_template_from(Template::from(template), variables, functions)
139}
140
141/// Compiles template from [Template].
142///
143/// For some special cases consider using [SimpleEvaluationEngine], [TemplateCompiler] or other specific
144/// [Evaluator](rubble_templates_core::evaluator::Evaluator) and [Compiler] traits implementations.
145///
146/// Template can look like the following: `Some template {{ variable }} - or something`.
147/// Code that will be evaluated should be put between `{{` and `}}`.
148pub fn compile_template_from(template: Template, variables: HashMap<String, String>, functions: HashMap<String, Box<dyn Function>>) -> Result<String, CompilationError> {
149    let engine = SimpleEvaluationEngine::from(functions);
150    let compiler = TemplateCompiler::new(engine);
151
152    compiler.compile(&template, Context::with_variables(variables))
153}
154
155#[cfg(test)]
156mod tests {
157    use crate::compile_template_from_file;
158    use std::path::PathBuf;
159    use std::collections::HashMap;
160    use rubble_templates_core::evaluator::Function;
161    use rubble_templates_core::functions::SimpleFunction;
162
163    #[test]
164    fn should_compile_template() {
165        let file = PathBuf::from("test-assets/complex-template");
166        let mut functions: HashMap<String, Box<dyn Function>> = HashMap::new();
167        functions.insert("plus".to_string(), SimpleFunction::new(plus_function));
168
169        let mut variables: HashMap<String, String> = HashMap::new();
170        variables.insert("hello".to_string(), "Hello world!".to_string());
171
172        let result = compile_template_from_file(file, variables, functions);
173
174        assert_eq!(result.ok(), Some("Some template. Hello world!.\n\nThis shows a function evaluation usage example:\n2 + 2 = 4".to_string()));
175    }
176
177    fn plus_function(parameters: &[String]) -> String {
178        parameters.iter()
179                .map(|param|
180                    param.parse::<i32>().unwrap()
181                )
182                .sum::<i32>()
183                .to_string()
184    }
185}
186