Fix posts not being written to new path on permalink changing
This commit is contained in:
parent
657f90c39c
commit
55b91944b2
@ -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>);
|
||||||
|
@ -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(),
|
||||||
|
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user