lib_humus/
template_loader.rs

1
2use log::{info,error};
3use tera::Tera;
4use serde::Deserialize;
5
6use std::error::Error;
7use std::fmt;
8
9use crate::HumusProtoEngine;
10use crate::read_toml_from_file;
11use crate::TomlError;
12
13/// Helps loading Templates from disk along with a handy configuration file.
14///
15/// Templates should be placed in a flat file structure in one directory.
16/// They may be accompanied by an `extra.toml` file.
17/// (by default, the location is configurable)
18///
19/// It also helps out a bit with deriving template realted configuration
20/// for the rest of whatever you are building.
21/// 
22/// Example:
23/// ```rust,ignore
24/// use lib_humus::TemplateEngineLoader;
25/// 
26/// let template_loader = TemplateEngineLoader::new(
27/// 	config.template.template_location.clone(),
28/// 	config.template.extra_config.clone()
29/// )
30/// 	.cli_template_location(cli_args.template_location)
31/// 	.cli_extra_config_location(cli_args.extra_config);
32/// 	
33/// 
34/// let templating_engine = match template_loader.load_templates() {
35/// 	Ok(t) => t.into(),
36/// 	Err(e) => {
37/// 		println!("{e}");
38/// 		::std::process::exit(1);
39/// 	}
40/// };
41/// ```
42///
43#[derive(Debug, Clone, Deserialize)]
44pub struct TemplateEngineLoader {
45	/// The path to the directory where the templates are.
46	pub template_location: String,
47	/// The path to the extra configuration
48	/// (relative to the current pwd, not to the templates)
49	pub extra_config_location: Option<String>,
50}
51
52impl TemplateEngineLoader {
53
54	/// Creates a new `TemplateEngineLoader` with minimal typing.
55	pub fn new(
56		template_location: String,
57		extra_config_location: Option<String>
58	) -> Self {
59		Self {
60			template_location: template_location,
61			extra_config_location: extra_config_location,
62		}
63	}
64
65	/// Overrides the template location with a new location if it is set.
66	///
67	/// Intended for processing cli-options.
68	pub fn cli_template_location(mut self, location: Option<String>) -> Self {
69		if let Some(location) = location {
70			self.template_location = location;
71		}
72		self
73	}
74
75	/// Overrides the extra configuration location with a new location if it is set.
76	///
77	/// Intended for processing cli-options.
78	pub fn cli_extra_config_location(mut self, location: Option<String>) -> Self {
79		if let Some(location) = location {
80			self.extra_config_location = Some(location);
81		}
82		self
83	}
84
85	/// Returns the template base directory.
86	///
87	/// Contructed by ensuring the template_location ends in a "/".
88	pub fn base_dir(&self) -> String {
89		if self.template_location.ends_with("/") {
90			self.template_location.clone()
91		} else {
92			self.template_location.clone()+"/"
93		}
94	}
95
96	/// Initialize a [HumusProtoEngine] with the given templates and extra configuration.
97	///
98	/// Failure Modes:
99	/// * The `extra.toml` was not found and the path was explicitly set.
100	/// * The `extra.toml` was found and is not valid toml.
101	/// * The `extra.toml` passes, but tera finds an error in the templates.
102	///
103	/// If `extra_config_location` is `None` no error is returned if the `extra.toml`
104	/// was not found as the template might not require one.
105	/// 
106	/// [HumusProtoEngine]: ./struct.HumusProtoEngine.html
107	pub fn load_templates(
108		&self
109	) -> Result<HumusProtoEngine,TemplateEngineLoaderError> {
110		let template_base_dir = self.base_dir();
111		let template_extra_config_res = match &self.extra_config_location {
112			Some(path) => read_toml_from_file(path),
113			None => {
114				read_toml_from_file(&(template_base_dir.clone()+"extra.toml"))
115			}
116		};
117		let template_extra_config = match template_extra_config_res {
118			Ok(c) => Some(c),
119			Err(e) => match &e {
120				TomlError::FileError{..} => {
121					// Only fatal if the file was explicitly requested.
122					// An implicit request could also mean that
123					// the template doesn't need a config file.
124					if self.extra_config_location.is_some() {
125						return Err(TemplateEngineLoaderError::TomlError(e));
126					}
127					None
128				},
129				TomlError::ParseError{..} => {
130					return Err(TemplateEngineLoaderError::TomlError(e));
131				}
132			},
133		};
134		let template_glob = template_base_dir.clone()+"*";
135		info!("Parsing Templates from '{}' ...", &template_glob);
136		let res = Tera::new((template_glob).as_str());
137		let tera = match res {
138			Ok(t) => t,
139			Err(e) => {
140				error!("Error Parsing Template: {e}");
141				return Err(TemplateEngineLoaderError::TemplateParseError{
142					path: template_glob,
143					tera_error: e,
144				});
145			}
146		};
147		Ok(HumusProtoEngine {
148			tera: tera,
149			template_config: template_extra_config,
150		})
151	}
152}
153
154/// Returned when loading a template using the [TemplateEngineLoader] fails.
155///
156/// [TemplateEngineLoader]: ./struct.TemplateEngineLoader.html
157#[derive(Debug)]
158pub enum TemplateEngineLoaderError {
159	/// An error occourred while loading the template configuration file.
160	TomlError(TomlError),
161	/// An error occourred while parsing the templates.
162	TemplateParseError{
163		/// Path to the template directory
164		path: String,
165		/// what went wrong
166		tera_error: tera::Error
167	},
168}
169
170impl fmt::Display for TemplateEngineLoaderError {
171	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
172		match self {
173			Self::TomlError(e) =>
174				write!(f,"Error with template extra configuration:\n{e}"),
175			Self::TemplateParseError{path, tera_error} =>
176				write!(f,"Error parsing template '{path}':\n{tera_error}"),
177		}
178	}
179}
180
181impl Error for TemplateEngineLoaderError {
182    fn source(&self) -> Option<&(dyn Error + 'static)> {
183        match self {
184			Self::TomlError(error) =>
185				Some(error),
186			Self::TemplateParseError{tera_error, ..} =>
187				Some(tera_error),
188        }
189    }
190}