further breaking up treehouse::generate into smaller submodules
This commit is contained in:
parent
fd40f99810
commit
2e14197fd1
|
@ -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());
|
||||||
|
|
30
crates/treehouse/src/generate/simple_template.rs
Normal file
30
crates/treehouse/src/generate/simple_template.rs
Normal 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:?}"),
|
||||||
|
}
|
||||||
|
}
|
113
crates/treehouse/src/generate/tree.rs
Normal file
113
crates/treehouse/src/generate/tree.rs
Normal 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:?}"),
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue