From 6aff395a5d016ce9bcbe88a65f639cf5f922ed28 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 24 Oct 2023 11:34:12 +0200 Subject: [PATCH 1/2] Use `eyre` for error handling `Box` is not `Send`, which makes it harder to parallelize the rendering of the blog posts. `eyre::Report` fortunately does not have this problem. --- Cargo.lock | 17 +++++++++++++++++ Cargo.toml | 1 + src/blog.rs | 4 +--- src/blogs.rs | 7 +++---- src/lib.rs | 21 ++++++++++----------- src/posts.rs | 6 +++--- 6 files changed, 35 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 19ad53230..92c8d842f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,6 +100,7 @@ version = "0.1.0" dependencies = [ "chrono", "comrak", + "eyre", "handlebars", "lazy_static", "regex", @@ -282,6 +283,16 @@ dependencies = [ "termcolor", ] +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fake-simd" version = "0.1.2" @@ -559,6 +570,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "1.9.1" diff --git a/Cargo.toml b/Cargo.toml index 6325f08a6..44173fd20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ name = "blog" path = "src/blog.rs" [dependencies] +eyre = "0.6.8" handlebars = { version = "3", features = ["dir_source"] } lazy_static = "1.4.0" serde = "1.0" diff --git a/src/blog.rs b/src/blog.rs index 17442c65e..2b5ef3d14 100644 --- a/src/blog.rs +++ b/src/blog.rs @@ -1,9 +1,7 @@ -use std::error::Error; - #[path = "lib.rs"] mod lib; -pub fn main() -> Result<(), Box> { +pub fn main() -> eyre::Result<()> { lib::main()?; println!("blog has been generated; you can now serve its content by running\n\ diff --git a/src/blogs.rs b/src/blogs.rs index 2ee8e9dd0..b76f70454 100644 --- a/src/blogs.rs +++ b/src/blogs.rs @@ -1,6 +1,5 @@ use super::posts::Post; use serde_derive::{Deserialize, Serialize}; -use std::error::Error; use std::path::{Path, PathBuf}; static MANIFEST_FILE: &str = "blog.yml"; @@ -46,7 +45,7 @@ pub(crate) struct Blog { } impl Blog { - fn load(prefix: PathBuf, dir: &Path) -> Result> { + fn load(prefix: PathBuf, dir: &Path) -> eyre::Result { let manifest_content = std::fs::read_to_string(dir.join(MANIFEST_FILE))?; let manifest: Manifest = serde_yaml::from_str(&manifest_content)?; @@ -122,7 +121,7 @@ impl Blog { /// Recursively load blogs in a directory. A blog is a directory with a `blog.yml` /// file inside it. -pub(crate) fn load(base: &Path) -> Result, Box> { +pub(crate) fn load(base: &Path) -> eyre::Result> { let mut blogs = Vec::new(); load_recursive(base, base, &mut blogs)?; Ok(blogs) @@ -132,7 +131,7 @@ fn load_recursive( base: &Path, current: &Path, blogs: &mut Vec, -) -> Result<(), Box> { +) -> eyre::Result<()> { for entry in std::fs::read_dir(current)? { let path = entry?.path(); let file_type = path.metadata()?.file_type(); diff --git a/src/lib.rs b/src/lib.rs index e77f632d6..c0a048bc1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,6 @@ use sass_rs::{compile_file, Options}; use serde_derive::Serialize; use serde_json::json; use std::convert::AsRef; -use std::error::Error; use std::fs::{self, File}; use std::io::{self, Write}; use std::path::{Path, PathBuf}; @@ -51,7 +50,7 @@ impl<'a> Generator<'a> { fn new( out_directory: impl AsRef, posts_directory: impl AsRef, - ) -> Result> { + ) -> eyre::Result { let mut handlebars = Handlebars::new(); handlebars.set_strict_mode(true); handlebars.register_templates_directory(".hbs", "templates")?; @@ -80,7 +79,7 @@ impl<'a> Generator<'a> { .replace(std::path::MAIN_SEPARATOR, "/") } - fn render(&self) -> Result<(), Box> { + fn render(&self) -> eyre::Result<()> { // make sure our output directory exists fs::create_dir_all(&self.out_directory)?; @@ -116,7 +115,7 @@ impl<'a> Generator<'a> { fs::write("./static/styles/vendor.css", &concatted).expect("couldn't write vendor css"); } - fn render_blog(&self, blog: &Blog) -> Result<(), Box> { + fn render_blog(&self, blog: &Blog) -> eyre::Result<()> { std::fs::create_dir_all(self.out_directory.join(blog.prefix()))?; let path = self.render_index(blog)?; @@ -136,7 +135,7 @@ impl<'a> Generator<'a> { Ok(()) } - fn render_index(&self, blog: &Blog) -> Result> { + fn render_index(&self, blog: &Blog) -> eyre::Result { let other_blogs: Vec<_> = self .blogs .iter() @@ -161,7 +160,7 @@ impl<'a> Generator<'a> { Ok(path) } - fn render_post(&self, blog: &Blog, post: &Post) -> Result> { + fn render_post(&self, blog: &Blog, post: &Post) -> eyre::Result { let path = blog .prefix() .join(format!("{:04}", &post.year)) @@ -186,7 +185,7 @@ impl<'a> Generator<'a> { Ok(path) } - fn render_feed(&self, blog: &Blog) -> Result<(), Box> { + fn render_feed(&self, blog: &Blog) -> eyre::Result<()> { let posts: Vec<_> = blog.posts().iter().take(10).collect(); let data = json!({ "blog": blog, @@ -198,7 +197,7 @@ impl<'a> Generator<'a> { Ok(()) } - fn render_releases_feed(&self, blog: &Blog) -> Result<(), Box> { + fn render_releases_feed(&self, blog: &Blog) -> eyre::Result<()> { let posts = blog.posts().iter().cloned().collect::>(); let is_released: Vec<&Post> = posts.iter().filter(|post| post.release).collect(); let releases: Vec = is_released @@ -223,7 +222,7 @@ impl<'a> Generator<'a> { Ok(()) } - fn copy_static_files(&self) -> Result<(), Box> { + fn copy_static_files(&self) -> eyre::Result<()> { copy_dir("static/fonts", &self.out_directory)?; copy_dir("static/images", &self.out_directory)?; copy_dir("static/styles", &self.out_directory)?; @@ -236,7 +235,7 @@ impl<'a> Generator<'a> { name: impl AsRef, template: &str, data: serde_json::Value, - ) -> Result<(), Box> { + ) -> eyre::Result<()> { let out_file = self.out_directory.join(name.as_ref()); let file = File::create(out_file)?; self.handlebars.render_to_write(template, &data, file)?; @@ -264,7 +263,7 @@ fn copy_dir(source: impl AsRef, dest: impl AsRef) -> Result<(), io:: copy_inner(source, &dest) } -pub fn main() -> Result<(), Box> { +pub fn main() -> eyre::Result<()> { let blog = Generator::new("site", "posts")?; blog.render()?; diff --git a/src/posts.rs b/src/posts.rs index 3b330cfac..1e5f459cb 100644 --- a/src/posts.rs +++ b/src/posts.rs @@ -2,8 +2,8 @@ use super::blogs::Manifest; use comrak::{ComrakExtensionOptions, ComrakOptions, ComrakRenderOptions}; use regex::Regex; use serde_derive::{Deserialize, Serialize}; -use std::error::Error; use std::path::{Path, PathBuf}; +use eyre::eyre; #[derive(Debug, PartialEq, Deserialize)] struct YamlHeader { @@ -36,7 +36,7 @@ pub(crate) struct Post { } impl Post { - pub(crate) fn open(path: &Path, manifest: &Manifest) -> Result> { + pub(crate) fn open(path: &Path, manifest: &Manifest) -> eyre::Result { // yeah this might blow up, but it won't let filename = path.file_name().unwrap().to_str().unwrap(); @@ -52,7 +52,7 @@ impl Post { let contents = std::fs::read_to_string(path)?; if contents.len() < 5 { return Err( - format!("{path:?} is empty, or too short to have valid front matter").into(), + eyre!("{path:?} is empty, or too short to have valid front matter") ); } From 92fe57861477531595040f9d4f8928de9f9175fb Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 24 Oct 2023 11:35:36 +0200 Subject: [PATCH 2/2] Use `rayon` to parallelize the blog post rendering --- Cargo.lock | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/lib.rs | 9 +++---- 3 files changed, 80 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 92c8d842f..b778e6a80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,6 +103,7 @@ dependencies = [ "eyre", "handlebars", "lazy_static", + "rayon", "regex", "sass-rs", "serde", @@ -229,6 +230,39 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -264,6 +298,12 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + [[package]] name = "entities" version = "1.0.1" @@ -682,6 +722,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.16" @@ -1028,6 +1077,26 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "rcgen" version = "0.9.3" @@ -1160,6 +1229,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "sct" version = "0.7.0" diff --git a/Cargo.toml b/Cargo.toml index 44173fd20..05aa9664f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ serde_derive = "1.0" serde_yaml = "0.8" serde_json = "1.0" comrak = "0.13" +rayon = "1.8.0" regex = "1.3" sass-rs = "0.2" chrono = "0.4" diff --git a/src/lib.rs b/src/lib.rs index c0a048bc1..004184896 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ use std::convert::AsRef; use std::fs::{self, File}; use std::io::{self, Write}; use std::path::{Path, PathBuf}; +use rayon::prelude::*; struct Generator<'a> { handlebars: Handlebars<'a>, @@ -125,11 +126,9 @@ impl<'a> Generator<'a> { self.render_feed(blog)?; self.render_releases_feed(blog)?; - for (i, post) in blog.posts().iter().enumerate() { - let path = self.render_post(blog, post)?; - if i == 0 { - println!("└─ Latest post: {}\n", self.file_url(&path)); - } + let paths = blog.posts().par_iter().map(|post| self.render_post(blog, post)).collect::, _>>()?; + if let Some(path) = paths.first() { + println!("└─ Latest post: {}\n", self.file_url(path)); } Ok(())