From 1c0c2c49c610cae8c3acd6b9fa93bd28e743cd40 Mon Sep 17 00:00:00 2001 From: Maciej Jur Date: Sun, 7 Jul 2024 13:09:14 +0200 Subject: [PATCH] feat: basic image optimization --- .gitignore | 3 + Cargo.lock | 297 +++++++++++++++++++++++++++++++++++++- Cargo.toml | 2 + src/build.rs | 109 +++++++++++--- src/html/home.rs | 7 +- src/html/post.rs | 11 +- src/html/slideshow.rs | 11 +- src/html/wiki.rs | 11 +- src/main.rs | 16 +- src/pipeline.rs | 12 +- src/text/md.rs | 36 ++++- src/ts/mod.rs | 20 +-- src/watch.rs | 2 +- styles/layouts/_home.scss | 1 - 14 files changed, 482 insertions(+), 56 deletions(-) diff --git a/.gitignore b/.gitignore index a97e972..ac326e7 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ target/ # JavaScript js/**/node_modules/ + +# Hashed images +.hash diff --git a/Cargo.lock b/Cargo.lock index 51a4444..1fadbc8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "ahash" version = "0.8.11" @@ -99,12 +114,38 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" +[[package]] +name = "async-trait" +version = "0.1.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "biblatex" version = "0.9.3" @@ -130,6 +171,18 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -141,9 +194,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.15.4" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytemuck" +version = "1.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" [[package]] name = "byteorder" @@ -266,6 +325,16 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +[[package]] +name = "clap_mangen" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50dde5bc0c853d6248de457e5eb6e5a674a54b93810a34ded88d882ca1fe2de" +dependencies = [ + "clap", + "roff", +] + [[package]] name = "codemap" version = "0.1.3" @@ -311,6 +380,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.19" @@ -492,6 +580,12 @@ dependencies = [ "libc", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "generic-array" version = "0.14.7" @@ -524,6 +618,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + [[package]] name = "glob" version = "0.3.1" @@ -636,6 +736,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "html-escape" version = "0.2.13" @@ -729,12 +835,13 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.3", + "rayon", "serde", ] @@ -842,6 +949,24 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libdeflate-sys" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "669ea17f9257bcb48c09c7ee4bef3957777504acffac557263e20c11001977bc" +dependencies = [ + "cc", +] + +[[package]] +name = "libdeflater" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dfd6424f7010ee0a3416f1d796d0450e3ad3ac237a237644f728277c4ded016" +dependencies = [ + "libdeflate-sys", +] + [[package]] name = "libquickjs-sys" version = "0.9.0" @@ -864,6 +989,15 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.11" @@ -931,12 +1065,41 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e25be21376a772d15f97ae789845340a9651d3c4246ff5ebb6a2b35f9c37bd31" +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "oxipng" +version = "9.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f398c53eb34e0cf71d9e0bc676cfa7c611e3844dd14ab05e92fb7b423c98ecf" +dependencies = [ + "bitvec", + "clap", + "clap_mangen", + "crossbeam-channel", + "filetime", + "indexmap", + "libdeflater", + "log", + "rayon", + "rgb", + "rustc-hash", + "rustc_version", +] + [[package]] name = "paste" version = "1.0.14" @@ -991,6 +1154,12 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1089,6 +1258,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -1119,6 +1294,26 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -1157,6 +1352,21 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "rgb" +version = "0.8.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7439be6844e40133eda024efd85bf07f59d0dd2f59b10c00dd6cfb92cc5c741" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "roff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" + [[package]] name = "rstml" version = "0.11.2" @@ -1171,6 +1381,27 @@ dependencies = [ "thiserror", ] +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -1192,6 +1423,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + [[package]] name = "serde" version = "1.0.203" @@ -1247,6 +1484,30 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha256" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0" +dependencies = [ + "async-trait", + "bytes", + "hex", + "sha2", + "tokio", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -1271,9 +1532,11 @@ dependencies = [ "notify-debouncer-mini", "npezza93-tree-sitter-nix", "once_cell", + "oxipng", "pulldown-cmark", "regex", "serde", + "sha256", "tree-sitter", "tree-sitter-css", "tree-sitter-haskell", @@ -1359,6 +1622,12 @@ dependencies = [ "syn 2.0.53", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "thiserror" version = "1.0.59" @@ -1403,6 +1672,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tokio" +version = "1.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +dependencies = [ + "backtrace", + "bytes", + "pin-project-lite", +] + [[package]] name = "tree-sitter" version = "0.22.6" @@ -1919,6 +2199,15 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "yaml-rust2" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 3312c6d..44a7196 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,8 +15,10 @@ hayagriva = "0.5.3" hypertext = "0.5.1" katex = "0.4.6" once_cell = "1.19.0" +oxipng = { version = "9.1.1", default-features = false, features = ["filetime", "parallel"] } regex = "1.10.5" serde = { version = "1.0.203", features = ["derive"] } +sha256 = "1.5.0" # Watch notify = "6.1.1" diff --git a/src/build.rs b/src/build.rs index 1bcb1a0..3fc8d9b 100644 --- a/src/build.rs +++ b/src/build.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::fs; use std::fs::File; use std::io; @@ -5,7 +6,7 @@ use std::io::Write; use std::path::Path; use std::process::Command; -use camino::Utf8Path; +use camino::{Utf8Path, Utf8PathBuf}; use crate::pipeline::{AssetKind, Output, OutputKind, Sack, Virtual}; use crate::BuildContext; @@ -23,10 +24,21 @@ pub(crate) fn build_styles() { fs::write("dist/styles.css", css).unwrap(); } -pub(crate) fn build_content(ctx: &BuildContext, pending: &[&Output], hole: &[&Output]) { +pub(crate) fn build_content( + ctx: &BuildContext, + pending: &[&Output], + hole: &[&Output], + hash: Option>, +) -> HashMap { let now = std::time::Instant::now(); - render_all(ctx, pending, hole); + let hashes = render_all(ctx, pending, hole, hash); println!("Elapsed: {:.2?}", now.elapsed()); + copy_recursively(Path::new(".hash"), Path::new("dist/hash")).unwrap(); + let mut lmao = HashMap::::new(); + for hash in hashes { + lmao.insert(hash.file, hash.hash); + } + lmao } pub(crate) fn build_static() { @@ -72,26 +84,64 @@ fn copy_recursively(src: impl AsRef, dst: impl AsRef) -> io::Result< Ok(()) } -fn render_all(ctx: &BuildContext, pending: &[&Output], hole: &[&Output]) { - for item in pending { - let file = match &item.kind { - OutputKind::Asset(a) => Some(&a.meta.path), - OutputKind::Virtual(_) => None, - }; - render( - item, - Sack { - ctx, - hole, - path: &item.path, - file, - }, - ); - } +fn render_all( + ctx: &BuildContext, + pending: &[&Output], + hole: &[&Output], + hash: Option>, +) -> Vec { + pending + .iter() + .filter_map(|item| { + let file = match &item.kind { + OutputKind::Asset(a) => Some(&a.meta.path), + OutputKind::Virtual(_) => None, + }; + + render( + item, + Sack { + ctx, + hole, + path: &item.path, + file, + hash: hash.clone(), + }, + ) + }) + .collect() } -fn render(item: &Output, sack: Sack) { - let o = Utf8Path::new("dist").join(&item.path); +fn store_hash_png(data: &[u8]) -> Utf8PathBuf { + let store = Utf8Path::new(".hash").join("png"); + let hash = sha256::digest(data); + let hash_path = store.join(&hash).with_extension("png"); + if !hash_path.exists() { + let opts = oxipng::Options { + interlace: Some(oxipng::Interlacing::Adam7), + ..Default::default() + }; + let data = oxipng::optimize_from_memory(data, &opts).expect("PNG Error"); + fs::create_dir_all(&store).unwrap(); + fs::write(hash_path, data).expect("Couldn't output optimized PNG"); + } + + Utf8Path::new("/") + .join("hash") + .join("png") + .join(hash) + .with_extension("png") +} + +#[derive(Debug)] +pub(crate) struct Hashed { + pub file: Utf8PathBuf, + pub hash: Utf8PathBuf, +} + +fn render(item: &Output, sack: Sack) -> Option { + let dist = Utf8Path::new("dist"); + let o = dist.join(&item.path); fs::create_dir_all(o.parent().unwrap()).unwrap(); match item.kind { @@ -103,19 +153,32 @@ fn render(item: &Output, sack: Sack) { let mut file = File::create(&o).unwrap(); file.write_all(closure(&sack).as_bytes()).unwrap(); println!("HTML: {} -> {}", i, o); + None } - AssetKind::Bibtex(_) => {} + AssetKind::Bibtex(_) => None, AssetKind::Image => { + let hash = match item.path.extension() { + Some("png") => Some(store_hash_png(&std::fs::read(i).unwrap())), + Some("") => None, + _ => None, + }; + fs::create_dir_all(o.parent().unwrap()).unwrap(); fs::copy(i, &o).unwrap(); println!("Image: {} -> {}", i, o); + + hash.map(|hash| Hashed { + file: item.path.to_owned(), + hash, + }) } - }; + } } OutputKind::Virtual(Virtual(ref closure)) => { let mut file = File::create(&o).unwrap(); file.write_all(closure(&sack).as_bytes()).unwrap(); println!("Virtual: -> {}", o); + None } } } diff --git a/src/html/home.rs b/src/html/home.rs index 0b00f6f..c318ec4 100644 --- a/src/html/home.rs +++ b/src/html/home.rs @@ -1,6 +1,9 @@ +use std::collections::HashMap; + use hypertext::{html_elements, maud, maud_move, GlobalAttributes, Raw, Renderable}; -use crate::{pipeline::Sack, text::md::parse, LinkDate, Linkable}; +use crate::pipeline::Sack; +use crate::text::md::parse; const INTRO: &str = r#" ## かもし @@ -15,7 +18,7 @@ const INTRO: &str = r#" "#; fn intro() -> impl Renderable { - let (_, html, _) = parse(INTRO.into(), None); + let (_, html, _) = parse(INTRO.into(), None, "".into(), HashMap::new()); maud!( section .p-card.intro-jp lang="ja-JP" { (Raw(html)) diff --git a/src/html/post.rs b/src/html/post.rs index 3cff100..bb3fd97 100644 --- a/src/html/post.rs +++ b/src/html/post.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use camino::Utf8PathBuf; use chrono::{DateTime, Utc}; use hayagriva::Library; @@ -18,8 +20,13 @@ pub(crate) struct Post { } impl Content for Post { - fn parse(data: String, lib: Option<&Library>) -> (Outline, String, Option>) { - crate::text::md::parse(data, lib) + fn parse( + data: String, + lib: Option<&Library>, + dir: Utf8PathBuf, + hash: HashMap, + ) -> (Outline, String, Option>) { + crate::text::md::parse(data, lib, dir, hash) } fn render<'s, 'p, 'html>( diff --git a/src/html/slideshow.rs b/src/html/slideshow.rs index ebdd920..350b021 100644 --- a/src/html/slideshow.rs +++ b/src/html/slideshow.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use camino::Utf8PathBuf; use chrono::{DateTime, Utc}; use hayagriva::Library; @@ -26,13 +28,18 @@ pub(crate) struct Slideshow { } impl Content for Slideshow { - fn parse(data: String, _: Option<&Library>) -> (Outline, String, Option>) { + fn parse( + data: String, + _: Option<&Library>, + dir: Utf8PathBuf, + hash: HashMap, + ) -> (Outline, String, Option>) { let html = data .split("\n-----\n") .map(|chunk| { chunk .split("\n---\n") - .map(|s| crate::text::md::parse(s.to_owned(), None)) + .map(|s| crate::text::md::parse(s.to_owned(), None, dir.clone(), hash.clone())) .map(|e| e.1) .collect::>() }) diff --git a/src/html/wiki.rs b/src/html/wiki.rs index 762ac83..bee1a27 100644 --- a/src/html/wiki.rs +++ b/src/html/wiki.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use camino::Utf8PathBuf; use hayagriva::Library; use hypertext::{html_elements, maud_move, GlobalAttributes, Renderable}; @@ -14,8 +16,13 @@ pub struct Wiki { } impl Content for Wiki { - fn parse(data: String, lib: Option<&Library>) -> (Outline, String, Option>) { - crate::text::md::parse(data, lib) + fn parse( + data: String, + lib: Option<&Library>, + path: Utf8PathBuf, + hash: HashMap, + ) -> (Outline, String, Option>) { + crate::text::md::parse(data, lib, path, hash) } fn render<'s, 'p, 'html>( diff --git a/src/main.rs b/src/main.rs index 7c787aa..6243c14 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ mod ts; mod utils; mod watch; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::fs; use std::process::Command; @@ -120,7 +120,7 @@ fn main() { kind: Asset { kind: pipeline::AssetKind::html(|sack| { let data = std::fs::read_to_string("content/index.md").unwrap(); - let (_, html, _) = text::md::parse(data, None); + let (_, html, _) = text::md::parse(data, None, "".into(), HashMap::new()); crate::html::home(sack, Raw(html)) .render() .to_owned() @@ -210,7 +210,8 @@ fn build(ctx: &BuildContext, sources: &[Source], special: Vec) -> Vec = sources.iter().chain(special.iter()).collect(); - crate::build::build_content(ctx, &assets, &assets); + let lmao = crate::build::build_content(ctx, &assets, &assets, None); + crate::build::build_content(ctx, &assets, &assets, Some(lmao)); crate::build::build_static(); crate::build::build_styles(); crate::build::build_pagefind(); @@ -254,13 +255,18 @@ where Some("md" | "mdx" | "lhs") => { let raw = fs::read_to_string(&meta.path).unwrap(); let (matter, parsed) = parse_frontmatter::(&raw); - let link = T::as_link(&matter, Utf8Path::new("/").join(dir)); + let link = T::as_link(&matter, Utf8Path::new("/").join(&dir)); Output { kind: Asset { kind: pipeline::AssetKind::html(move |sack| { let lib = sack.get_library(); - let (outline, parsed, bib) = T::parse(parsed.clone(), lib); + let (outline, parsed, bib) = T::parse( + parsed.clone(), + lib, + dir.clone(), + sack.hash.as_ref().map(ToOwned::to_owned).unwrap_or_default(), + ); T::render(matter.clone(), sack, Raw(parsed), outline, bib) .render() .into() diff --git a/src/pipeline.rs b/src/pipeline.rs index 30cc63b..b4718e9 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -20,8 +20,12 @@ use crate::{BuildContext, Link, LinkDate, Linkable}; pub(crate) trait Content { /// Parse the document. Pass an optional library for bibliography. /// This generates the initial HTML markup from content. - fn parse(document: String, library: Option<&Library>) - -> (Outline, String, Option>); + fn parse( + document: String, + library: Option<&Library>, + path: Utf8PathBuf, + hash: HashMap, + ) -> (Outline, String, Option>); /// Render the full page from parsed content. fn render<'s, 'p, 'html>( @@ -75,7 +79,7 @@ impl Debug for AssetKind { // rust mental gymnastics moment let ptr = &**fun as *const dyn Fn(&Sack) -> String as *const () as usize; f.debug_tuple("Html").field(&ptr).finish() - }, + } Self::Bibtex(b) => f.debug_tuple("Bibtex").field(b).finish(), Self::Image => write!(f, "Image"), } @@ -188,6 +192,8 @@ pub(crate) struct Sack<'a> { pub path: &'a Utf8PathBuf, /// Original file location for this page pub file: Option<&'a Utf8PathBuf>, + /// Hashed optimized images + pub hash: Option> } impl<'a> Sack<'a> { diff --git a/src/text/md.rs b/src/text/md.rs index bd9bf45..6d1b8bc 100644 --- a/src/text/md.rs +++ b/src/text/md.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +use camino::{Utf8Path, Utf8PathBuf}; use hayagriva::{ archive::ArchivedStyle, citationberg::{IndependentStyle, Locale, Style}, @@ -47,7 +48,12 @@ static STYLE: Lazy = pub struct Outline(pub Vec<(String, String)>); -pub fn parse(text: String, lib: Option<&Library>) -> (Outline, String, Option>) { +pub fn parse( + text: String, + lib: Option<&Library>, + dir: Utf8PathBuf, + hash: HashMap, +) -> (Outline, String, Option>) { let (outline, stream) = { let stream = Parser::new_ext(&text, *OPTS); let mut stream: Vec<_> = TextMergeStream::new(stream).collect(); @@ -59,6 +65,7 @@ pub fn parse(text: String, lib: Option<&Library>) -> (Outline, String, Option>(); let stream = make_code(stream) @@ -349,3 +356,30 @@ fn make_emoji(event: Event) -> Event { _ => event, } } + +fn swap_hashed_image( + dir: Utf8PathBuf, + hash: HashMap, +) -> impl Fn(Event) -> Event { + move |event| match event { + Event::Start(start) => match start { + Tag::Image { + dest_url, + link_type, + title, + id, + } => { + let rel = dir.join(dest_url.as_ref()); + let hashed = hash.get(&rel).map(|path| path.as_str().to_owned().into()); + Event::Start(Tag::Image { + link_type, + dest_url: hashed.unwrap_or(dest_url), + title, + id, + }) + } + _ => Event::Start(start), + }, + _ => event, + } +} diff --git a/src/ts/mod.rs b/src/ts/mod.rs index 5270c83..eebd6fa 100644 --- a/src/ts/mod.rs +++ b/src/ts/mod.rs @@ -6,7 +6,7 @@ use std::borrow::Cow; use hypertext::{html_elements, maud_move, GlobalAttributes, Raw, Renderable}; use tree_sitter_highlight::{HighlightEvent, Highlighter}; -pub enum Event { +pub enum TSEvent { Write(String), Enter(String), Close, @@ -31,23 +31,23 @@ fn to_html(lang: &str, code: &str) -> String { get_events(lang, code) .into_iter() .map(|event| match event { - Event::Write(text) => Cow::from( + TSEvent::Write(text) => Cow::from( text.replace('&', "&") .replace('<', "<") .replace('>', ">"), ), - Event::Enter(class) => { + TSEvent::Enter(class) => { Cow::from(format!("", class.replace('.', "-"))) } - Event::Close => Cow::from(""), + TSEvent::Close => Cow::from(""), }) .collect() } -fn get_events(lang: &str, src: &str) -> Vec { +fn get_events(lang: &str, src: &str) -> Vec { let config = match configs::get_config(lang) { Some(c) => c, - None => return vec![Event::Write(src.into())], + None => return vec![TSEvent::Write(src.into())], }; let mut hl = Highlighter::new(); @@ -66,10 +66,10 @@ fn get_events(lang: &str, src: &str) -> Vec { out } -fn map_event(event: HighlightEvent, src: &str) -> Event { +fn map_event(event: HighlightEvent, src: &str) -> TSEvent { match event { - HighlightEvent::Source { start, end } => Event::Write(src[start..end].into()), - HighlightEvent::HighlightStart(s) => Event::Enter(captures::NAMES[s.0].into()), - HighlightEvent::HighlightEnd => Event::Close, + HighlightEvent::Source { start, end } => TSEvent::Write(src[start..end].into()), + HighlightEvent::HighlightStart(s) => TSEvent::Enter(captures::NAMES[s.0].into()), + HighlightEvent::HighlightEnd => TSEvent::Close, } } diff --git a/src/watch.rs b/src/watch.rs index 4080aa4..a356141 100644 --- a/src/watch.rs +++ b/src/watch.rs @@ -118,7 +118,7 @@ pub fn watch(ctx: &BuildContext, sources: &[Source], state: Vec) -> Resu let state_next = update_stream(&state, &items); let abc: Vec<&Output> = items.iter().map(AsRef::as_ref).collect(); let xyz: Vec<&Output> = state_next.iter().map(AsRef::as_ref).collect(); - build_content(ctx, &abc, &xyz); + build_content(ctx, &abc, &xyz, None); state = state_next; dirty = true; } diff --git a/styles/layouts/_home.scss b/styles/layouts/_home.scss index 650ac7a..4fd4d69 100644 --- a/styles/layouts/_home.scss +++ b/styles/layouts/_home.scss @@ -1,6 +1,5 @@ @use '../consts' as consts; - .l-home { display: flex; flex-direction: column;