Add modding tutorials
This commit is contained in:
parent
49feefaedc
commit
22cbe75dc2
@ -88,7 +88,16 @@ impl<O: 'static, S: Synchronicity> GraphBuilder<O, S> {
|
||||
///
|
||||
/// Returns an [`Input`] representing the newly-added node, which can be used to construct rules.
|
||||
pub fn add_value<V: NodeValue>(&mut self, value: V) -> Input<V> {
|
||||
return self.add_node(ConstNode::new(value));
|
||||
return self.add_node(ConstNode::new(value, None));
|
||||
}
|
||||
|
||||
/// Adds a constant node with the given value to the graph.
|
||||
///
|
||||
/// The node's label will be the given string.
|
||||
///
|
||||
/// Returns an [`Input`] representing the newly-added node, which can be used to construct rules.
|
||||
pub fn add_named_value<V: NodeValue>(&mut self, value: V, label: String) -> Input<V> {
|
||||
return self.add_node(ConstNode::new(value, Some(label)));
|
||||
}
|
||||
|
||||
/// Adds an invalidatable node with the given value to the graph.
|
||||
|
@ -151,13 +151,15 @@ impl<T: PartialEq + 'static> NodeValue for T {
|
||||
|
||||
pub(crate) struct ConstNode<V, S> {
|
||||
value: Rc<RefCell<Option<V>>>,
|
||||
label: Option<String>,
|
||||
synchronicity: std::marker::PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<V, S> ConstNode<V, S> {
|
||||
pub(crate) fn new(value: V) -> Self {
|
||||
pub(crate) fn new(value: V, label: Option<String>) -> Self {
|
||||
Self {
|
||||
value: Rc::new(RefCell::new(Some(value))),
|
||||
label,
|
||||
synchronicity: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
@ -183,9 +185,13 @@ impl<V: NodeValue, S: Synchronicity> Node<V, S> for ConstNode<V, S> {
|
||||
|
||||
impl<V, S> std::fmt::Debug for ConstNode<V, S> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(ref label) = self.label {
|
||||
write!(f, "ConstNode<{}>({})", pretty_type_name::<V>(), label)
|
||||
} else {
|
||||
write!(f, "ConstNode<{}>", pretty_type_name::<V>())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct InvalidatableConstNode<V, S> {
|
||||
value: Rc<RefCell<Option<V>>>,
|
||||
|
@ -34,7 +34,9 @@
|
||||
<a href="/{{ tag.slug }}/">{{ tag.name }}</a>{% if loop.last %}.{% else %},{% endif %}
|
||||
</span>
|
||||
{% endfor %}
|
||||
<span title="{{ latest_post.word_count }} word{% if latest_post.word_count != 1 %}s{% endif %}">
|
||||
{{ latest_post.word_count | reading_time }} minute read.
|
||||
</span>
|
||||
</p>
|
||||
<div class="body-content">
|
||||
{% if latest_post.excerpt %}
|
||||
|
@ -25,8 +25,6 @@
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}{{ post.metadata.title }}{% endblock %}
|
||||
|
||||
{% block content -%}
|
||||
|
||||
<article itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
|
||||
@ -49,7 +47,9 @@
|
||||
<a href="/{{ tag.slug }}/">{{ tag.name }}</a>{% if loop.last %}.{% else %},{% endif %}
|
||||
</span>
|
||||
{% endfor %}
|
||||
<span title="{{ word_count }} word{% if word_count != 1 %}s{% endif %}">
|
||||
{{ word_count | reading_time }} minute read.
|
||||
</span>
|
||||
</p>
|
||||
<div class="body-content" itemprop="articleBody">
|
||||
{{ content }}
|
||||
|
27
site_test/layout/tutorial_post.html
Normal file
27
site_test/layout/tutorial_post.html
Normal file
@ -0,0 +1,27 @@
|
||||
{% extends "default" %}
|
||||
|
||||
{% block titlevariable %}
|
||||
{% set title = post.metadata.title ~ " | " ~ post.series_name %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content -%}
|
||||
|
||||
<article>
|
||||
<h1 class="headline">{{ post.metadata.title }}</h1>
|
||||
<p class="article-meta">
|
||||
Published on
|
||||
<time datetime="{{ post.metadata.date | iso_datetime }}">
|
||||
{{ post.metadata.date | pretty_date }},
|
||||
</time>
|
||||
in
|
||||
<a href="/tutorials/{{ post.series_slug }}/">{{ post.series_name }}</a>.
|
||||
<span title="{{ post.word_count }} word{% if post.word_count != 1 %}s{% endif %}">
|
||||
{{ post.word_count | reading_time }} minute read.
|
||||
</span>
|
||||
</p>
|
||||
<div class="body-content">
|
||||
{{ post.content }}
|
||||
</div>
|
||||
</article>
|
||||
|
||||
{%- endblock %}
|
30
site_test/layout/tutorial_series.html
Normal file
30
site_test/layout/tutorial_series.html
Normal file
@ -0,0 +1,30 @@
|
||||
{% extends "default" %}
|
||||
|
||||
{% block titlevariable %}
|
||||
{% set title = series_name %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content -%}
|
||||
|
||||
<h1 class="headline">{{ series_name }}</h1>
|
||||
|
||||
{% for entry in entries %}
|
||||
|
||||
<h2 class="headline">
|
||||
<a href="/tutorials/{{ series_slug }}/{{ entry.slug }}/">
|
||||
{{ entry.title }}
|
||||
</a>
|
||||
</h2>
|
||||
<p class="article-meta">
|
||||
Published on
|
||||
<time datetime="{{ entry.date | iso_datetime }}">
|
||||
{{ entry.date | pretty_date }}.
|
||||
</time>
|
||||
<span title="{{ entry.word_count }} word{% if entry.word_count != 1 %}s{% endif %}">
|
||||
{{ entry.word_count | reading_time }} minute read.
|
||||
</span>
|
||||
</p>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{%- endblock %}
|
31
site_test/tutorials.html
Normal file
31
site_test/tutorials.html
Normal file
@ -0,0 +1,31 @@
|
||||
{% extends "default" %}
|
||||
|
||||
{% block titlevariable %}
|
||||
{% set title = "Modding Tutorials" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content -%}
|
||||
|
||||
<h1 class="headline">Modding Tutorials</h1>
|
||||
|
||||
{% for series in entries %}
|
||||
|
||||
<h2 class="headline">
|
||||
<a href="/tutorials/{{ series.slug }}/">
|
||||
{{ series.name }}
|
||||
</a>
|
||||
</h2>
|
||||
<p class="article-meta">
|
||||
{{ series.post_count }}
|
||||
post{% if series.post_count != 1 %}s{% endif %}.
|
||||
{% if series.last_updated %}
|
||||
Last updated on
|
||||
<time datetime="{{ series.last_updated | iso_datetime }}">
|
||||
{{ series.last_updated | pretty_date }}.
|
||||
</time>
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{%- endblock %}
|
@ -48,7 +48,7 @@ pub fn make_graph(
|
||||
name: "archive",
|
||||
output_path: "/archive/index.html".into(),
|
||||
templates: archive_template,
|
||||
context,
|
||||
context: context.into(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ pub fn make_graph(
|
||||
name: "home",
|
||||
output_path: "index.html".into(),
|
||||
templates: home_template,
|
||||
context,
|
||||
context: context.into(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ mod rss;
|
||||
mod static_files;
|
||||
mod tags;
|
||||
mod templates;
|
||||
mod tutorials;
|
||||
mod tv;
|
||||
mod util;
|
||||
|
||||
@ -67,6 +68,12 @@ fn make_graph(watcher: Rc<RefCell<FileWatcher>>) -> anyhow::Result<AsyncGraph<()
|
||||
|
||||
let tv = tv::make_graph(&mut builder, default_template.clone(), Rc::clone(&watcher));
|
||||
|
||||
let tutorials = tutorials::make_graph(
|
||||
&mut builder,
|
||||
default_template.clone(),
|
||||
&mut *watcher.borrow_mut(),
|
||||
);
|
||||
|
||||
let not_found = not_found::make_graph(
|
||||
&mut builder,
|
||||
default_template.clone(),
|
||||
@ -82,6 +89,7 @@ fn make_graph(watcher: Rc<RefCell<FileWatcher>>) -> anyhow::Result<AsyncGraph<()
|
||||
statics,
|
||||
rss,
|
||||
tv,
|
||||
tutorials,
|
||||
not_found,
|
||||
]);
|
||||
builder.set_existing_output(output);
|
||||
|
@ -1,11 +1,14 @@
|
||||
use compute_graph::{builder::GraphBuilder, input::Input, synchronicity::Asynchronous};
|
||||
|
||||
use crate::generator::{
|
||||
templates::{AddTemplate, BuildTemplateContext, RenderTemplate},
|
||||
templates::{AddTemplate, RenderTemplate},
|
||||
util::content_path,
|
||||
};
|
||||
|
||||
use super::{FileWatcher, templates::Templates};
|
||||
use super::{
|
||||
FileWatcher,
|
||||
templates::{Templates, make_template_context},
|
||||
};
|
||||
|
||||
pub fn make_graph(
|
||||
builder: &mut GraphBuilder<(), Asynchronous>,
|
||||
@ -17,18 +20,11 @@ pub fn make_graph(
|
||||
builder.add_invalidatable_rule(AddTemplate::new("404", path.clone(), default_template));
|
||||
watcher.watch(path, move || invalidate.invalidate());
|
||||
|
||||
let void = builder.add_value(());
|
||||
let context = builder.add_rule(BuildTemplateContext::new(
|
||||
"/404.html".into(),
|
||||
void,
|
||||
|_, _| {},
|
||||
));
|
||||
|
||||
let render = builder.add_rule(RenderTemplate {
|
||||
name: "404",
|
||||
output_path: "404.html".into(),
|
||||
templates,
|
||||
context,
|
||||
context: make_template_context(&"/404.html".into()).into(),
|
||||
});
|
||||
|
||||
render
|
||||
|
@ -320,7 +320,7 @@ impl DynamicRule for MakeWritePosts {
|
||||
name: "article",
|
||||
output_path: output_path.into(),
|
||||
templates: self.article_template.clone(),
|
||||
context,
|
||||
context: context.into(),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ pub fn make_graph(
|
||||
) -> Input<()> {
|
||||
let by_tags = builder.add_dynamic_rule(MakePostsByTags::new(posts));
|
||||
|
||||
let template_path = content_path("tag.html");
|
||||
let template_path = content_path("layout/tag.html");
|
||||
let (tag_template, invalidate_template) = builder.add_invalidatable_rule(AddTemplate::new(
|
||||
"tag",
|
||||
template_path.clone(),
|
||||
@ -209,7 +209,7 @@ impl DynamicRule for MakeWriteTagPages {
|
||||
name: "tag",
|
||||
output_path: format!("/{slug}/index.html").into(),
|
||||
templates: self.templates.clone(),
|
||||
context: template_context,
|
||||
context: template_context.into(),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
@ -119,17 +119,8 @@ impl<T: 'static, F: Fn(&T, &mut Context) -> () + 'static> Rule for BuildTemplate
|
||||
type Output = Context;
|
||||
|
||||
fn evaluate(&mut self) -> Self::Output {
|
||||
let mut context = Context::new();
|
||||
let mut context = make_template_context(&self.permalink);
|
||||
(self.func)(&*self.input.value(), &mut context);
|
||||
context.insert("_domain", &*DOMAIN);
|
||||
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
|
||||
}
|
||||
|
||||
@ -142,6 +133,20 @@ impl<T: 'static, F: Fn(&T, &mut Context) -> () + 'static> Rule for BuildTemplate
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_template_context(permalink: &TemplatePermalink) -> Context {
|
||||
let mut context = Context::new();
|
||||
context.insert("_domain", &*DOMAIN);
|
||||
match 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(#[skip_visit] &'static str),
|
||||
@ -170,22 +175,28 @@ pub struct RenderTemplate {
|
||||
pub name: &'static str,
|
||||
pub output_path: TemplateOutputPath,
|
||||
pub templates: Input<Templates>,
|
||||
pub context: Input<Context>,
|
||||
pub context: RenderTemplateContext,
|
||||
}
|
||||
impl Rule for RenderTemplate {
|
||||
type Output = ();
|
||||
|
||||
fn evaluate(&mut self) -> Self::Output {
|
||||
let templates = self.templates.value();
|
||||
assert!(templates.tera.get_template_names().any(|n| n == self.name));
|
||||
let has_template = templates.tera.get_template_names().any(|n| n == self.name);
|
||||
if !has_template {
|
||||
error!("Missing template {:?}", self.name);
|
||||
return;
|
||||
}
|
||||
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.value(), writer);
|
||||
let context = match self.context {
|
||||
RenderTemplateContext::Constant(ref ctx) => ctx,
|
||||
RenderTemplateContext::Dynamic(ref input) => &input.value(),
|
||||
};
|
||||
let result = templates.tera.render_to(&self.name, context, writer);
|
||||
if let Err(e) = result {
|
||||
error!("Error rendering template to {path:?}: {e:?}");
|
||||
}
|
||||
@ -224,6 +235,21 @@ impl From<Input<PathBuf>> for TemplateOutputPath {
|
||||
Self::Dynamic(value)
|
||||
}
|
||||
}
|
||||
#[derive(InputVisitable)]
|
||||
pub enum RenderTemplateContext {
|
||||
Constant(#[skip_visit] Context),
|
||||
Dynamic(Input<Context>),
|
||||
}
|
||||
impl From<Context> for RenderTemplateContext {
|
||||
fn from(value: Context) -> Self {
|
||||
Self::Constant(value)
|
||||
}
|
||||
}
|
||||
impl From<Input<Context>> for RenderTemplateContext {
|
||||
fn from(value: Input<Context>) -> Self {
|
||||
Self::Dynamic(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod filters {
|
||||
use chrono::{DateTime, Datelike, Local};
|
||||
|
232
src/generator/tutorials.rs
Normal file
232
src/generator/tutorials.rs
Normal file
@ -0,0 +1,232 @@
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use compute_graph::input::InputVisitable;
|
||||
use compute_graph::rule::{DynamicNodeFactory, DynamicRule, DynamicRuleContext};
|
||||
use compute_graph::{builder::GraphBuilder, input::Input, synchronicity::Asynchronous};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::generator::templates::{AddTemplate, RenderTemplate, make_template_context};
|
||||
use crate::generator::util::{Combine, MapDynamicToVoid, word_count};
|
||||
|
||||
use super::markdown;
|
||||
use super::util::slugify::slugify;
|
||||
use super::util::{content_path, from_frontmatter};
|
||||
use super::{FileWatcher, templates::Templates};
|
||||
|
||||
pub fn make_graph(
|
||||
builder: &mut GraphBuilder<(), Asynchronous>,
|
||||
default_template: Input<Templates>,
|
||||
watcher: &mut FileWatcher,
|
||||
) -> Input<()> {
|
||||
let post_path = content_path("layout/tutorial_post.html");
|
||||
let (post_template, invalidate) = builder.add_invalidatable_rule(AddTemplate::new(
|
||||
"tutorial_post",
|
||||
post_path.clone(),
|
||||
default_template.clone(),
|
||||
));
|
||||
watcher.watch(post_path, move || invalidate.invalidate());
|
||||
|
||||
let series_path = content_path("layout/tutorial_series.html");
|
||||
let (series_template, invalidate) = builder.add_invalidatable_rule(AddTemplate::new(
|
||||
"tutorial_series",
|
||||
series_path.clone(),
|
||||
default_template.clone(),
|
||||
));
|
||||
watcher.watch(series_path, move || invalidate.invalidate());
|
||||
|
||||
let index_path = content_path("tutorials.html");
|
||||
let (index_template, invalidate) = builder.add_invalidatable_rule(AddTemplate::new(
|
||||
"tutorials",
|
||||
index_path.clone(),
|
||||
default_template,
|
||||
));
|
||||
watcher.watch(index_path, move || invalidate.invalidate());
|
||||
|
||||
let serieses = vec![
|
||||
read_series("forge-modding-1102", "Forge Mods for 1.10.2"),
|
||||
read_series("forge-modding-1112", "Forge Mods for 1.11.2"),
|
||||
read_series("forge-modding-112", "Forge Mods for 1.12"),
|
||||
];
|
||||
|
||||
let mut index_entries = vec![];
|
||||
let mut render_inputs = vec![];
|
||||
|
||||
for series in serieses {
|
||||
index_entries.push(TutorialIndexEntry {
|
||||
name: series.name,
|
||||
slug: series.slug,
|
||||
post_count: series.posts.len(),
|
||||
last_updated: series.posts.iter().map(|p| p.metadata.date).max(),
|
||||
});
|
||||
|
||||
let mut series_context =
|
||||
make_template_context(&format!("/tutorials/{}/", series.slug).into());
|
||||
let entries = series
|
||||
.posts
|
||||
.iter()
|
||||
.map(|post| TutorialSeriesIndexEntry {
|
||||
title: post.metadata.title.clone(),
|
||||
slug: post.slug.clone(),
|
||||
date: post.metadata.date,
|
||||
word_count: post.word_count,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
series_context.insert("entries", &entries);
|
||||
series_context.insert("series_name", series.name);
|
||||
series_context.insert("series_slug", series.slug);
|
||||
render_inputs.push(builder.add_rule(RenderTemplate {
|
||||
name: "tutorial_series",
|
||||
output_path: format!("tutorials/{}/index.html", series.slug).into(),
|
||||
templates: series_template.clone(),
|
||||
context: series_context.into(),
|
||||
}));
|
||||
|
||||
let render_dynamic = builder.add_dynamic_rule(MakeRenderTutorials::new(
|
||||
series.posts,
|
||||
post_template.clone(),
|
||||
));
|
||||
render_inputs.push(builder.add_rule(MapDynamicToVoid(render_dynamic)))
|
||||
}
|
||||
|
||||
let mut index_context = make_template_context(&"/tutorials/".into());
|
||||
index_context.insert("entries", &index_entries);
|
||||
render_inputs.push(builder.add_rule(RenderTemplate {
|
||||
name: "tutorials",
|
||||
output_path: "tutorials/index.html".into(),
|
||||
templates: index_template,
|
||||
context: index_context.into(),
|
||||
}));
|
||||
|
||||
Combine::make(builder, &render_inputs)
|
||||
}
|
||||
|
||||
fn read_series(slug: &'static str, name: &'static str) -> TutorialSeries {
|
||||
let mut path = content_path("tutorials");
|
||||
path.push(slug);
|
||||
let entries = std::fs::read_dir(path).expect("reading tutorial dir");
|
||||
let posts = entries
|
||||
.into_iter()
|
||||
.map(move |ent| {
|
||||
let ent = ent.expect("tutorial dir ent");
|
||||
assert!(
|
||||
ent.file_type()
|
||||
.expect("getting tutorial post file type")
|
||||
.is_file()
|
||||
);
|
||||
let path = ent.path();
|
||||
assert!(path.extension().unwrap().eq_ignore_ascii_case("md"));
|
||||
let str = std::fs::read_to_string(path).expect("reading tutorial post");
|
||||
TutorialPost::new(&str, slug, name)
|
||||
})
|
||||
.collect();
|
||||
|
||||
TutorialSeries { name, slug, posts }
|
||||
}
|
||||
|
||||
struct TutorialSeries {
|
||||
name: &'static str,
|
||||
slug: &'static str,
|
||||
posts: Vec<TutorialPost>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Serialize)]
|
||||
struct TutorialPost {
|
||||
series_slug: &'static str,
|
||||
series_name: &'static str,
|
||||
slug: String,
|
||||
content: String,
|
||||
word_count: u32,
|
||||
metadata: TutorialPostMetadata,
|
||||
}
|
||||
|
||||
impl TutorialPost {
|
||||
fn new(contents: &str, series_slug: &'static str, series_name: &'static str) -> Self {
|
||||
let (metadata, rest) =
|
||||
from_frontmatter::<TutorialPostMetadata>(contents).expect("parsing tutorial metadata");
|
||||
let slug = metadata
|
||||
.slug
|
||||
.clone()
|
||||
.unwrap_or_else(|| slugify(&metadata.title));
|
||||
|
||||
let mut buf = vec![];
|
||||
markdown::render(rest, &mut buf);
|
||||
let html = String::from_utf8(buf).unwrap();
|
||||
|
||||
let word_count = word_count::markdown(contents);
|
||||
|
||||
TutorialPost {
|
||||
series_slug,
|
||||
series_name,
|
||||
slug,
|
||||
content: html,
|
||||
word_count,
|
||||
metadata,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Serialize)]
|
||||
struct TutorialPostMetadata {
|
||||
title: String,
|
||||
date: DateTime<FixedOffset>,
|
||||
slug: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(InputVisitable)]
|
||||
struct MakeRenderTutorials {
|
||||
#[skip_visit]
|
||||
posts: Vec<TutorialPost>,
|
||||
#[skip_visit]
|
||||
templates: Input<Templates>,
|
||||
#[skip_visit]
|
||||
evaluated: bool,
|
||||
render_factory: DynamicNodeFactory<String, ()>,
|
||||
}
|
||||
impl MakeRenderTutorials {
|
||||
fn new(posts: Vec<TutorialPost>, templates: Input<Templates>) -> Self {
|
||||
Self {
|
||||
posts,
|
||||
templates,
|
||||
evaluated: false,
|
||||
render_factory: DynamicNodeFactory::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl DynamicRule for MakeRenderTutorials {
|
||||
type ChildOutput = ();
|
||||
fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec<Input<Self::ChildOutput>> {
|
||||
assert!(!self.evaluated);
|
||||
self.evaluated = true;
|
||||
for post in self.posts.drain(..) {
|
||||
let mut context = make_template_context(
|
||||
&format!("/tutorials/{}/{}/", post.series_slug, post.slug).into(),
|
||||
);
|
||||
context.insert("post", &post);
|
||||
self.render_factory.add_node(ctx, post.slug.clone(), |ctx| {
|
||||
ctx.add_rule(RenderTemplate {
|
||||
name: "tutorial_post",
|
||||
output_path: format!("tutorials/{}/{}/index.html", post.series_slug, post.slug)
|
||||
.into(),
|
||||
templates: self.templates.clone(),
|
||||
context: context.into(),
|
||||
})
|
||||
});
|
||||
}
|
||||
self.render_factory.all_nodes(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct TutorialIndexEntry {
|
||||
name: &'static str,
|
||||
slug: &'static str,
|
||||
post_count: usize,
|
||||
last_updated: Option<DateTime<FixedOffset>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct TutorialSeriesIndexEntry {
|
||||
title: String,
|
||||
slug: String,
|
||||
date: DateTime<FixedOffset>,
|
||||
word_count: u32,
|
||||
}
|
@ -42,7 +42,7 @@ pub fn make_graph(
|
||||
.borrow_mut()
|
||||
.watch(tv_path, move || invalidate.invalidate());
|
||||
|
||||
let show_path = content_path("show.html");
|
||||
let show_path = content_path("layout/show.html");
|
||||
let (show_template, invalidate) = builder.add_invalidatable_rule(AddTemplate::new(
|
||||
"show",
|
||||
show_path.clone(),
|
||||
@ -75,7 +75,7 @@ pub fn make_graph(
|
||||
name: "tv",
|
||||
output_path: "tv/index.html".into(),
|
||||
templates: index_template,
|
||||
context: index_context,
|
||||
context: index_context.into(),
|
||||
});
|
||||
|
||||
builder.add_rule(Combine(void_outputs, render_index))
|
||||
@ -309,7 +309,7 @@ impl DynamicRule for MakeRenderShows {
|
||||
name: "show",
|
||||
output_path: format!("/tv/{}/index.html", show.slug).into(),
|
||||
templates: self.templates.clone(),
|
||||
context,
|
||||
context: context.into(),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user