diff --git a/crates/treehouse/src/generate.rs b/crates/treehouse/src/generate.rs index f2b6ab7..0f32ccc 100644 --- a/crates/treehouse/src/generate.rs +++ b/crates/treehouse/src/generate.rs @@ -1,9 +1,10 @@ mod dir_helper; mod include_static_helper; +mod simple_template; +mod tree; use std::{collections::HashMap, fmt, ops::ControlFlow, sync::Arc}; -use anyhow::{ensure, Context}; use dir_helper::DirHelper; use handlebars::{handlebars_helper, Handlebars}; use include_static_helper::IncludeStaticHelper; @@ -14,32 +15,13 @@ use crate::{ config::Config, dirs::Dirs, fun::seasons::Season, - html::{breadcrumbs::breadcrumbs_to_html, tree::branches_to_html}, sources::Sources, - state::FileId, vfs::{ self, Cd, ContentCache, Dir, DirEntry, DynDir, HtmlCanonicalize, MemDir, Overlay, ToDynDir, VPath, VPathBuf, }, }; -#[derive(Serialize)] -struct Page { - title: String, - thumbnail: Option, - scripts: Vec, - styles: Vec, - breadcrumbs: String, - tree_path: Option, - tree: String, -} - -#[derive(Serialize)] -struct Thumbnail { - url: String, - alt: Option, -} - #[derive(Serialize)] struct BaseTemplateData<'a> { config: &'a Config, @@ -48,11 +30,37 @@ struct BaseTemplateData<'a> { dev: bool, } -#[derive(Serialize)] -struct PageTemplateData<'a> { - #[serde(flatten)] - base: &'a BaseTemplateData<'a>, - page: Page, +impl<'a> BaseTemplateData<'a> { + fn new(sources: &'a Sources) -> Self { + Self { + config: &sources.config, + import_map: serde_json::to_string_pretty(&sources.import_map) + .expect("import map should be serializable to JSON"), + season: Season::current(), + dev: cfg!(debug_assertions), + } + } +} + +struct TreehouseDir { + dirs: Arc, + sources: Arc, + dir_index: DirIndex, + handlebars: Handlebars<'static>, +} + +impl TreehouseDir { + fn new(dirs: Arc, sources: Arc, dir_index: DirIndex) -> Self { + let mut handlebars = create_handlebars(&sources.config.site, dirs.static_.clone()); + load_templates(&mut handlebars, &dirs.template); + + Self { + dirs, + sources, + dir_index, + handlebars, + } + } } fn create_handlebars(site: &str, static_: DynDir) -> Handlebars<'static> { @@ -85,173 +93,6 @@ fn load_templates(handlebars: &mut Handlebars, dir: &dyn Dir) { }); } -#[instrument(skip(sources, handlebars))] -fn generate_simple_template( - sources: &Sources, - handlebars: &Handlebars, - template_name: &str, -) -> anyhow::Result { - let base_template_data = BaseTemplateData { - config: &sources.config, - import_map: serde_json::to_string_pretty(&sources.import_map) - .expect("import map should be serializable to JSON"), - season: Season::current(), - dev: cfg!(debug_assertions), - }; - handlebars - .render(template_name, &base_template_data) - .context("failed to render template") -} - -fn generate_simple_template_or_error( - sources: &Sources, - handlebars: &Handlebars, - template_name: &str, -) -> String { - match generate_simple_template(sources, handlebars, template_name) { - Ok(html) => html, - Err(error) => format!("error: {error:?}"), - } -} - -#[instrument(skip(sources, dirs, handlebars))] -fn generate_tree( - sources: &Sources, - dirs: &Dirs, - handlebars: &Handlebars, - file_id: FileId, -) -> anyhow::Result { - let breadcrumbs = breadcrumbs_to_html(&sources.config, &sources.navigation_map, file_id); - - let roots = sources - .treehouse - .roots - .get(&file_id) - .expect("tree should have been added to the treehouse"); - - let tree = { - let _span = info_span!("generate_tree::branches_to_html").entered(); - let mut tree = String::new(); - branches_to_html( - &mut tree, - &sources.treehouse, - &sources.config, - dirs, - file_id, - &roots.branches, - ); - tree - }; - - let base_template_data = BaseTemplateData { - config: &sources.config, - import_map: serde_json::to_string_pretty(&sources.import_map) - .expect("import map should be serializable to JSON"), - season: Season::current(), - dev: cfg!(debug_assertions), - }; - - let template_data = PageTemplateData { - base: &base_template_data, - page: Page { - title: roots.attributes.title.clone(), - thumbnail: roots - .attributes - .thumbnail - .as_ref() - .map(|thumbnail| Thumbnail { - url: sources.config.pic_url(&*dirs.pic, &thumbnail.id), - alt: thumbnail.alt.clone(), - }), - scripts: roots.attributes.scripts.clone(), - styles: roots.attributes.styles.clone(), - breadcrumbs, - tree_path: sources.treehouse.tree_path(file_id).map(|s| s.to_string()), - tree, - }, - }; - let template_name = roots - .attributes - .template - .clone() - .unwrap_or_else(|| "_tree.hbs".into()); - - ensure!( - handlebars.has_template(&template_name), - "template {template_name} does not exist" - ); - - let _span = info_span!("handlebars::render").entered(); - handlebars - .render(&template_name, &template_data) - .context("template rendering failed") -} - -fn generate_tree_or_error( - sources: &Sources, - dirs: &Dirs, - handlebars: &Handlebars, - file_id: FileId, -) -> String { - match generate_tree(sources, dirs, handlebars, file_id) { - Ok(html) => html, - Err(error) => format!("error: {error:?}"), - } -} - -/// Acceleration structure for `dir` operations on [`TreehouseDir`]s. -#[derive(Debug, Default)] -struct DirIndex { - full_path: VPathBuf, - children: HashMap, -} - -impl DirIndex { - #[instrument(name = "DirIndex::new", skip(paths))] - pub fn new<'a>(paths: impl Iterator) -> Self { - let mut root = DirIndex::default(); - - for path in paths { - let mut parent = &mut root; - let mut full_path = VPath::ROOT.to_owned(); - for segment in path.segments() { - full_path.push(segment); - let child = parent - .children - .entry(segment.to_owned()) - .or_insert_with(|| DirIndex { - full_path: full_path.clone(), - children: HashMap::new(), - }); - parent = child; - } - } - - root - } -} - -struct TreehouseDir { - dirs: Arc, - sources: Arc, - dir_index: DirIndex, - handlebars: Handlebars<'static>, -} - -impl TreehouseDir { - fn new(dirs: Arc, sources: Arc, dir_index: DirIndex) -> Self { - let mut handlebars = create_handlebars(&sources.config.site, dirs.static_.clone()); - load_templates(&mut handlebars, &dirs.template); - - Self { - dirs, - sources, - dir_index, - handlebars, - } - } -} - impl Dir for TreehouseDir { #[instrument("TreehouseDir::dir", skip(self))] fn dir(&self, path: &VPath) -> Vec { @@ -290,14 +131,14 @@ impl Dir for TreehouseDir { .files_by_tree_path .get(path) .map(|&file_id| { - generate_tree_or_error(&self.sources, &self.dirs, &self.handlebars, file_id).into() + tree::generate_or_error(&self.sources, &self.dirs, &self.handlebars, file_id).into() }) .or_else(|| { if path.file_name().is_some_and(|s| !s.starts_with('_')) { let template_name = path.with_extension("hbs"); if self.handlebars.has_template(template_name.as_str()) { return Some( - generate_simple_template_or_error( + simple_template::generate_or_error( &self.sources, &self.handlebars, template_name.as_str(), @@ -321,6 +162,38 @@ impl fmt::Debug for TreehouseDir { } } +/// Acceleration structure for `dir` operations on [`TreehouseDir`]s. +#[derive(Debug, Default)] +struct DirIndex { + full_path: VPathBuf, + children: HashMap, +} + +impl DirIndex { + #[instrument(name = "DirIndex::new", skip(paths))] + pub fn new<'a>(paths: impl Iterator) -> Self { + let mut root = DirIndex::default(); + + for path in paths { + let mut parent = &mut root; + let mut full_path = VPath::ROOT.to_owned(); + for segment in path.segments() { + full_path.push(segment); + let child = parent + .children + .entry(segment.to_owned()) + .or_insert_with(|| DirIndex { + full_path: full_path.clone(), + children: HashMap::new(), + }); + parent = child; + } + } + + root + } +} + pub fn target(dirs: Arc, sources: Arc) -> DynDir { let mut root = MemDir::new(); root.add(VPath::new("static"), dirs.static_.clone()); diff --git a/crates/treehouse/src/generate/simple_template.rs b/crates/treehouse/src/generate/simple_template.rs new file mode 100644 index 0000000..6ed88e6 --- /dev/null +++ b/crates/treehouse/src/generate/simple_template.rs @@ -0,0 +1,30 @@ +use anyhow::Context; +use handlebars::Handlebars; +use tracing::instrument; + +use crate::sources::Sources; + +use super::BaseTemplateData; + +#[instrument(name = "simple_template::generate", skip(sources, handlebars))] +pub fn generate( + sources: &Sources, + handlebars: &Handlebars, + template_name: &str, +) -> anyhow::Result { + let base_template_data = BaseTemplateData::new(sources); + handlebars + .render(template_name, &base_template_data) + .context("failed to render template") +} + +pub fn generate_or_error( + sources: &Sources, + handlebars: &Handlebars, + template_name: &str, +) -> String { + match generate(sources, handlebars, template_name) { + Ok(html) => html, + Err(error) => format!("error: {error:?}"), + } +} diff --git a/crates/treehouse/src/generate/tree.rs b/crates/treehouse/src/generate/tree.rs new file mode 100644 index 0000000..5d08e5d --- /dev/null +++ b/crates/treehouse/src/generate/tree.rs @@ -0,0 +1,113 @@ +use anyhow::{ensure, Context}; +use handlebars::Handlebars; +use serde::Serialize; +use tracing::{info_span, instrument}; + +use crate::{ + dirs::Dirs, + generate::BaseTemplateData, + html::{breadcrumbs::breadcrumbs_to_html, tree::branches_to_html}, + sources::Sources, + state::FileId, +}; + +#[derive(Serialize)] +struct Page { + title: String, + thumbnail: Option, + scripts: Vec, + styles: Vec, + breadcrumbs: String, + tree_path: Option, + tree: String, +} + +#[derive(Serialize)] +struct Thumbnail { + url: String, + alt: Option, +} + +#[derive(Serialize)] +struct PageTemplateData<'a> { + #[serde(flatten)] + base: &'a BaseTemplateData<'a>, + page: Page, +} + +#[instrument(skip(sources, dirs, handlebars))] +pub fn generate( + sources: &Sources, + dirs: &Dirs, + handlebars: &Handlebars, + file_id: FileId, +) -> anyhow::Result { + let breadcrumbs = breadcrumbs_to_html(&sources.config, &sources.navigation_map, file_id); + + let roots = sources + .treehouse + .roots + .get(&file_id) + .expect("tree should have been added to the treehouse"); + + let tree = { + let _span = info_span!("generate_tree::branches_to_html").entered(); + let mut tree = String::new(); + branches_to_html( + &mut tree, + &sources.treehouse, + &sources.config, + dirs, + file_id, + &roots.branches, + ); + tree + }; + + let template_data = PageTemplateData { + base: &BaseTemplateData::new(sources), + page: Page { + title: roots.attributes.title.clone(), + thumbnail: roots + .attributes + .thumbnail + .as_ref() + .map(|thumbnail| Thumbnail { + url: sources.config.pic_url(&*dirs.pic, &thumbnail.id), + alt: thumbnail.alt.clone(), + }), + scripts: roots.attributes.scripts.clone(), + styles: roots.attributes.styles.clone(), + breadcrumbs, + tree_path: sources.treehouse.tree_path(file_id).map(|s| s.to_string()), + tree, + }, + }; + let template_name = roots + .attributes + .template + .clone() + .unwrap_or_else(|| "_tree.hbs".into()); + + ensure!( + handlebars.has_template(&template_name), + "template {template_name} does not exist" + ); + + let _span = info_span!("handlebars::render").entered(); + handlebars + .render(&template_name, &template_data) + .context("template rendering failed") +} + +pub fn generate_or_error( + sources: &Sources, + dirs: &Dirs, + handlebars: &Handlebars, + file_id: FileId, +) -> String { + match generate(sources, dirs, handlebars, file_id) { + Ok(html) => html, + Err(error) => format!("error: {error:?}"), + } +}