diff --git a/src/generator/posts.rs b/src/generator/posts.rs index e88ebec..559ade7 100644 --- a/src/generator/posts.rs +++ b/src/generator/posts.rs @@ -210,7 +210,8 @@ impl Rule for ReadPost { struct MakeWritePosts { posts: DynamicInput, article_template: Input, - unwrapped_factory: DynamicNodeFactory>, + permalink_factory: DynamicNodeFactory, + output_path_factory: DynamicNodeFactory, build_context_factory: DynamicNodeFactory, render_factory: DynamicNodeFactory, } @@ -219,7 +220,8 @@ impl MakeWritePosts { Self { posts, article_template: templates, - unwrapped_factory: DynamicNodeFactory::new(), + permalink_factory: DynamicNodeFactory::new(), + output_path_factory: DynamicNodeFactory::new(), build_context_factory: DynamicNodeFactory::new(), render_factory: DynamicNodeFactory::new(), } @@ -230,11 +232,17 @@ impl DynamicRule for MakeWritePosts { fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec> { for post_input in self.posts.value().inputs.iter() { if let Some(post) = post_input.value().as_ref() { + let permalink = self + .permalink_factory + .add_node(ctx, post.path.clone(), |ctx| { + ctx.add_rule(PostPermalink(post_input.clone())) + }); + let context = self .build_context_factory .add_node(ctx, post.path.clone(), |ctx| { ctx.add_rule(BuildTemplateContext::new( - post.permalink().into(), + permalink.clone().into(), post_input.clone(), |post_opt, ctx| { let post = post_opt.as_ref().unwrap(); @@ -244,24 +252,49 @@ impl DynamicRule for MakeWritePosts { )) }); - let mut output_path = PathBuf::from(post.permalink()); - output_path.push("index.html"); + let output_path = + self.output_path_factory + .add_node(ctx, post.path.clone(), |ctx| { + ctx.add_rule(PostOutputPath(permalink)) + }); + self.render_factory.add_node(ctx, post.path.clone(), |ctx| { ctx.add_rule(RenderTemplate { name: "article", - output_path, + output_path: output_path.into(), templates: self.article_template.clone(), context, }) }); } } - self.unwrapped_factory.finalize_nodes(ctx); + self.permalink_factory.finalize_nodes(ctx); + self.output_path_factory.finalize_nodes(ctx); self.build_context_factory.finalize_nodes(ctx); self.render_factory.all_nodes(ctx) } } +#[derive(InputVisitable)] +struct PostPermalink(Input); +impl Rule for PostPermalink { + type Output = String; + fn evaluate(&mut self) -> Self::Output { + self.input_0().as_ref().unwrap().permalink() + } +} + +#[derive(InputVisitable)] +struct PostOutputPath(Input); +impl Rule for PostOutputPath { + type Output = PathBuf; + fn evaluate(&mut self) -> Self::Output { + let mut path = PathBuf::from(&*self.input_0()); + path.push("index.html"); + path + } +} + /// Flattens Vec>> into Vec> #[derive(InputVisitable)] struct AllPosts(DynamicInput); diff --git a/src/generator/tags.rs b/src/generator/tags.rs index dda3b06..168ab57 100644 --- a/src/generator/tags.rs +++ b/src/generator/tags.rs @@ -56,9 +56,10 @@ impl DynamicRule for MakePostsByTags { ) -> Vec> { let mut all_tags = HashMap::new(); for post_input in self.posts().inputs.iter() { - let post = post_input.value(); - for tag in post.as_ref().unwrap().metadata.tags.iter().flatten() { - all_tags.insert(tag.slug.clone(), tag.name.clone()); + if let Some(post) = post_input.value().as_ref() { + for tag in post.metadata.tags.iter().flatten() { + all_tags.insert(tag.slug.clone(), tag.name.clone()); + } } } for (slug, name) in all_tags { @@ -87,10 +88,14 @@ impl Rule for PostsByTag { .inputs .iter() .flat_map(|post_input| { - let post_ = post_input.value(); - let post = post_.as_ref().unwrap(); - let mut tags = post.metadata.tags.iter().flatten(); - if tags.any(|t| t.slug == self.tag.slug) { + if let Some(post) = post_input.value().as_ref() + && post + .metadata + .tags + .iter() + .flatten() + .any(|t| t.slug == self.tag.slug) + { Some(Entry { permalink: post.permalink(), title: post.metadata.title.clone(), diff --git a/src/generator/templates.rs b/src/generator/templates.rs index 6f676f4..5a9c19f 100644 --- a/src/generator/templates.rs +++ b/src/generator/templates.rs @@ -1,5 +1,9 @@ -use std::{borrow::Cow, path::PathBuf, time::SystemTime}; +use std::{ + path::{Path, PathBuf}, + time::SystemTime, +}; +use chrono::Local; use compute_graph::{ builder::GraphBuilder, node::NodeValue, @@ -82,14 +86,13 @@ static CB: Lazy = Lazy::new(|| { .as_secs() }); -#[derive(InputVisitable)] pub struct BuildTemplateContext { - permalink: Cow<'static, str>, + permalink: TemplatePermalink, input: Input, func: F, } impl ()> BuildTemplateContext { - pub fn new(permalink: Cow<'static, str>, input: Input, func: F) -> Self { + pub fn new(permalink: TemplatePermalink, input: Input, func: F) -> Self { Self { permalink, input, @@ -97,46 +100,122 @@ impl ()> BuildTemplateContext { } } } +impl InputVisitable for BuildTemplateContext { + fn visit_inputs(&self, visitor: &mut impl compute_graph::rule::InputVisitor) { + if let TemplatePermalink::Dynamic(ref input) = self.permalink { + visitor.visit(input); + } + visitor.visit(&self.input); + } +} impl () + 'static> Rule for BuildTemplateContext { type Output = Context; fn evaluate(&mut self) -> Self::Output { let mut context = Context::new(); - (self.func)(&*self.input(), &mut context); + (self.func)(&*self.input.value(), &mut context); context.insert("_domain", &*DOMAIN); - context.insert("_permalink", &self.permalink); + match &self.permalink { + TemplatePermalink::Constant(s) => context.insert("_permalink", s), + TemplatePermalink::ConstantOwned(s) => context.insert("_permalink", s), + TemplatePermalink::Dynamic(input) => context.insert("_permalink", &*input.value()), + } context.insert("_stylesheet_cache_buster", &*CB); context.insert("_development", &cfg!(debug_assertions)); + context.insert("_generated_at", &Local::now()); context } } -#[derive(InputVisitable)] +pub enum TemplatePermalink { + Constant(&'static str), + ConstantOwned(String), + Dynamic(Input), +} +impl From<&'static str> for TemplatePermalink { + fn from(value: &'static str) -> Self { + Self::Constant(value) + } +} +impl From for TemplatePermalink { + fn from(value: String) -> Self { + Self::ConstantOwned(value) + } +} +impl From> for TemplatePermalink { + fn from(value: Input) -> Self { + Self::Dynamic(value) + } +} + pub struct RenderTemplate { pub name: &'static str, - pub output_path: PathBuf, + pub output_path: TemplateOutputPath, pub templates: Input, pub context: Input, } +// TODO: derive InputVisitable for enums? +// make Input impl InputVisitable, then the derived impl can just call visit_inputs on everything +impl InputVisitable for RenderTemplate { + fn visit_inputs(&self, visitor: &mut impl compute_graph::rule::InputVisitor) { + if let TemplateOutputPath::Dynamic(ref input) = self.output_path { + visitor.visit(input); + } + visitor.visit(&self.templates); + visitor.visit(&self.context); + } +} impl Rule for RenderTemplate { type Output = (); fn evaluate(&mut self) -> Self::Output { - let templates = self.templates(); + let templates = self.templates.value(); assert!(templates.tera.get_template_names().any(|n| n == self.name)); - let writer = output_writer(&self.output_path).expect("output writer"); + let path: &Path = match self.output_path { + TemplateOutputPath::Constant(ref p) => p, + TemplateOutputPath::Dynamic(ref input) => &input.value(), + }; + let writer = output_writer(path).expect("output writer"); let result = templates .tera - .render_to(&self.name, &*self.context(), writer); + .render_to(&self.name, &*self.context.value(), writer); if let Err(e) = result { - error!( - "Error rendering template to {:?}: {:?}", - &self.output_path, e - ); + error!("Error rendering template to {path:?}: {e:?}"); } } fn node_label(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self.output_path.display()) + match self.output_path { + TemplateOutputPath::Constant(ref p) => write!(f, "{}", p.display()), + TemplateOutputPath::Dynamic(ref input) => write!(f, "{}", input.value().display()), + } + } +} +pub enum TemplateOutputPath { + Constant(PathBuf), + Dynamic(Input), +} +impl<'a> From<&'a str> for TemplateOutputPath { + fn from(value: &'a str) -> Self { + Self::Constant(value.into()) + } +} +// TODO: i feel like it should be possible to unify this impl with the &'a str one above +impl From for TemplateOutputPath { + fn from(value: String) -> Self { + Self::Constant(value.into()) + } +} +impl From for TemplateOutputPath { + fn from(value: PathBuf) -> Self { + Self::Constant(value) + } +} +impl From> for TemplateOutputPath { + fn from(value: Input) -> Self { + Self::Dynamic(value) + } +} + pub mod filters { use chrono::{DateTime, Datelike, Local}; use serde::Deserialize;