Skip to main content

lib_humus/
humus_engine_loader.rs

1// SPDX-FileCopyrightText: 2026 Slatian <baschdel@disroot.org>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5use log::error;
6use log::warn;
7use tera::Tera;
8
9use std::marker::PhantomData;
10use std::path::PathBuf;
11use std::sync::Arc;
12
13use crate::HumusEngine;
14use crate::language::LanguageEngine;
15use crate::language::LanguageEngineLoaderError;
16use crate::templating::HumusApiFormat;
17use crate::templating::TemplatingEngine;
18use crate::templating::TemplatingEngineLoaderError;
19
20/// Helps loading a [HumusEngine] from disk.
21///
22/// This merges settings from a configuration file and from command line arguments and then loads both the [TemplatingEngine] and [LanguageEngine] into a working [HumusEngine].
23///
24/// For documentation of the on disk format see [the template directory documentation](crate::doc::template_directory).
25///
26/// The functions doing the actual work are [TemplatingEngine::load_from_directory] and  [LanguageEngine::load_from_directory].
27///
28/// Example:
29/// ```rust,ignore
30/// use lib_humus::HumusEngineLoader;
31/// use lib_humus::crates::tera::Tera;
32///
33/// let template_loader = HumusEngineLoader::new(
34/// 	config.template.template_location.clone(),
35/// 	config.template.extra_config.clone(),
36/// 	Tera::new(),
37/// )
38/// 	.cli_template_location(cli_args.template_location)
39/// 	.cli_extra_config_location(cli_args.extra_config);
40///
41///
42/// let templating_engine = match template_loader.load_templates() {
43/// 	Ok(t) => t.into(),
44/// 	Err(e) => {
45/// 		println!("{e}");
46/// 		::std::process::exit(1);
47/// 	}
48/// };
49/// ```
50///
51#[derive(Debug, Clone)]
52pub struct HumusEngineLoader<ApiFormat: HumusApiFormat> {
53	/// The path to the directory where the templates are.
54	pub template_location: PathBuf,
55
56	/// The path to the extra configuration
57	/// (relative to the current pwd, not to the templates)
58	pub extra_config_location: Option<PathBuf>,
59
60	/// The empty templaing engine.
61	/// It is accessible so you can add customizations before loading the templates.
62	pub tera: Tera,
63
64	phantom_api_format: PhantomData<ApiFormat>,
65}
66
67impl<ApiFormat: HumusApiFormat> HumusEngineLoader<ApiFormat> {
68	/// Creates a new `HumusEngineLoader` with minimal typing.
69	pub fn new(template_location: PathBuf, extra_config_location: Option<PathBuf>) -> Self {
70		Self {
71			template_location: template_location,
72			extra_config_location: extra_config_location,
73			tera: Tera::new(),
74			phantom_api_format: PhantomData,
75		}
76	}
77
78	/// Overrides the template location with a new location if it is set.
79	///
80	/// Intended for processing cli-options.
81	pub fn cli_template_location(mut self, location: Option<PathBuf>) -> Self {
82		if let Some(location) = location {
83			self.template_location = location;
84		}
85		self
86	}
87
88	/// Overrides the extra configuration location with a new location if it is set.
89	///
90	/// Intended for processing cli-options.
91	pub fn cli_extra_config_location(mut self, location: Option<PathBuf>) -> Self {
92		if let Some(location) = location {
93			self.extra_config_location = Some(location);
94		}
95		self
96	}
97
98	/// Returns the template base directory.
99	pub fn base_dir(&self) -> PathBuf {
100		self.template_location.clone()
101	}
102
103	/// Initialize a [HumusEngine] with the given templates and extra configuration.
104	///
105	/// Failure Modes:
106	/// * The `extra.toml` was not found and the path was explicitly set.
107	/// * The `extra.toml` was found and is not valid toml.
108	/// * The `extra.toml` passes, but tera finds an error in the templates.
109	///
110	/// If `extra_config_location` is `None` no error is returned if the `extra.toml`
111	/// was not found as the template might not require one.
112	#[expect(clippy::result_large_err)]
113	pub fn load_templates(&self) -> Result<HumusEngine<ApiFormat>, HumusEngineLoaderError> {
114		let languages_base_dir = self.template_location.join("languages");
115		let language_engine = match LanguageEngine::load_from_directory(&languages_base_dir) {
116			Ok(engine) => engine,
117			Err(e) => {
118				error!("Error loading language engine: {e}");
119				match &e {
120					LanguageEngineLoaderError::BaseDirectoryDoesNotExist { path, .. } => {
121						warn!(
122							"Using an empty language engine, localization functions will not work."
123						);
124						warn!(
125							"Please consider creating the file {:?}, see the lib-humus crate documentation for details.",
126							path.join("manifest.toml")
127						);
128						LanguageEngine::new_empty()
129					}
130					_ => {
131						return Err(HumusEngineLoaderError::LanguageEngineError {
132							path: languages_base_dir,
133							error: e,
134						});
135					}
136				}
137			}
138		};
139
140		let language_engine = Arc::new(language_engine);
141
142		let templating_engine = TemplatingEngine::load_from_directory(
143			&self.template_location,
144			self.extra_config_location.as_deref(),
145			self.tera.clone(),
146			Some(language_engine.clone()),
147		)
148		.map_err(HumusEngineLoaderError::TemplatingEngineError)?;
149
150		Ok(HumusEngine::new(templating_engine, language_engine))
151	}
152}
153
154/// Returned when loading a template using the [HumusEngineLoader] fails.
155#[derive(Debug, thiserror::Error)]
156pub enum HumusEngineLoaderError {
157	/// Problem while loading templates
158	#[error("Error loading templates: {0}")]
159	TemplatingEngineError(#[source] TemplatingEngineLoaderError),
160	/// An error occurred while loading languages
161	#[error("Error loading languge engine {path:?}:\n{error}")]
162	LanguageEngineError {
163		/// Path to the languages directory
164		path: PathBuf,
165		/// what went wrong
166		#[source]
167		error: LanguageEngineLoaderError,
168	},
169}