further breaking up treehouse::generate into smaller submodules

This commit is contained in:
liquidex 2024-11-27 19:02:30 +01:00
parent fd40f99810
commit 2e14197fd1
3 changed files with 210 additions and 194 deletions

View file

@ -1,9 +1,10 @@
mod dir_helper; mod dir_helper;
mod include_static_helper; mod include_static_helper;
mod simple_template;
mod tree;
use std::{collections::HashMap, fmt, ops::ControlFlow, sync::Arc}; use std::{collections::HashMap, fmt, ops::ControlFlow, sync::Arc};
use anyhow::{ensure, Context};
use dir_helper::DirHelper; use dir_helper::DirHelper;
use handlebars::{handlebars_helper, Handlebars}; use handlebars::{handlebars_helper, Handlebars};
use include_static_helper::IncludeStaticHelper; use include_static_helper::IncludeStaticHelper;
@ -14,32 +15,13 @@ use crate::{
config::Config, config::Config,
dirs::Dirs, dirs::Dirs,
fun::seasons::Season, fun::seasons::Season,
html::{breadcrumbs::breadcrumbs_to_html, tree::branches_to_html},
sources::Sources, sources::Sources,
state::FileId,
vfs::{ vfs::{
self, Cd, ContentCache, Dir, DirEntry, DynDir, HtmlCanonicalize, MemDir, Overlay, ToDynDir, self, Cd, ContentCache, Dir, DirEntry, DynDir, HtmlCanonicalize, MemDir, Overlay, ToDynDir,
VPath, VPathBuf, VPath, VPathBuf,
}, },
}; };
#[derive(Serialize)]
struct Page {
title: String,
thumbnail: Option<Thumbnail>,
scripts: Vec<String>,
styles: Vec<String>,
breadcrumbs: String,
tree_path: Option<String>,
tree: String,
}
#[derive(Serialize)]
struct Thumbnail {
url: String,
alt: Option<String>,
}
#[derive(Serialize)] #[derive(Serialize)]
struct BaseTemplateData<'a> { struct BaseTemplateData<'a> {
config: &'a Config, config: &'a Config,
@ -48,11 +30,37 @@ struct BaseTemplateData<'a> {
dev: bool, dev: bool,
} }
#[derive(Serialize)] impl<'a> BaseTemplateData<'a> {
struct PageTemplateData<'a> { fn new(sources: &'a Sources) -> Self {
#[serde(flatten)] Self {
base: &'a BaseTemplateData<'a>, config: &sources.config,
page: Page, 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<Dirs>,
sources: Arc<Sources>,
dir_index: DirIndex,
handlebars: Handlebars<'static>,
}
impl TreehouseDir {
fn new(dirs: Arc<Dirs>, sources: Arc<Sources>, 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> { 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<String> {
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<String> {
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<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
}
}
struct TreehouseDir {
dirs: Arc<Dirs>,
sources: Arc<Sources>,
dir_index: DirIndex,
handlebars: Handlebars<'static>,
}
impl TreehouseDir {
fn new(dirs: Arc<Dirs>, sources: Arc<Sources>, 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 { impl Dir for TreehouseDir {
#[instrument("TreehouseDir::dir", skip(self))] #[instrument("TreehouseDir::dir", skip(self))]
fn dir(&self, path: &VPath) -> Vec<DirEntry> { fn dir(&self, path: &VPath) -> Vec<DirEntry> {
@ -290,14 +131,14 @@ impl Dir for TreehouseDir {
.files_by_tree_path .files_by_tree_path
.get(path) .get(path)
.map(|&file_id| { .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(|| { .or_else(|| {
if path.file_name().is_some_and(|s| !s.starts_with('_')) { if path.file_name().is_some_and(|s| !s.starts_with('_')) {
let template_name = path.with_extension("hbs"); let template_name = path.with_extension("hbs");
if self.handlebars.has_template(template_name.as_str()) { if self.handlebars.has_template(template_name.as_str()) {
return Some( return Some(
generate_simple_template_or_error( simple_template::generate_or_error(
&self.sources, &self.sources,
&self.handlebars, &self.handlebars,
template_name.as_str(), 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<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 { pub fn target(dirs: Arc<Dirs>, sources: Arc<Sources>) -> DynDir {
let mut root = MemDir::new(); let mut root = MemDir::new();
root.add(VPath::new("static"), dirs.static_.clone()); root.add(VPath::new("static"), dirs.static_.clone());

View file

@ -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<String> {
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:?}"),
}
}

View file

@ -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<Thumbnail>,
scripts: Vec<String>,
styles: Vec<String>,
breadcrumbs: String,
tree_path: Option<String>,
tree: String,
}
#[derive(Serialize)]
struct Thumbnail {
url: String,
alt: Option<String>,
}
#[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<String> {
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:?}"),
}
}