use serde::Deserialize; use std::{ fs::{self, read_to_string, remove_dir_all}, path::PathBuf, }; use glob::glob; use minify_html::{minify, Cfg}; use std::{fs::File, io::Write, path::Path}; #[derive(Deserialize, Clone, Default)] pub struct FileConfig { pub scheme: Option, pub port: Option, pub mail: Option, pub lang: Option, pub onion: Option, } #[derive(Clone)] pub struct Config { pub fc: FileConfig, pub static_location: String, } #[derive(Deserialize)] struct AskamaConfig { general: AskamaConfigGeneral, } #[derive(Deserialize)] struct AskamaConfigGeneral { dirs: Vec, } impl FileConfig { fn new() -> Self { Self { scheme: Some("http".to_string()), port: Some(8080), ..FileConfig::default() } } fn complete(a: Self) -> Self { // Default config let d = FileConfig::new(); /// Return the default value if nothing is value is none fn test(val: Option, default: Option) -> Option { if val.is_some() { val } else { default } } Self { scheme: test(a.scheme, d.scheme), port: test(a.port, d.port), mail: test(a.mail, d.mail), lang: test(a.lang, d.lang), onion: test(a.onion, d.onion), } } } fn get_file_config(file_path: &str) -> FileConfig { match fs::read_to_string(file_path) { Ok(file) => match toml::from_str(&file) { Ok(stored_config) => FileConfig::complete(stored_config), Err(file_error) => { panic!("Error in config file: {file_error}"); } }, Err(_) => // No config file { FileConfig::new() } } } fn get_askama_config() -> AskamaConfig { toml::from_str(&read_to_string("askama.toml").unwrap()).unwrap() } pub fn get_config(file_path: &str) -> Config { let internal_config = get_file_config(file_path); let static_dir = "static".to_string(); let templates_dirs = get_askama_config().general.dirs; if templates_dirs.len() != 1 { panic!("Too many templates directories") } let mut template_data = templates_dirs[0].split('/'); let files_root = init( template_data.next().unwrap().to_string(), static_dir.clone(), template_data.next().unwrap().to_string(), ); Config { fc: internal_config, static_location: format!("{}/{}", files_root, static_dir), } } fn init(dist_dir: String, static_dir: String, templates_dir: String) -> String { // Be sure that we not gonna use the dist folder by deleting it remove_dir_all(&dist_dir).unwrap_or_default(); let cfg = Cfg::spec_compliant(); // Static files for entry in glob(&format!("{static_dir}/**/*.*")).unwrap() { let path = entry.unwrap(); let path_with_dist = path .to_string_lossy() .replace(&static_dir, &format!("{dist_dir}/{static_dir}")); copy_to_dist(&cfg, path, path_with_dist); } // Template files for entry in glob(&format!("{templates_dir}/**/*.*")).unwrap() { let path = entry.unwrap(); let path_with_dist = path .to_string_lossy() .replace(&templates_dir, &format!("{dist_dir}/{templates_dir}")); copy_to_dist(&cfg, path, path_with_dist); } dist_dir } fn copy_to_dist(cfg: &Cfg, path: PathBuf, path_with_dist: String) { // Create folders let new_path = Path::new(&path_with_dist); fs::create_dir_all(new_path.parent().unwrap()).unwrap(); let mut copy = true; if let Some(ext) = path.extension() { // List of files who should be minified if ["html", "css", "js", "svg", "webmanifest", "xml"] .iter() .any(|item| ext.to_string_lossy().to_lowercase().contains(item)) && !cfg!(debug_assertions) { // We won't copy, we'll minify copy = false; // Minify let data = fs::read(&path).unwrap(); let minified = minify(&data, cfg); // Write files let file = File::create(&path_with_dist); file.expect("Error when minify the file") .write_all(&minified) .unwrap(); } } if copy { // If no minification is needed fs::copy(path, path_with_dist).unwrap(); } }