remove treehouse-format crate and collapse everything into src

This commit is contained in:
りき萌 2025-07-10 16:50:41 +02:00
parent ca127a9411
commit b792688776
66 changed files with 145 additions and 112 deletions

238
src/generate.rs Normal file
View file

@ -0,0 +1,238 @@
mod atom;
mod dir_helper;
mod include_static_helper;
mod simple_template;
mod tree;
use std::{collections::HashMap, fmt, ops::ControlFlow, sync::Arc};
use atom::FeedDir;
use dir_helper::DirHelper;
use handlebars::{handlebars_helper, Handlebars};
use include_static_helper::IncludeStaticHelper;
use serde::Serialize;
use tracing::{error, info_span, instrument};
use crate::{
config::Config,
dirs::Dirs,
fun::seasons::Season,
sources::Sources,
vfs::{
self, AnchoredAtExt, Cd, Content, ContentCache, Dir, DynDir, Entries, HtmlCanonicalize,
MemDir, Overlay, ToDynDir, VPath, VPathBuf,
},
};
#[derive(Serialize)]
struct BaseTemplateData<'a> {
config: &'a Config,
import_map: String,
season: Option<Season>,
dev: bool,
feeds: Vec<String>,
}
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),
feeds: sources.treehouse.feeds_by_name.keys().cloned().collect(),
}
}
}
fn create_handlebars(site: &str, static_: DynDir) -> Handlebars<'static> {
let mut handlebars = Handlebars::new();
handlebars_helper!(cat: |a: String, b: String| a + &b);
handlebars.register_helper("cat", Box::new(cat));
handlebars.register_helper("asset", Box::new(DirHelper::new(site, static_.clone())));
handlebars.register_helper(
"include_static",
Box::new(IncludeStaticHelper::new(static_)),
);
handlebars
}
#[instrument(skip(handlebars))]
fn load_templates(handlebars: &mut Handlebars, dir: &dyn Dir) {
vfs::walk_dir_rec(dir, VPath::ROOT, &mut |path| {
if path.extension() == Some("hbs") {
if let Some(content) = vfs::query::<Content>(dir, path).and_then(|c| c.string().ok()) {
let _span = info_span!("register_template", ?path).entered();
if let Err(err) = handlebars.register_template_string(path.as_str(), content) {
error!("in template: {err}");
}
}
}
ControlFlow::Continue(())
});
}
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);
let handlebars = Arc::new(handlebars);
let mut root = MemDir::new();
root.add(
VPath::new("feed"),
ContentCache::new(FeedDir::new(
dirs.clone(),
sources.clone(),
handlebars.clone(),
))
.to_dyn(),
);
root.add(VPath::new("static"), dirs.static_.clone());
root.add(
VPath::new("robots.txt"),
Cd::new(dirs.static_.clone(), VPathBuf::new("robots.txt")).to_dyn(),
);
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 tree_view = ContentCache::new(tree_view);
tree_view.warm_up();
let tree_view = HtmlCanonicalize::new(tree_view);
Overlay::new(tree_view.to_dyn(), root.to_dyn())
.anchored_at(VPath::ROOT.to_owned())
.to_dyn()
}