lib_humus/
template_loader.rs

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