Fix posts not being written to new path on permalink changing

This commit is contained in:
Shadowfacts 2025-01-04 14:59:28 -05:00
parent 657f90c39c
commit 55b91944b2
3 changed files with 147 additions and 30 deletions

View File

@ -210,7 +210,8 @@ impl Rule for ReadPost {
struct MakeWritePosts { struct MakeWritePosts {
posts: DynamicInput<ReadPostOutput>, posts: DynamicInput<ReadPostOutput>,
article_template: Input<Templates>, article_template: Input<Templates>,
unwrapped_factory: DynamicNodeFactory<PathBuf, Post<HtmlContent>>, permalink_factory: DynamicNodeFactory<PathBuf, String>,
output_path_factory: DynamicNodeFactory<PathBuf, PathBuf>,
build_context_factory: DynamicNodeFactory<PathBuf, Context>, build_context_factory: DynamicNodeFactory<PathBuf, Context>,
render_factory: DynamicNodeFactory<PathBuf, ()>, render_factory: DynamicNodeFactory<PathBuf, ()>,
} }
@ -219,7 +220,8 @@ impl MakeWritePosts {
Self { Self {
posts, posts,
article_template: templates, article_template: templates,
unwrapped_factory: DynamicNodeFactory::new(), permalink_factory: DynamicNodeFactory::new(),
output_path_factory: DynamicNodeFactory::new(),
build_context_factory: DynamicNodeFactory::new(), build_context_factory: DynamicNodeFactory::new(),
render_factory: DynamicNodeFactory::new(), render_factory: DynamicNodeFactory::new(),
} }
@ -230,11 +232,17 @@ impl DynamicRule for MakeWritePosts {
fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec<Input<Self::ChildOutput>> { fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec<Input<Self::ChildOutput>> {
for post_input in self.posts.value().inputs.iter() { for post_input in self.posts.value().inputs.iter() {
if let Some(post) = post_input.value().as_ref() { 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 let context = self
.build_context_factory .build_context_factory
.add_node(ctx, post.path.clone(), |ctx| { .add_node(ctx, post.path.clone(), |ctx| {
ctx.add_rule(BuildTemplateContext::new( ctx.add_rule(BuildTemplateContext::new(
post.permalink().into(), permalink.clone().into(),
post_input.clone(), post_input.clone(),
|post_opt, ctx| { |post_opt, ctx| {
let post = post_opt.as_ref().unwrap(); let post = post_opt.as_ref().unwrap();
@ -244,24 +252,49 @@ impl DynamicRule for MakeWritePosts {
)) ))
}); });
let mut output_path = PathBuf::from(post.permalink()); let output_path =
output_path.push("index.html"); 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| { self.render_factory.add_node(ctx, post.path.clone(), |ctx| {
ctx.add_rule(RenderTemplate { ctx.add_rule(RenderTemplate {
name: "article", name: "article",
output_path, output_path: output_path.into(),
templates: self.article_template.clone(), templates: self.article_template.clone(),
context, 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.build_context_factory.finalize_nodes(ctx);
self.render_factory.all_nodes(ctx) self.render_factory.all_nodes(ctx)
} }
} }
#[derive(InputVisitable)]
struct PostPermalink(Input<ReadPostOutput>);
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<String>);
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<Option<Post<HtmlContent>>> into Vec<Post<HtmlContent>> /// Flattens Vec<Option<Post<HtmlContent>>> into Vec<Post<HtmlContent>>
#[derive(InputVisitable)] #[derive(InputVisitable)]
struct AllPosts(DynamicInput<ReadPostOutput>); struct AllPosts(DynamicInput<ReadPostOutput>);

View File

@ -56,9 +56,10 @@ impl DynamicRule for MakePostsByTags {
) -> Vec<Input<Self::ChildOutput>> { ) -> Vec<Input<Self::ChildOutput>> {
let mut all_tags = HashMap::new(); let mut all_tags = HashMap::new();
for post_input in self.posts().inputs.iter() { for post_input in self.posts().inputs.iter() {
let post = post_input.value(); if let Some(post) = post_input.value().as_ref() {
for tag in post.as_ref().unwrap().metadata.tags.iter().flatten() { for tag in post.metadata.tags.iter().flatten() {
all_tags.insert(tag.slug.clone(), tag.name.clone()); all_tags.insert(tag.slug.clone(), tag.name.clone());
}
} }
} }
for (slug, name) in all_tags { for (slug, name) in all_tags {
@ -87,10 +88,14 @@ impl Rule for PostsByTag {
.inputs .inputs
.iter() .iter()
.flat_map(|post_input| { .flat_map(|post_input| {
let post_ = post_input.value(); if let Some(post) = post_input.value().as_ref()
let post = post_.as_ref().unwrap(); && post
let mut tags = post.metadata.tags.iter().flatten(); .metadata
if tags.any(|t| t.slug == self.tag.slug) { .tags
.iter()
.flatten()
.any(|t| t.slug == self.tag.slug)
{
Some(Entry { Some(Entry {
permalink: post.permalink(), permalink: post.permalink(),
title: post.metadata.title.clone(), title: post.metadata.title.clone(),

View File

@ -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::{ use compute_graph::{
builder::GraphBuilder, builder::GraphBuilder,
node::NodeValue, node::NodeValue,
@ -82,14 +86,13 @@ static CB: Lazy<u64> = Lazy::new(|| {
.as_secs() .as_secs()
}); });
#[derive(InputVisitable)]
pub struct BuildTemplateContext<T, F> { pub struct BuildTemplateContext<T, F> {
permalink: Cow<'static, str>, permalink: TemplatePermalink,
input: Input<T>, input: Input<T>,
func: F, func: F,
} }
impl<T, F: Fn(&T, &mut Context) -> ()> BuildTemplateContext<T, F> { impl<T, F: Fn(&T, &mut Context) -> ()> BuildTemplateContext<T, F> {
pub fn new(permalink: Cow<'static, str>, input: Input<T>, func: F) -> Self { pub fn new(permalink: TemplatePermalink, input: Input<T>, func: F) -> Self {
Self { Self {
permalink, permalink,
input, input,
@ -97,46 +100,122 @@ impl<T, F: Fn(&T, &mut Context) -> ()> BuildTemplateContext<T, F> {
} }
} }
} }
impl<T, F> InputVisitable for BuildTemplateContext<T, F> {
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<T: 'static, F: Fn(&T, &mut Context) -> () + 'static> Rule for BuildTemplateContext<T, F> { impl<T: 'static, F: Fn(&T, &mut Context) -> () + 'static> Rule for BuildTemplateContext<T, F> {
type Output = Context; type Output = Context;
fn evaluate(&mut self) -> Self::Output { fn evaluate(&mut self) -> Self::Output {
let mut context = Context::new(); let mut context = Context::new();
(self.func)(&*self.input(), &mut context); (self.func)(&*self.input.value(), &mut context);
context.insert("_domain", &*DOMAIN); 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("_stylesheet_cache_buster", &*CB);
context.insert("_development", &cfg!(debug_assertions)); context.insert("_development", &cfg!(debug_assertions));
context.insert("_generated_at", &Local::now());
context context
} }
} }
#[derive(InputVisitable)] pub enum TemplatePermalink {
Constant(&'static str),
ConstantOwned(String),
Dynamic(Input<String>),
}
impl From<&'static str> for TemplatePermalink {
fn from(value: &'static str) -> Self {
Self::Constant(value)
}
}
impl From<String> for TemplatePermalink {
fn from(value: String) -> Self {
Self::ConstantOwned(value)
}
}
impl From<Input<String>> for TemplatePermalink {
fn from(value: Input<String>) -> Self {
Self::Dynamic(value)
}
}
pub struct RenderTemplate { pub struct RenderTemplate {
pub name: &'static str, pub name: &'static str,
pub output_path: PathBuf, pub output_path: TemplateOutputPath,
pub templates: Input<Templates>, pub templates: Input<Templates>,
pub context: Input<Context>, pub context: Input<Context>,
} }
// 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 { impl Rule for RenderTemplate {
type Output = (); type Output = ();
fn evaluate(&mut self) -> Self::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)); 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 let result = templates
.tera .tera
.render_to(&self.name, &*self.context(), writer); .render_to(&self.name, &*self.context.value(), writer);
if let Err(e) = result { if let Err(e) = result {
error!( error!("Error rendering template to {path:?}: {e:?}");
"Error rendering template to {:?}: {:?}",
&self.output_path, e
);
} }
} }
fn node_label(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 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<PathBuf>),
}
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<String> for TemplateOutputPath {
fn from(value: String) -> Self {
Self::Constant(value.into())
}
}
impl From<PathBuf> for TemplateOutputPath {
fn from(value: PathBuf) -> Self {
Self::Constant(value)
}
}
impl From<Input<PathBuf>> for TemplateOutputPath {
fn from(value: Input<PathBuf>) -> Self {
Self::Dynamic(value)
}
}
pub mod filters { pub mod filters {
use chrono::{DateTime, Datelike, Local}; use chrono::{DateTime, Datelike, Local};
use serde::Deserialize; use serde::Deserialize;