diff --git a/src/generate.rs b/src/generate.rs index ec43d98..7ea0bce 100644 --- a/src/generate.rs +++ b/src/generate.rs @@ -4,7 +4,7 @@ mod include_static_helper; mod simple_template; mod tree; -use std::{collections::HashMap, fmt, ops::ControlFlow, sync::Arc}; +use std::{ops::ControlFlow, sync::Arc}; use atom::FeedDir; use dir_helper::DirHelper; @@ -17,10 +17,14 @@ use crate::{ config::Config, dirs::Dirs, fun::seasons::Season, + generate::{ + simple_template::SimpleTemplateDir, + tree::{DirIndex, TreehouseDir}, + }, sources::Sources, vfs::{ - self, AnchoredAtExt, Cd, Content, ContentCache, Dir, DynDir, Entries, HtmlCanonicalize, - MemDir, Overlay, ToDynDir, VPath, VPathBuf, + self, layered_dir, AnchoredAtExt, Cd, Content, ContentCache, Dir, DynDir, Entries, + HtmlCanonicalize, MemDir, Overlay, ToDynDir, VPath, VPathBuf, }, }; @@ -76,134 +80,6 @@ fn load_templates(handlebars: &mut Handlebars, dir: &dyn Dir) { }); } -struct TreehouseDir { - dirs: Arc, - sources: Arc, - handlebars: Arc>, - dir_index: DirIndex, -} - -impl TreehouseDir { - fn new( - dirs: Arc, - sources: Arc, - handlebars: Arc>, - dir_index: DirIndex, - ) -> Self { - Self { - dirs, - sources, - handlebars, - dir_index, - } - } - - #[instrument("TreehouseDir::dir", skip(self))] - fn dir(&self, path: &VPath) -> Vec { - // NOTE: This does not include simple templates, because that's not really needed right now. - - let mut index = &self.dir_index; - for component in path.segments() { - if let Some(child) = index.children.get(component) { - index = child; - } else { - // There cannot possibly be any entries under an invalid path. - // Bail early. - return vec![]; - } - } - - index - .children - .values() - .map(|child| child.full_path.clone()) - .collect() - } - - #[instrument("TreehouseDir::content", skip(self))] - fn content(&self, path: &VPath) -> Option { - let path = if path.is_root() { - VPath::new_const("index") - } else { - path - }; - - self.sources - .treehouse - .files_by_tree_path - .get(path) - .map(|&file_id| { - Content::new( - "text/html", - 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(Content::new( - "text/html", - simple_template::generate_or_error( - &self.sources, - &self.handlebars, - template_name.as_str(), - ) - .into(), - )); - } - } - None - }) - } -} - -impl Dir for TreehouseDir { - fn query(&self, path: &VPath, query: &mut vfs::Query) { - query.provide(|| Entries(self.dir(path))); - query.try_provide(|| self.content(path)); - } -} - -impl fmt::Debug for TreehouseDir { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("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 handlebars = create_handlebars(&sources.config.site, dirs.static_.clone()); load_templates(&mut handlebars, &dirs.template); @@ -226,13 +102,16 @@ pub fn target(dirs: Arc, sources: Arc) -> DynDir { ); let dir_index = DirIndex::new(sources.treehouse.files_by_tree_path.keys().map(|x| &**x)); - let tree_view = TreehouseDir::new(dirs, sources, handlebars, dir_index); + let treehouse_dir = layered_dir(&[ + TreehouseDir::new(dirs, sources.clone(), handlebars.clone(), dir_index).to_dyn(), + SimpleTemplateDir::new(sources.clone(), handlebars.clone()).to_dyn(), + ]); - let tree_view = ContentCache::new(tree_view); + let tree_view = ContentCache::new(treehouse_dir); tree_view.warm_up(); let tree_view = HtmlCanonicalize::new(tree_view); - Overlay::new(tree_view.to_dyn(), root.to_dyn()) + layered_dir(&[tree_view.to_dyn(), root.to_dyn()]) .anchored_at(VPath::ROOT.to_owned()) .to_dyn() } diff --git a/src/generate/atom.rs b/src/generate/atom.rs index 9a39f7c..f41bb9b 100644 --- a/src/generate/atom.rs +++ b/src/generate/atom.rs @@ -4,8 +4,7 @@ use anyhow::Context; use chrono::{DateTime, Utc}; use handlebars::Handlebars; use serde::Serialize; -use tracing::{info, info_span, instrument}; -use ulid::Ulid; +use tracing::{info_span, instrument}; use crate::{ dirs::Dirs, diff --git a/src/generate/simple_template.rs b/src/generate/simple_template.rs index 6ed88e6..879485b 100644 --- a/src/generate/simple_template.rs +++ b/src/generate/simple_template.rs @@ -1,30 +1,70 @@ +use std::{fmt, sync::Arc}; + use anyhow::Context; use handlebars::Handlebars; use tracing::instrument; -use crate::sources::Sources; +use crate::{ + sources::Sources, + vfs::{Content, Dir, Query, VPath}, +}; 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 struct SimpleTemplateDir { + sources: Arc, + handlebars: Arc>, } -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:?}"), +impl SimpleTemplateDir { + pub fn new(sources: Arc, handlebars: Arc>) -> Self { + Self { + sources, + handlebars, + } + } + + #[instrument(name = "simple_template::generate", skip(self))] + fn generate(&self, template_name: &str) -> anyhow::Result { + let base_template_data = BaseTemplateData::new(&self.sources); + self.handlebars + .render(template_name, &base_template_data) + .context("failed to render template") + } + + fn generate_or_error(&self, template_name: &str) -> String { + match self.generate(template_name) { + Ok(html) => html, + Err(error) => format!("error: {error:?}"), + } + } + + #[instrument("TreehouseDir::content", skip(self))] + fn content(&self, path: &VPath) -> Option { + 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(Content::new( + "text/html", + self.generate_or_error(template_name.as_str()).into(), + )); + } + } + + None + } +} + +impl Dir for SimpleTemplateDir { + fn query(&self, path: &VPath, query: &mut Query) { + // NOTE: An implementation of Entries is not currently provided, because SimpleTemplateDir + // isn't used enough to need one. + query.try_provide(|| self.content(path)); + } +} + +impl fmt::Debug for SimpleTemplateDir { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("SimpleTemplateDir") } } diff --git a/src/generate/tree.rs b/src/generate/tree.rs index 9fc77aa..9ecd14c 100644 --- a/src/generate/tree.rs +++ b/src/generate/tree.rs @@ -1,3 +1,5 @@ +use std::{collections::HashMap, fmt, sync::Arc}; + use anyhow::{ensure, Context}; use handlebars::Handlebars; use serde::Serialize; @@ -5,10 +7,11 @@ use tracing::{info_span, instrument}; use crate::{ dirs::Dirs, - generate::BaseTemplateData, + generate::{simple_template, BaseTemplateData}, html::{breadcrumbs::breadcrumbs_to_html, tree}, sources::Sources, state::FileId, + vfs::{self, Content, Dir, Entries, VPath, VPathBuf}, }; #[derive(Serialize)] @@ -109,3 +112,113 @@ pub fn generate_or_error( Err(error) => format!("error: {error:?}"), } } + +pub struct TreehouseDir { + dirs: Arc, + sources: Arc, + handlebars: Arc>, + dir_index: DirIndex, +} + +impl TreehouseDir { + pub fn new( + dirs: Arc, + sources: Arc, + handlebars: Arc>, + dir_index: DirIndex, + ) -> Self { + Self { + dirs, + sources, + handlebars, + dir_index, + } + } + + #[instrument("TreehouseDir::dir", skip(self))] + fn dir(&self, path: &VPath) -> Vec { + // NOTE: This does not include simple templates, because that's not really needed right now. + + let mut index = &self.dir_index; + for component in path.segments() { + if let Some(child) = index.children.get(component) { + index = child; + } else { + // There cannot possibly be any entries under an invalid path. + // Bail early. + return vec![]; + } + } + + index + .children + .values() + .map(|child| child.full_path.clone()) + .collect() + } + + #[instrument("TreehouseDir::content", skip(self))] + fn content(&self, path: &VPath) -> Option { + let path = if path.is_root() { + VPath::new_const("index") + } else { + path + }; + + self.sources + .treehouse + .files_by_tree_path + .get(path) + .map(|&file_id| { + Content::new( + "text/html", + generate_or_error(&self.sources, &self.dirs, &self.handlebars, file_id).into(), + ) + }) + } +} + +impl Dir for TreehouseDir { + fn query(&self, path: &VPath, query: &mut vfs::Query) { + query.provide(|| Entries(self.dir(path))); + query.try_provide(|| self.content(path)); + } +} + +impl fmt::Debug for TreehouseDir { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("TreehouseDir") + } +} + +/// Acceleration structure for `dir` operations on [`TreehouseDir`]s. +#[derive(Debug, Default)] +pub 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 + } +} diff --git a/src/vfs.rs b/src/vfs.rs index 7d9c6b4..772592e 100644 --- a/src/vfs.rs +++ b/src/vfs.rs @@ -168,6 +168,12 @@ impl<'a> dyn Erased<'a> + 'a { } } +impl Dir for () { + fn query(&self, _path: &VPath, _query: &mut Query) { + // Noop implementation. + } +} + impl Dir for &T where T: Dir, diff --git a/src/vfs/html_canonicalize.rs b/src/vfs/html_canonicalize.rs index fb3ee54..bd3e772 100644 --- a/src/vfs/html_canonicalize.rs +++ b/src/vfs/html_canonicalize.rs @@ -2,6 +2,7 @@ use core::fmt; use super::{Dir, Query, VPath}; +/// This Dir exists to serve as a compatibility layer for very old links that end with .html. pub struct HtmlCanonicalize { inner: T, } diff --git a/src/vfs/overlay.rs b/src/vfs/overlay.rs index 13907ed..470a076 100644 --- a/src/vfs/overlay.rs +++ b/src/vfs/overlay.rs @@ -2,6 +2,8 @@ use std::fmt; use tracing::instrument; +use crate::vfs::ToDynDir; + use super::{entries, Dir, DynDir, Entries, Query, VPath, VPathBuf}; pub struct Overlay { @@ -38,3 +40,18 @@ impl fmt::Debug for Overlay { write!(f, "Overlay({:?}, {:?})", self.base, self.overlay) } } + +pub fn layered_dir(layers: &[DynDir]) -> DynDir { + match layers { + [] => ().to_dyn(), + [dir] => dir.clone(), + [left, right] => Overlay::new(left.clone(), right.clone()).to_dyn(), + [left, right, rest @ ..] => { + let mut overlay = Overlay::new(left.clone(), right.clone()); + for dir in rest { + overlay = Overlay::new(overlay.to_dyn(), dir.clone()); + } + overlay.to_dyn() + } + } +}