factor out simple templates into a separate SimpleTemplateDir

there's no need to bloat TreehouseDir with that logic
This commit is contained in:
りき萌 2025-07-10 16:50:41 +02:00
parent b792688776
commit 550c062327
7 changed files with 211 additions and 156 deletions

View file

@ -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<Dirs>,
sources: Arc<Sources>,
handlebars: Arc<Handlebars<'static>>,
dir_index: DirIndex,
}
impl TreehouseDir {
fn new(
dirs: Arc<Dirs>,
sources: Arc<Sources>,
handlebars: Arc<Handlebars<'static>>,
dir_index: DirIndex,
) -> Self {
Self {
dirs,
sources,
handlebars,
dir_index,
}
}
#[instrument("TreehouseDir::dir", skip(self))]
fn dir(&self, path: &VPath) -> Vec<VPathBuf> {
// 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<Content> {
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<VPathBuf, DirIndex>,
}
impl DirIndex {
#[instrument(name = "DirIndex::new", skip(paths))]
pub fn new<'a>(paths: impl Iterator<Item = &'a VPath>) -> 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<Dirs>, sources: Arc<Sources>) -> 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<Dirs>, sources: Arc<Sources>) -> 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()
}

View file

@ -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,

View file

@ -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<String> {
let base_template_data = BaseTemplateData::new(sources);
handlebars
.render(template_name, &base_template_data)
.context("failed to render template")
pub struct SimpleTemplateDir {
sources: Arc<Sources>,
handlebars: Arc<Handlebars<'static>>,
}
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<Sources>, handlebars: Arc<Handlebars<'static>>) -> Self {
Self {
sources,
handlebars,
}
}
#[instrument(name = "simple_template::generate", skip(self))]
fn generate(&self, template_name: &str) -> anyhow::Result<String> {
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<Content> {
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")
}
}

View file

@ -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<Dirs>,
sources: Arc<Sources>,
handlebars: Arc<Handlebars<'static>>,
dir_index: DirIndex,
}
impl TreehouseDir {
pub fn new(
dirs: Arc<Dirs>,
sources: Arc<Sources>,
handlebars: Arc<Handlebars<'static>>,
dir_index: DirIndex,
) -> Self {
Self {
dirs,
sources,
handlebars,
dir_index,
}
}
#[instrument("TreehouseDir::dir", skip(self))]
fn dir(&self, path: &VPath) -> Vec<VPathBuf> {
// 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<Content> {
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<VPathBuf, DirIndex>,
}
impl DirIndex {
#[instrument(name = "DirIndex::new", skip(paths))]
pub fn new<'a>(paths: impl Iterator<Item = &'a VPath>) -> 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
}
}

View file

@ -168,6 +168,12 @@ impl<'a> dyn Erased<'a> + 'a {
}
}
impl Dir for () {
fn query(&self, _path: &VPath, _query: &mut Query) {
// Noop implementation.
}
}
impl<T> Dir for &T
where
T: Dir,

View file

@ -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<T> {
inner: T,
}

View file

@ -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()
}
}
}