diff --git a/Cargo.lock b/Cargo.lock index cba6a81..7489246 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -260,33 +260,10 @@ dependencies = [ ] [[package]] -name = "askama" -version = "0.12.0" +name = "arrayvec" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47cbc3cf73fa8d9833727bbee4835ba5c421a0d65b72daf9a7b5d0e0f9cfb57e" -dependencies = [ - "askama_derive", - "askama_escape", - "humansize", - "num-traits", - "percent-encoding", -] - -[[package]] -name = "askama_derive" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c22fbe0413545c098358e56966ff22cdd039e10215ae213cfbd65032b119fc94" -dependencies = [ - "basic-toml", - "mime", - "mime_guess", - "nom", - "proc-macro2", - "quote", - "serde", - "syn 2.0.13", -] +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "askama_escape" @@ -300,6 +277,19 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bae" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b8de67cc41132507eeece2584804efcb15f85ba516e34c944b7667f480397a" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "base64" version = "0.21.0" @@ -307,13 +297,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" [[package]] -name = "basic-toml" -version = "0.1.2" +name = "beef" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c0de75129aa8d0cceaf750b89013f0e08804d6ec61416da787b35ad0d7cddf1" -dependencies = [ - "serde", -] +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" [[package]] name = "bitflags" @@ -475,9 +462,9 @@ version = "0.1.0" dependencies = [ "actix-files", "actix-web", - "askama", "glob", "minify-html", + "ramhorns", "serde", "toml", ] @@ -589,6 +576,15 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.2.6" @@ -627,15 +623,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" -[[package]] -name = "humansize" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" -dependencies = [ - "libm", -] - [[package]] name = "idna" version = "0.3.0" @@ -689,12 +676,6 @@ version = "0.2.141" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" -[[package]] -name = "libm" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" - [[package]] name = "local-channel" version = "0.1.3" @@ -732,6 +713,29 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "logos" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf8b031682c67a8e3d5446840f9573eb7fe26efe7ec8d195c9ac4c0647c502f1" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d849148dbaf9661a6151d1ca82b13bb4c4c128146a88d05253b38d4e2f496c" +dependencies = [ + "beef", + "fnv", + "proc-macro2", + "quote", + "regex-syntax", + "syn 1.0.109", +] + [[package]] name = "memchr" version = "2.5.0" @@ -815,15 +819,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - [[package]] name = "num_cpus" version = "1.15.0" @@ -910,6 +905,30 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.56" @@ -919,6 +938,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pulldown-cmark" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63" +dependencies = [ + "bitflags", + "memchr", + "unicase", +] + [[package]] name = "quote" version = "1.0.26" @@ -928,6 +958,33 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "ramhorns" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47008ae2e2a9085a3f658203609d79f8a027829cf88a088d0c0084e18ba8f0b9" +dependencies = [ + "arrayvec", + "beef", + "fnv", + "logos", + "pulldown-cmark", + "ramhorns-derive", +] + +[[package]] +name = "ramhorns-derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada9bbdd21adf426f932bf76b3db7d553538dffc16afd5fb8ce2ce2110a75536" +dependencies = [ + "bae", + "fnv", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "rand" version = "0.8.5" @@ -1300,6 +1357,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "url" version = "2.3.1" diff --git a/Cargo.toml b/Cargo.toml index a817a98..8ab6a74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ license = "AGPL-3.0-or-later" [dependencies] actix-web = "4" actix-files = "0.6" -askama = "0.12.0" +ramhorns = "0.14.0" toml = "0.7.3" serde = { version = "1.0.159", features = ["derive"] } minify-html = "0.10.8" diff --git a/src/config.rs b/src/config.rs index 02380f4..acf6af0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,14 @@ use serde::Deserialize; -use std::fs; +use std::{ + fs::{self, remove_dir_all}, + path::PathBuf, +}; + +use glob::glob; +use minify_html::{minify, Cfg}; +use std::{fs::File, io::Write, path::Path}; + +use crate::template::Template; #[derive(Deserialize, Clone, Default)] pub struct FileConfig { @@ -10,11 +19,6 @@ pub struct FileConfig { pub onion: Option, } -#[derive(Clone)] -pub struct Config { - pub fc: FileConfig, -} - impl FileConfig { fn new() -> Self { Self { @@ -47,6 +51,13 @@ impl FileConfig { } } +#[derive(Clone)] +pub struct Config { + pub fc: FileConfig, + pub static_location: String, + pub tmpl: Template, +} + fn get_file_config(file_path: &str) -> FileConfig { match fs::read_to_string(file_path) { Ok(file) => match toml::from_str(&file) { @@ -66,7 +77,86 @@ fn get_file_config(file_path: &str) -> FileConfig { pub fn get_config(file_path: &str) -> Config { let internal_config = get_file_config(file_path); + let static_dir = "static".to_string(); + let templates_dir = "templates".to_string(); + let files_root = init( + "dist".to_string(), + static_dir.clone(), + templates_dir.clone(), + ); + Config { fc: internal_config, + static_location: format!("{}/{}", files_root, static_dir), + tmpl: Template { + directory: format!("{}/{}", files_root, templates_dir), + }, + } +} + +fn init(dist_dir: String, static_dir: String, templates_dir: String) -> String { + // The static folder is minimized only in release mode + if cfg!(debug_assertions) { + // Be sure that we not gonna use the dist folder by deleting it + remove_dir_all(dist_dir).unwrap_or_default(); + + ".".to_string() + } else { + 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}")); + + 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 + } +} + +fn minify_and_copy(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)) + { + // 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(); } } diff --git a/src/main.rs b/src/main.rs index 25874f6..00e7570 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,6 @@ use actix_files::Files; use actix_web::{middleware::DefaultHeaders, web, App, HttpServer}; -use glob::glob; -use minify_html::{minify, Cfg}; -use std::{ - fs::{self, File}, - io::{self, Write}, - path::Path, -}; +use std::io; mod config; mod template; @@ -35,52 +29,6 @@ async fn main() -> io::Result<()> { let addr = ("0.0.0.0", config.fc.port.unwrap()); - let static_folder = "static"; - let dist_folder = format!("dist/{static_folder}"); - - // The static folder is minimized only in release mode - let folder = if cfg!(debug_assertions) { - format!("{static_folder}/") - } else { - let cfg = Cfg::spec_compliant(); - - for entry in glob(&format!("{static_folder}/**/*.*")).unwrap() { - let path = entry.unwrap(); - let path_with_dist = path.to_string_lossy().replace(static_folder, &dist_folder); - - // 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)) - { - // We won't copy, we'll minify - copy = false; - - // Minify - let data = fs::read(&path).unwrap(); - let minified = minify(&data, &cfg); - - // Write files - let mut file = File::create(&path_with_dist)?; - file.write_all(&minified)?; - } - } - - if copy { - // If no minification is needed - fs::copy(path, path_with_dist)?; - } - } - - format!("{dist_folder}/") - }; - println!( "Listening to {}://{}:{}", config.clone().fc.scheme.unwrap(), @@ -103,7 +51,7 @@ async fn main() -> io::Result<()> { .service(networks::page) .service(portfolio::page) .service(contrib::page) - .service(Files::new("/", &folder)) + .service(Files::new("/", config.static_location.clone())) .default_service(web::to(not_found::page)) }) .bind(addr)? diff --git a/src/routes/agreements.rs b/src/routes/agreements.rs index 3e41b74..9a5c570 100644 --- a/src/routes/agreements.rs +++ b/src/routes/agreements.rs @@ -1,7 +1,6 @@ -use actix_web::{dev::ConnectionInfo, get, routes, web, HttpRequest, HttpResponse, Responder}; -use askama::Template; - use crate::config::Config; +use actix_web::{dev::ConnectionInfo, get, routes, web, HttpRequest, HttpResponse, Responder}; +use ramhorns::Content; #[routes] #[get("/.well-known/security.txt")] @@ -13,8 +12,7 @@ pub async fn security(req: HttpRequest, config: web::Data) -> impl Respo )) } -#[derive(Template)] -#[template(path = "../templates/security.txt")] +#[derive(Content)] struct SecurityTemplate { contact: String, pref_lang: String, @@ -22,17 +20,18 @@ struct SecurityTemplate { } fn get_security(config: Config, info: ConnectionInfo) -> String { - let data = SecurityTemplate { - contact: config.fc.mail.unwrap_or_default(), - pref_lang: config.fc.lang.unwrap_or_default(), - url: format!( - "{}://{}/.well-known/security.txt", - info.scheme(), - info.host() - ), - }; - - data.render().unwrap() + config.tmpl.render( + "security.txt", + SecurityTemplate { + contact: config.fc.mail.unwrap_or_default(), + pref_lang: config.fc.lang.unwrap_or_default(), + url: format!( + "{}://{}/.well-known/security.txt", + info.scheme(), + info.host() + ), + }, + ) } #[get("/humans.txt")] diff --git a/src/routes/contrib.rs b/src/routes/contrib.rs index 28bfdd3..50ed003 100644 --- a/src/routes/contrib.rs +++ b/src/routes/contrib.rs @@ -1,17 +1,15 @@ -use actix_web::{get, HttpResponse, Responder}; -use askama::Template; - -use crate::template::render_html; +use crate::config::Config; +use actix_web::{get, web, HttpResponse, Responder}; +use ramhorns::Content; #[get("/contrib")] -pub async fn page() -> impl Responder { - HttpResponse::Ok().body(get_page()) +pub async fn page(config: web::Data) -> impl Responder { + HttpResponse::Ok().body(get_page(config.get_ref().clone())) } -#[derive(Template)] -#[template(path = "../templates/contrib.html")] +#[derive(Content)] struct PortfolioTemplate {} -pub fn get_page() -> std::string::String { - render_html(&PortfolioTemplate {}) +pub fn get_page(config: Config) -> std::string::String { + config.tmpl.render("contrib.html", PortfolioTemplate {}) } diff --git a/src/routes/index.rs b/src/routes/index.rs index 0c1a953..f985d30 100644 --- a/src/routes/index.rs +++ b/src/routes/index.rs @@ -1,17 +1,16 @@ -use actix_web::{get, HttpResponse, Responder}; -use askama::Template; +use actix_web::{get, web, HttpResponse, Responder}; +use ramhorns::Content; -use crate::template::render_html; +use crate::config::Config; #[get("/")] -pub async fn page() -> impl Responder { - HttpResponse::Ok().body(get_page()) +pub async fn page(config: web::Data) -> impl Responder { + HttpResponse::Ok().body(get_page(config.get_ref().clone())) } -#[derive(Template)] -#[template(path = "../templates/index.html")] +#[derive(Content)] struct IndexTemplate {} -pub fn get_page() -> std::string::String { - render_html(&IndexTemplate {}) +pub fn get_page(config: Config) -> std::string::String { + config.tmpl.render("index.html", IndexTemplate {}) } diff --git a/src/routes/networks.rs b/src/routes/networks.rs index 36df9ac..d726919 100644 --- a/src/routes/networks.rs +++ b/src/routes/networks.rs @@ -1,17 +1,16 @@ -use actix_web::{get, HttpResponse, Responder}; -use askama::Template; +use actix_web::{get, web, HttpResponse, Responder}; +use ramhorns::Content; -use crate::template::render_html; +use crate::config::Config; #[get("/networks")] -pub async fn page() -> impl Responder { - HttpResponse::Ok().body(get_page()) +pub async fn page(config: web::Data) -> impl Responder { + HttpResponse::Ok().body(get_page(config.get_ref().clone())) } -#[derive(Template)] -#[template(path = "../templates/networks.html")] +#[derive(Content)] struct NetworksTemplate {} -pub fn get_page() -> std::string::String { - render_html(&NetworksTemplate {}) +pub fn get_page(config: Config) -> std::string::String { + config.tmpl.render("networks.html", NetworksTemplate {}) } diff --git a/src/routes/not_found.rs b/src/routes/not_found.rs index 5d1f566..9e57b20 100644 --- a/src/routes/not_found.rs +++ b/src/routes/not_found.rs @@ -1,16 +1,15 @@ -use actix_web::{HttpResponse, Responder}; -use askama::Template; +use actix_web::{web, HttpResponse, Responder}; +use ramhorns::Content; -use crate::template::render_html; +use crate::config::Config; -pub async fn page() -> impl Responder { - HttpResponse::NotFound().body(get_page()) +pub async fn page(config: web::Data) -> impl Responder { + HttpResponse::NotFound().body(get_page(config.get_ref().clone())) } -#[derive(Template)] -#[template(path = "../templates/404.html")] +#[derive(Content)] struct Error404Template {} -pub fn get_page() -> std::string::String { - render_html(&Error404Template {}) +pub fn get_page(config: Config) -> std::string::String { + config.tmpl.render("404.html", Error404Template {}) } diff --git a/src/routes/portfolio.rs b/src/routes/portfolio.rs index 01323df..98386e0 100644 --- a/src/routes/portfolio.rs +++ b/src/routes/portfolio.rs @@ -1,17 +1,16 @@ -use actix_web::{get, HttpResponse, Responder}; -use askama::Template; +use actix_web::{get, web, HttpResponse, Responder}; +use ramhorns::Content; -use crate::template::render_html; +use crate::config::Config; #[get("/portfolio")] -pub async fn page() -> impl Responder { - HttpResponse::Ok().body(get_page()) +pub async fn page(config: web::Data) -> impl Responder { + HttpResponse::Ok().body(get_page(config.get_ref().clone())) } -#[derive(Template)] -#[template(path = "../templates/portfolio.html")] +#[derive(Content)] struct PortfolioTemplate {} -pub fn get_page() -> std::string::String { - render_html(&PortfolioTemplate {}) +pub fn get_page(config: Config) -> std::string::String { + config.tmpl.render("portfolio.html", PortfolioTemplate {}) } diff --git a/src/template.rs b/src/template.rs index 9b7c608..1c8e015 100644 --- a/src/template.rs +++ b/src/template.rs @@ -1,14 +1,15 @@ -use askama::DynTemplate; -use minify_html::{minify, Cfg}; +use ramhorns::{Content, Ramhorns}; -pub fn render_html(template: &dyn DynTemplate) -> String { - let data = template.dyn_render().unwrap(); - - // we just minify before sending to the client, - // we need to find a way to pre-minify static pages to the dist folder, - // maybe in release mode? to make debugging easier - // from https://git.mylloon.fr/Anri/mylloon.fr/issues/15 - - let cfg = Cfg::spec_compliant(); - String::from_utf8(minify(data.as_bytes(), &cfg)).unwrap() +#[derive(Clone)] +pub struct Template { + pub directory: String, +} + +impl Template { + pub fn render(&self, template: &str, data: C) -> String { + let mut templates: Ramhorns = Ramhorns::lazy(&self.directory).unwrap(); + let tplt = templates.from_file(template).unwrap(); + + tplt.render(&data) + } }