mylloon.fr/src/config.rs
Mylloon aba8a501af
All checks were successful
ci/woodpecker/push/publish Pipeline was successful
more clippy advices
2024-05-28 20:58:41 +02:00

209 lines
6.3 KiB
Rust

use serde::Deserialize;
use std::{fs, path::PathBuf};
use glob::glob;
use std::{fs::File, io::Write, path::Path};
use crate::template::Template;
/// Store the configuration of config/config.toml
#[derive(Clone, Debug, Default, Deserialize)]
pub struct FileConfiguration {
/// http/https
pub scheme: Option<String>,
/// Domain name "sub.domain.tld"
pub domain: Option<String>,
/// Port used
pub port: Option<u16>,
/// Mail of owner
pub mail: Option<String>,
/// Lang used
pub lang: Option<String>,
/// .onion address for Tor of the app
pub onion: Option<String>,
/// App name
pub app_name: Option<String>,
/// Name of website owner
pub name: Option<String>,
/// Fullname of website owner
pub fullname: Option<String>,
/// List exclusion for courses
pub exclude_courses: Option<Vec<String>>,
}
impl FileConfiguration {
/// Initialize with default values
fn new() -> Self {
Self {
scheme: Some("http".into()),
domain: Some("localhost".into()),
port: Some(8080),
app_name: Some("EWP".into()),
exclude_courses: Some([].into()),
..Self::default()
}
}
/// Complete default structure with an existing one
fn complete(a: Self) -> Self {
// Default config
let d = Self::new();
#[allow(clippy::items_after_statements)]
/// Return the default value if nothing is value is none
fn test<T>(val: Option<T>, default: Option<T>) -> Option<T> {
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),
domain: test(a.domain, d.domain),
onion: test(a.onion, d.onion),
app_name: test(a.app_name, d.app_name),
name: test(a.name, d.name),
fullname: test(a.fullname, d.fullname),
exclude_courses: test(a.exclude_courses, d.exclude_courses),
}
}
}
// Paths where files are stored
#[derive(Clone, Debug)]
pub struct Locations {
pub static_dir: String,
pub data_dir: String,
}
/// Configuration used internally in the app
#[derive(Clone, Debug)]
pub struct Config {
/// Information given in the config file
pub fc: FileConfiguration,
/// Location where the static files are stored
pub locations: Locations,
/// Informations about templates
pub tmpl: Template,
}
/// Load the config file
fn get_file_config(file_path: &str) -> FileConfiguration {
fs::read_to_string(file_path).map_or_else(
|_| FileConfiguration::new(),
|file| match toml::from_str(&file) {
Ok(stored_config) => FileConfiguration::complete(stored_config),
Err(file_error) => {
panic!("Error in config file: {file_error}");
}
},
)
}
/// Build the configuration
pub fn get_configuration(file_path: &str) -> Config {
let internal_config = get_file_config(file_path);
let static_dir = "static";
let templates_dir = "templates";
let files_root = init("dist".into(), static_dir, templates_dir);
Config {
fc: internal_config.clone(),
locations: Locations {
static_dir: format!("{files_root}/{static_dir}"),
data_dir: String::from("data"),
},
tmpl: Template {
directory: format!("{files_root}/{templates_dir}"),
app_name: internal_config.app_name.unwrap(),
url: internal_config.domain.unwrap(),
name: internal_config.name,
},
}
}
/// Preparation before running the http server
fn init(dist_dir: String, static_dir: &str, templates_dir: &str) -> String {
// The static folder is minimized only in release mode
if cfg!(debug_assertions) {
".".into()
} else {
let cfg = minify_html::Cfg {
keep_closing_tags: true,
minify_css: true,
minify_js: true,
remove_bangs: false,
preserve_brace_template_syntax: true,
..minify_html::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}"));
minify_and_copy(&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}"));
minify_and_copy(&cfg, path, path_with_dist);
}
dist_dir
}
}
/// Minify some assets for production
fn minify_and_copy(cfg: &minify_html::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 session = minify_js::Session::new();
let mut copy = true;
if let Some(ext) = path.extension() {
let js_ext = "js";
let current_ext = ext.to_string_lossy().to_lowercase();
// List of files who should be minified
if ["html", "css", js_ext, "svg", "webmanifest", "xml"].contains(&current_ext.as_str()) {
// We won't copy, we'll minify
copy = false;
// Minify
let data = fs::read(&path).unwrap();
let minified = if current_ext == js_ext {
let mut out = Vec::new();
minify_js::minify(&session, minify_js::TopLevelMode::Global, &data, &mut out)
.unwrap();
out
} else {
minify_html::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();
}
}