1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173

use tera::Tera;
use serde::Deserialize;

use std::fmt;

use crate::HumusProtoEngine;
use crate::read_toml_from_file;
use crate::TomlError;

/// Helps loading Templates from disk along with a handy configuration file.
///
/// Templates should be placed in a flat file structure in one directory.
/// They may be accompanied by an `extra.toml` file.
/// (by default, the location is configurable)
///
/// It also helps out a bit with deriving template realted configuration
/// for the rest of whatever you are building.
/// 
/// Example:
/// ```
/// let template_loader = TemplateEngineLoader::new(
/// 	config.template.template_location.clone(),
/// 	config.template.extra_config.clone()
/// )
/// 	.cli_template_location(cli_args.template_location)
/// 	.cli_extra_config_location(cli_args.extra_config);
/// 	
/// 
/// let templating_engine = match template_loader.load_templates() {
/// 	Ok(t) => t.into(),
/// 	Err(e) => {
/// 		println!("{e}");
/// 		::std::process::exit(1);
/// 	}
/// };
/// ```
///
#[derive(Deserialize, Clone)]
pub struct TemplateEngineLoader {
	/// The path to the directory where the templates are.
	pub template_location: String,
	/// The path to the extra configuration
	/// (relative to the current pwd, not to the templates)
	pub extra_config_location: Option<String>,
}

impl TemplateEngineLoader {

	/// Creates a new `TemplateEngineLoader` with minimal typing.
	pub fn new(
		template_location: String,
		extra_config_location: Option<String>
	) -> Self {
		Self {
			template_location: template_location,
			extra_config_location: extra_config_location,
		}
	}

	/// Overrides the template location with a new location if it is set.
	///
	/// Intended for processing cli-options.
	pub fn cli_template_location(mut self, location: Option<String>) -> Self {
		if let Some(location) = location {
			self.template_location = location;
		}
		self
	}

	/// Overrides the extra configuration location with a new location if it is set.
	///
	/// Intended for processing cli-options.
	pub fn cli_extra_config_location(mut self, location: Option<String>) -> Self {
		if let Some(location) = location {
			self.extra_config_location = Some(location);
		}
		self
	}

	/// Returns the template base directory.
	///
	/// Contructed by ensuring the template_location ends in a "/".
	pub fn base_dir(&self) -> String {
		if self.template_location.ends_with("/") {
			self.template_location.clone()
		} else {
			self.template_location.clone()+"/"
		}
	}

	/// Initialize a [HumusProtoEngine] with the given templates and extra configuration.
	///
	/// Failure Modes:
	/// * The `extra.toml` was not found and the path was explicitly set.
	/// * The `extra.toml` was found and is not valid toml.
	/// * The `extra.toml` passes, but tera finds an error in the templates.
	///
	/// If `extra_config_location` is `None` no error is returned if the `extra.toml`
	/// was not found as the template might not require one.
	/// 
	/// [HumusProtoEngine]: ./struct.HumusProtoEngine.html
	pub fn load_templates(
		&self
	) -> Result<HumusProtoEngine,TemplateEngineLoaderError> {
		let template_base_dir = self.base_dir();
		let template_extra_config_res = match &self.extra_config_location {
			Some(path) => read_toml_from_file(path),
			None => {
				read_toml_from_file(&(template_base_dir.clone()+"extra.toml"))
			}
		};
		let template_extra_config = match template_extra_config_res {
			Ok(c) => Some(c),
			Err(e) => match &e {
				TomlError::FileError{..} => {
					// Only fatal if the file was explicitly requested.
					// An implicit request could also mean that
					// the template doesn't need a config file.
					if self.extra_config_location.is_some() {
						return Err(TemplateEngineLoaderError::TomlError(e));
					}
					None
				},
				TomlError::ParseError{..} => {
					return Err(TemplateEngineLoaderError::TomlError(e));
				}
			},
		};
		let template_glob = template_base_dir.clone()+"*";
		println!("Parsing Templates from '{}' ...", &template_glob);
		let res = Tera::new((template_glob).as_str());
		let tera = match res {
			Ok(t) => t,
			Err(e) => {
				return Err(TemplateEngineLoaderError::TemplateParseError{
					path: template_glob,
					tera_error: e,
				});
			}
		};
		Ok(HumusProtoEngine {
			tera: tera,
			template_config: template_extra_config,
		})
	}
}

/// Returned when loading a template using the [TemplateEngineLoader] fails.
///
/// [TemplateEngineLoader]: ./struct.TemplateEngineLoader.html
pub enum TemplateEngineLoaderError {
	/// An error occourred while loading the template configuration file.
	TomlError(TomlError),
	/// An error occourred while parsing the templates.
	TemplateParseError{
		/// Path to the template directory
		path: String,
		/// what went wrong
		tera_error: tera::Error
	},
}

impl fmt::Display for TemplateEngineLoaderError {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		match self {
			Self::TomlError(e) =>
				write!(f,"Error with template extra configuration:\n{e}"),
			Self::TemplateParseError{path, tera_error} =>
				write!(f,"Error parsing template '{path}':\n{tera_error}"),
		}
	}
}