mirror of
https://github.com/zyllian/webdog.git
synced 2025-05-09 18:16:40 -07:00
make resources configurable by the user without modifying code
This commit is contained in:
parent
50574bf352
commit
159c8fa5b3
21 changed files with 181 additions and 337 deletions
|
@ -2,7 +2,32 @@ base_url: "https://zyl.gay"
|
||||||
title: zyl is gay
|
title: zyl is gay
|
||||||
description: "zyl's website."
|
description: "zyl's website."
|
||||||
sass_styles: [index.scss, pet.scss, click.scss]
|
sass_styles: [index.scss, pet.scss, click.scss]
|
||||||
images_per_page: 10
|
|
||||||
blog_posts_per_page: 20
|
|
||||||
cdn_url: "https://i.zyl.gay"
|
cdn_url: "https://i.zyl.gay"
|
||||||
s3_prefix: # no longer prefixed
|
|
||||||
|
resources:
|
||||||
|
blog:
|
||||||
|
source_path: blog
|
||||||
|
output_path_short: blog
|
||||||
|
output_path_long: blog
|
||||||
|
resource_template: blog-post
|
||||||
|
resource_list_template: blog-list
|
||||||
|
rss_template: rss/blog-post
|
||||||
|
rss_title: zyl's blog
|
||||||
|
rss_description: feed of recent blog posts on zyl's website.
|
||||||
|
list_title: blog
|
||||||
|
tag_list_title: blog tags
|
||||||
|
resource_name_plural: blog posts
|
||||||
|
resources_per_page: 20
|
||||||
|
images:
|
||||||
|
source_path: images
|
||||||
|
output_path_short: i
|
||||||
|
output_path_long: images
|
||||||
|
resource_template: image
|
||||||
|
resource_list_template: images
|
||||||
|
rss_template: rss/image
|
||||||
|
rss_title: zyl's images
|
||||||
|
rss_description: feed of newly uploaded images from zyl's website.
|
||||||
|
list_title: images
|
||||||
|
tag_list_title: image tags
|
||||||
|
resource_name_plural: images
|
||||||
|
resources_per_page: 10
|
||||||
|
|
|
@ -3,7 +3,7 @@ title: being gay is so cool
|
||||||
timestamp: 2023-11-14T23:09:00.00Z
|
timestamp: 2023-11-14T23:09:00.00Z
|
||||||
tags: [gay, lesbiab, trans]
|
tags: [gay, lesbiab, trans]
|
||||||
desc: holy shit being gay is cool y'all
|
desc: holy shit being gay is cool y'all
|
||||||
header_image_file: 2023/11/pfp.png
|
cdn_file: 2023/11/pfp.png
|
||||||
header_image_alt: A dead Among Us bean layered on top of transgender, lesbian, and bisexual pride flags.
|
header_image_alt: A dead Among Us bean layered on top of transgender, lesbian, and bisexual pride flags.
|
||||||
---
|
---
|
||||||
|
|
|
@ -3,7 +3,7 @@ title: estradiol delivery methods
|
||||||
timestamp: 2023-11-12T20:55:00.00Z
|
timestamp: 2023-11-12T20:55:00.00Z
|
||||||
tags: [trans, hrt, estradiol]
|
tags: [trans, hrt, estradiol]
|
||||||
desc: a comparison of the four forms of estradiol i've taken since starting hrt
|
desc: a comparison of the four forms of estradiol i've taken since starting hrt
|
||||||
header_image_file: 2023/11/syringe.jpeg
|
cdn_file: 2023/11/syringe.jpeg
|
||||||
header_image_alt: A photograph of a syringe for instramuscular injection with needle still in its wrapper.
|
header_image_alt: A photograph of a syringe for instramuscular injection with needle still in its wrapper.
|
||||||
---
|
---
|
||||||
|
|
|
@ -3,7 +3,7 @@ title: i built a minecraft classic server
|
||||||
timestamp: 2024-07-08T10:37:00.00Z
|
timestamp: 2024-07-08T10:37:00.00Z
|
||||||
tags: [minecraft, minecraft classic, dev]
|
tags: [minecraft, minecraft classic, dev]
|
||||||
desc: i got bored and built a minecraft classic server
|
desc: i got bored and built a minecraft classic server
|
||||||
header_image_file: 2024/07/classic2.png
|
cdn_file: 2024/07/classic2.png
|
||||||
header_image_alt: Screenshot of my Minecraft classic server showing two players standing in front of a stack of blocks.
|
header_image_alt: Screenshot of my Minecraft classic server showing two players standing in front of a stack of blocks.
|
||||||
---
|
---
|
||||||
|
|
|
@ -3,7 +3,7 @@ title: send trans people money.
|
||||||
timestamp: 2024-06-21T14:05:00.00Z
|
timestamp: 2024-06-21T14:05:00.00Z
|
||||||
tags: [trans, money]
|
tags: [trans, money]
|
||||||
desc: you should send trans people money. i'm trans btw
|
desc: you should send trans people money. i'm trans btw
|
||||||
header_image_file: 2024/06/gamecube.jpeg
|
cdn_file: 2024/06/gamecube.jpeg
|
||||||
header_image_alt: A photo of me holding a Cipon-branded Gamecube controller in my lap.
|
header_image_alt: A photo of me holding a Cipon-branded Gamecube controller in my lap.
|
||||||
---
|
---
|
||||||
|
|
|
@ -3,7 +3,7 @@ title: so now i have a blog
|
||||||
timestamp: 2023-06-09T22:20:00.00Z
|
timestamp: 2023-06-09T22:20:00.00Z
|
||||||
tags: [meta, blog]
|
tags: [meta, blog]
|
||||||
desc: I added a blog to my site. Here's a picture of some smoke.
|
desc: I added a blog to my site. Here's a picture of some smoke.
|
||||||
header_image_file: 2023/06/smoke.jpeg
|
cdn_file: 2023/06/smoke.jpeg
|
||||||
header_image_alt: Photo of smoke engulfing an interstate from a highway across a river.
|
header_image_alt: Photo of smoke engulfing an interstate from a highway across a river.
|
||||||
---
|
---
|
||||||
|
|
|
@ -3,7 +3,7 @@ title: among us 😱
|
||||||
timestamp: 2022-12-14T00:00:00.00Z
|
timestamp: 2022-12-14T00:00:00.00Z
|
||||||
alt: Screenshot of Pokémon White on an evolution screen. Text reads "Congratulations! Your amogus evolved into Amoonguss!"
|
alt: Screenshot of Pokémon White on an evolution screen. Text reads "Congratulations! Your amogus evolved into Amoonguss!"
|
||||||
desc: aaahhhh
|
desc: aaahhhh
|
||||||
file: amogus.png
|
cdn_file: amogus.png
|
||||||
tags: [pokémon, sussy]
|
tags: [pokémon, sussy]
|
||||||
---
|
---
|
||||||
|
|
|
@ -2,6 +2,6 @@
|
||||||
title: cat 3
|
title: cat 3
|
||||||
timestamp: 2022-12-14T00:00:00.00Z
|
timestamp: 2022-12-14T00:00:00.00Z
|
||||||
alt: Picture of my cat sleeping in a box barely large enough for them. Their head is resting on one edge of the box.
|
alt: Picture of my cat sleeping in a box barely large enough for them. Their head is resting on one edge of the box.
|
||||||
file: boxtop.jpeg
|
cdn_file: boxtop.jpeg
|
||||||
tags: [cat]
|
tags: [cat]
|
||||||
---
|
---
|
|
@ -2,6 +2,6 @@
|
||||||
title: cat
|
title: cat
|
||||||
timestamp: 2022-12-14T00:00:00.00Z
|
timestamp: 2022-12-14T00:00:00.00Z
|
||||||
alt: Picture of my cat sleeping curled up on top of some pillows.
|
alt: Picture of my cat sleeping curled up on top of some pillows.
|
||||||
file: cat.jpeg
|
cdn_file: cat.jpeg
|
||||||
tags: [cat]
|
tags: [cat]
|
||||||
---
|
---
|
|
@ -2,6 +2,6 @@
|
||||||
title: cat 2
|
title: cat 2
|
||||||
timestamp: 2022-12-14T00:00:00.00Z
|
timestamp: 2022-12-14T00:00:00.00Z
|
||||||
alt: Close up picture of my cat laying on a shelf while staring not quite at the camera.
|
alt: Close up picture of my cat laying on a shelf while staring not quite at the camera.
|
||||||
file: cat2.jpeg
|
cdn_file: cat2.jpeg
|
||||||
tags: [cat]
|
tags: [cat]
|
||||||
---
|
---
|
|
@ -2,7 +2,7 @@
|
||||||
title: shorts to dresses
|
title: shorts to dresses
|
||||||
timestamp: 2022-12-14T00:00:00.00Z
|
timestamp: 2022-12-14T00:00:00.00Z
|
||||||
alt: Screenshot from Pokémon Black 2 of an NPC saying "This dress is comfy and easy to wear..."
|
alt: Screenshot from Pokémon Black 2 of an NPC saying "This dress is comfy and easy to wear..."
|
||||||
file: trans-comfy.png
|
cdn_file: trans-comfy.png
|
||||||
tags: [pokémon, trans]
|
tags: [pokémon, trans]
|
||||||
---
|
---
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<div class="header-image-wrapper">
|
<div class="header-image-wrapper">
|
||||||
<p class="short-desc">{{desc}}</p>
|
<p class="short-desc">{{desc}}</p>
|
||||||
<img class="header-image" src="{{header_image}}" alt="{{header_image_alt}}"
|
<img class="header-image" src="{{cdn_file}}" alt="{{header_image_alt}}"
|
||||||
style="object-fit: {{object_fit}}; object-position: {{object_position}};">
|
style="object-fit: cover; object-position: 50% 50%">
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{{{content}}}
|
{{{content}}}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<div class="image-full">
|
<div class="image-full">
|
||||||
<h1 class="title">{{title}}</h1>
|
<h1 class="title">{{title}}</h1>
|
||||||
<span class="timestamp">published {{timestamp}}</span>
|
<span class="timestamp">published {{timestamp}}</span>
|
||||||
<img class="image-actual" src="{{src}}" alt="{{alt}}">
|
<img class="image-actual" src="{{cdn_file}}" alt="{{alt}}">
|
||||||
{{{content}}}
|
{{{content}}}
|
||||||
<p><a href="{{src}}">view full size image</a></p>
|
<p><a href="{{cdn_file}}">view full size image</a></p>
|
||||||
<h3 class="tags-title">tags</h3>
|
<h3 class="tags-title">tags</h3>
|
||||||
<div class="image-tags">
|
<div class="image-tags">
|
||||||
{{#each tags}}
|
{{#each tags}}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<div class="images-list">
|
<div class="images-list">
|
||||||
{{#each resources}}
|
{{#each resources}}
|
||||||
<a class="image" href="/i/{{id}}">
|
<a class="image" href="/i/{{id}}">
|
||||||
<img class="image-actual" src="{{src}}" alt="{{alt}}">
|
<img class="image-actual" src="{{cdn_file}}" alt="{{alt}}">
|
||||||
<span class="title">{{title}}</span>
|
<span class="title">{{title}}</span>
|
||||||
</a>
|
</a>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
98
src/blog.rs
98
src/blog.rs
|
@ -1,98 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
resource::{EmbedMetadata, ResourceBuilderConfig, ResourceMetadata, ResourceMethods},
|
|
||||||
Site, SiteConfig,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const BLOG_PATH: &str = "blog";
|
|
||||||
|
|
||||||
/// Gets the blog's resource configuration.
|
|
||||||
pub fn get_blog_resource_config(site: &Site) -> ResourceBuilderConfig {
|
|
||||||
ResourceBuilderConfig {
|
|
||||||
source_path: BLOG_PATH.to_string(),
|
|
||||||
output_path_short: BLOG_PATH.to_string(),
|
|
||||||
output_path_long: BLOG_PATH.to_string(),
|
|
||||||
resource_template: "blog-post".to_string(),
|
|
||||||
resource_list_template: "blog-list".to_string(),
|
|
||||||
rss_template: "rss/blog-post".to_string(),
|
|
||||||
rss_title: "zyl's blog".to_string(),
|
|
||||||
rss_description: "Feed of recent blog posts on zyl's website.".to_string(),
|
|
||||||
list_title: "blog".to_string(),
|
|
||||||
tag_list_title: "blog tags".to_string(),
|
|
||||||
resource_name_plural: "blog posts".to_string(),
|
|
||||||
resources_per_page: site.config.blog_posts_per_page,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Metadata for a blog post.
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct BlogPostMetadata {
|
|
||||||
/// A short description about the post.
|
|
||||||
pub desc: String,
|
|
||||||
/// Path to the post's header image.
|
|
||||||
pub header_image_file: String,
|
|
||||||
/// Alt text for the post's header image.
|
|
||||||
pub header_image_alt: String,
|
|
||||||
/// Optional custom object fit value.
|
|
||||||
pub image_fit: Option<String>,
|
|
||||||
/// Optional custom object position value.
|
|
||||||
pub image_center: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BlogPostMetadata {
|
|
||||||
/// Helper to get the CDN URL to the blog post's header image.
|
|
||||||
fn get_header_image(&self, site_config: &SiteConfig) -> eyre::Result<String> {
|
|
||||||
Ok(site_config.cdn_url(&self.header_image_file)?.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Template data for a blog post.
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
pub struct BlogPostTemplateData {
|
|
||||||
/// CDN path to the post's header image.
|
|
||||||
pub header_image: String,
|
|
||||||
/// Custom object fit value.
|
|
||||||
pub object_fit: String,
|
|
||||||
/// Custom object position value.
|
|
||||||
pub object_position: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResourceMethods<BlogPostTemplateData> for ResourceMetadata<BlogPostMetadata> {
|
|
||||||
fn get_short_desc(&self) -> String {
|
|
||||||
self.inner.desc.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_extra_resource_template_data(
|
|
||||||
&self,
|
|
||||||
site_config: &SiteConfig,
|
|
||||||
) -> eyre::Result<BlogPostTemplateData> {
|
|
||||||
// TODO: render markdown
|
|
||||||
Ok(BlogPostTemplateData {
|
|
||||||
header_image: self.inner.get_header_image(site_config)?,
|
|
||||||
object_fit: self
|
|
||||||
.inner
|
|
||||||
.image_fit
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| "cover".to_string()),
|
|
||||||
object_position: self
|
|
||||||
.inner
|
|
||||||
.image_center
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| "50% 50%".to_string()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_head_data(&self, site_config: &SiteConfig) -> eyre::Result<String> {
|
|
||||||
Ok(EmbedMetadata {
|
|
||||||
title: self.title.clone(),
|
|
||||||
site_name: site_config.title.clone(),
|
|
||||||
description: Some(self.inner.desc.clone()),
|
|
||||||
image: Some(self.inner.get_header_image(site_config)?),
|
|
||||||
url: None,
|
|
||||||
theme_color: EmbedMetadata::default_theme_color(),
|
|
||||||
large_image: true,
|
|
||||||
}
|
|
||||||
.build())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! Module containing the site builder.
|
//! Module containing the site builder.
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
use eyre::{eyre, Context, OptionExt};
|
use eyre::{eyre, Context, OptionExt};
|
||||||
use gray_matter::{engine::YAML, Matter};
|
use gray_matter::{engine::YAML, Matter};
|
||||||
|
@ -43,12 +43,8 @@ pub struct SiteBuilder<'a> {
|
||||||
/// Whether the site is going to be served locally with the dev server.
|
/// Whether the site is going to be served locally with the dev server.
|
||||||
serving: bool,
|
serving: bool,
|
||||||
|
|
||||||
/// Resource builder for the site's images section.
|
/// The resource builders available to the builder.
|
||||||
pub images_builder:
|
pub resource_builders: HashMap<String, ResourceBuilder>,
|
||||||
ResourceBuilder<crate::images::ImageMetadata, crate::images::ImageTemplateData>,
|
|
||||||
/// Resource builder for the site's blog section.
|
|
||||||
pub blog_builder:
|
|
||||||
ResourceBuilder<crate::blog::BlogPostMetadata, crate::blog::BlogPostTemplateData>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> SiteBuilder<'a> {
|
impl<'a> SiteBuilder<'a> {
|
||||||
|
@ -65,8 +61,7 @@ impl<'a> SiteBuilder<'a> {
|
||||||
Self {
|
Self {
|
||||||
matter: Matter::new(),
|
matter: Matter::new(),
|
||||||
reg: Handlebars::new(),
|
reg: Handlebars::new(),
|
||||||
images_builder: ResourceBuilder::new(crate::images::get_images_resource_config(&site)),
|
resource_builders: HashMap::new(),
|
||||||
blog_builder: ResourceBuilder::new(crate::blog::get_blog_resource_config(&site)),
|
|
||||||
site,
|
site,
|
||||||
build_path,
|
build_path,
|
||||||
serving,
|
serving,
|
||||||
|
@ -112,34 +107,35 @@ impl<'a> SiteBuilder<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let images_path = self.build_path.join(crate::images::IMAGES_OUT_PATH);
|
self.reload()?;
|
||||||
if !images_path.exists() {
|
|
||||||
std::fs::create_dir(images_path).wrap_err("Failed to create images path")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.reload_images_builder()?;
|
|
||||||
self.reload_blog_builder()?;
|
|
||||||
|
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reloads the images builder's metadata.
|
/// Performs actions that need to be done when the config changes while serving.
|
||||||
pub fn reload_images_builder(&mut self) -> eyre::Result<()> {
|
pub fn reload(&mut self) -> eyre::Result<()> {
|
||||||
let mut images_builder = std::mem::take(&mut self.images_builder);
|
self.resource_builders.clear();
|
||||||
images_builder
|
for (prefix, config) in &self.site.config.resources {
|
||||||
.load_all(self)
|
self.resource_builders
|
||||||
.wrap_err("Failed to load images metadata")?;
|
.insert(prefix.to_owned(), ResourceBuilder::new(config.clone()));
|
||||||
self.images_builder = images_builder;
|
}
|
||||||
|
|
||||||
|
for prefix in self.resource_builders.keys().cloned().collect::<Vec<_>>() {
|
||||||
|
self.reload_resource_builder(&prefix)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reloads the blog builder's metadata.
|
/// Reloads a particular resource builder's metadata.
|
||||||
pub fn reload_blog_builder(&mut self) -> eyre::Result<()> {
|
pub fn reload_resource_builder(&mut self, builder: &str) -> eyre::Result<()> {
|
||||||
let mut blog_builder = std::mem::take(&mut self.blog_builder);
|
let mut resource_builder = self
|
||||||
blog_builder
|
.resource_builders
|
||||||
.load_all(self)
|
.remove(builder)
|
||||||
.wrap_err("Failed to load blog metadata")?;
|
.ok_or_else(|| eyre!("missing resource builder: {builder}"))?;
|
||||||
self.blog_builder = blog_builder;
|
resource_builder.load_all(self)?;
|
||||||
|
self.resource_builders
|
||||||
|
.insert(builder.to_string(), resource_builder);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,13 +380,19 @@ impl<'a> SiteBuilder<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds the site's various image pages.
|
/// Builds all resource types.
|
||||||
pub fn build_images(&self) -> eyre::Result<()> {
|
pub fn build_all_resources(&self) -> eyre::Result<()> {
|
||||||
self.images_builder.build_all(self)
|
for builder in self.resource_builders.values() {
|
||||||
|
builder.build_all(self)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds the site's blog.
|
/// Builds a resource type from the site.
|
||||||
pub fn build_blog(&self) -> eyre::Result<()> {
|
pub fn build_resources(&self, resource: &str) -> eyre::Result<()> {
|
||||||
self.blog_builder.build_all(self)
|
self.resource_builders
|
||||||
|
.get(resource)
|
||||||
|
.ok_or_else(|| eyre!("missing resource: {resource}"))?
|
||||||
|
.build_all(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use lol_html::{element, RewriteStrSettings};
|
use lol_html::{element, RewriteStrSettings};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::{blog::BlogPostMetadata, builder::SiteBuilder, resource::ResourceTemplateData};
|
use crate::{builder::SiteBuilder, resource::ResourceTemplateData};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Extra {
|
pub enum Extra {
|
||||||
|
@ -31,6 +31,7 @@ pub fn get_extra(extra: &str) -> Option<Extra> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extra to append a tempalte to the page.
|
||||||
fn append_to(page: &str, content: &str, selector: &str) -> eyre::Result<String> {
|
fn append_to(page: &str, content: &str, selector: &str) -> eyre::Result<String> {
|
||||||
Ok(lol_html::rewrite_str(
|
Ok(lol_html::rewrite_str(
|
||||||
page,
|
page,
|
||||||
|
@ -48,22 +49,22 @@ fn append_to(page: &str, content: &str, selector: &str) -> eyre::Result<String>
|
||||||
fn index(page: String, builder: &SiteBuilder) -> eyre::Result<String> {
|
fn index(page: String, builder: &SiteBuilder) -> eyre::Result<String> {
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
struct SidebarTemplateData<'r> {
|
struct SidebarTemplateData<'r> {
|
||||||
// resources: Vec<&'r ResourceMetadata<BlogPostMetadata>>,
|
resources: Vec<ResourceTemplateData<'r>>,
|
||||||
resources: Vec<ResourceTemplateData<'r, BlogPostMetadata, ()>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let sidebar = builder.reg.render(
|
let sidebar = builder.reg.render(
|
||||||
"extras/index-injection",
|
"extras/index-injection",
|
||||||
&SidebarTemplateData {
|
&SidebarTemplateData {
|
||||||
resources: builder
|
resources: builder
|
||||||
.blog_builder
|
.resource_builders
|
||||||
|
.get("blog")
|
||||||
|
.expect("missing blog builder")
|
||||||
.loaded_metadata
|
.loaded_metadata
|
||||||
.iter()
|
.iter()
|
||||||
.take(3)
|
.take(3)
|
||||||
.map(|(id, v)| ResourceTemplateData {
|
.map(|(id, v)| ResourceTemplateData {
|
||||||
resource: v,
|
resource: v,
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
extra: (),
|
|
||||||
timestamp: v.timestamp,
|
timestamp: v.timestamp,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
resource::{EmbedMetadata, ResourceBuilderConfig, ResourceMetadata, ResourceMethods},
|
|
||||||
Site, SiteConfig,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub(crate) const IMAGES_PATH: &str = "images";
|
|
||||||
pub(crate) const IMAGES_OUT_PATH: &str = "i";
|
|
||||||
|
|
||||||
/// Gets the resource configuration for images.
|
|
||||||
pub fn get_images_resource_config(site: &Site) -> ResourceBuilderConfig {
|
|
||||||
ResourceBuilderConfig {
|
|
||||||
source_path: IMAGES_PATH.to_string(),
|
|
||||||
output_path_short: IMAGES_OUT_PATH.to_string(),
|
|
||||||
output_path_long: "images".to_string(),
|
|
||||||
resource_template: "image".to_string(),
|
|
||||||
resource_list_template: "images".to_string(),
|
|
||||||
rss_template: "rss/image".to_string(),
|
|
||||||
rss_title: "zyl's images".to_string(),
|
|
||||||
rss_description: "feed of newly uploaded images from zyl's website.".to_string(),
|
|
||||||
list_title: "images".to_string(),
|
|
||||||
tag_list_title: "image tags".to_string(),
|
|
||||||
resource_name_plural: "images".to_string(),
|
|
||||||
resources_per_page: site.config.images_per_page,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Definition for a remote image.
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct ImageMetadata {
|
|
||||||
/// The image's alt text.
|
|
||||||
pub alt: String,
|
|
||||||
/// The image's extra description, if any.
|
|
||||||
pub desc: Option<String>,
|
|
||||||
/// The image's file path.
|
|
||||||
pub file: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ImageMetadata {
|
|
||||||
fn get_image_url(&self, site_config: &SiteConfig) -> eyre::Result<String> {
|
|
||||||
Ok(site_config.cdn_url(&self.file)?.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Template data for a specific image.
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
pub struct ImageTemplateData {
|
|
||||||
/// Direct URL to the image's CDN location.
|
|
||||||
/// TODO: link to smaller versions on list pages
|
|
||||||
src: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResourceMethods<ImageTemplateData> for ResourceMetadata<ImageMetadata> {
|
|
||||||
fn get_short_desc(&self) -> String {
|
|
||||||
self.inner.desc.clone().unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_extra_resource_template_data(
|
|
||||||
&self,
|
|
||||||
site_config: &SiteConfig,
|
|
||||||
) -> eyre::Result<ImageTemplateData> {
|
|
||||||
Ok(ImageTemplateData {
|
|
||||||
src: self.inner.get_image_url(site_config)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_head_data(&self, site_config: &SiteConfig) -> eyre::Result<String> {
|
|
||||||
Ok(EmbedMetadata {
|
|
||||||
title: self.title.clone(),
|
|
||||||
site_name: site_config.title.clone(),
|
|
||||||
description: self.inner.desc.clone(),
|
|
||||||
image: Some(self.inner.get_image_url(site_config)?),
|
|
||||||
url: None,
|
|
||||||
theme_color: EmbedMetadata::default_theme_color(),
|
|
||||||
large_image: true,
|
|
||||||
}
|
|
||||||
.build())
|
|
||||||
}
|
|
||||||
}
|
|
22
src/lib.rs
22
src/lib.rs
|
@ -1,7 +1,5 @@
|
||||||
mod blog;
|
|
||||||
mod builder;
|
mod builder;
|
||||||
mod extras;
|
mod extras;
|
||||||
mod images;
|
|
||||||
mod link_list;
|
mod link_list;
|
||||||
mod resource;
|
mod resource;
|
||||||
#[cfg(feature = "serve")]
|
#[cfg(feature = "serve")]
|
||||||
|
@ -15,7 +13,7 @@ use std::{
|
||||||
|
|
||||||
use eyre::Context;
|
use eyre::Context;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use resource::EmbedMetadata;
|
use resource::{EmbedMetadata, ResourceBuilderConfig};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use util::get_name;
|
use util::get_name;
|
||||||
|
@ -41,20 +39,16 @@ pub struct SiteConfig {
|
||||||
pub build: Option<String>,
|
pub build: Option<String>,
|
||||||
/// A list of Sass stylesheets that will be built.
|
/// A list of Sass stylesheets that will be built.
|
||||||
pub sass_styles: Vec<PathBuf>,
|
pub sass_styles: Vec<PathBuf>,
|
||||||
/// The number of images to display on a single page of an image list.
|
|
||||||
pub images_per_page: usize,
|
|
||||||
/// The number of blog posts to display on a single page of a post list.
|
|
||||||
pub blog_posts_per_page: usize,
|
|
||||||
/// URL to the CDN used for the site's images.
|
/// URL to the CDN used for the site's images.
|
||||||
pub cdn_url: Url,
|
pub cdn_url: Url,
|
||||||
/// Prefix applied to all files uploaded to the site's S3 space.
|
/// List of resources the site should build.
|
||||||
pub s3_prefix: String,
|
pub resources: HashMap<String, ResourceBuilderConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SiteConfig {
|
impl SiteConfig {
|
||||||
/// Gets a CDN url from the given file name.
|
/// Gets a CDN url from the given file name.
|
||||||
pub fn cdn_url(&self, file: &str) -> eyre::Result<Url> {
|
pub fn cdn_url(&self, file: &str) -> eyre::Result<Url> {
|
||||||
Ok(self.cdn_url.join(&self.s3_prefix)?.join(file)?)
|
Ok(self.cdn_url.join(file)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,8 +149,12 @@ impl Site {
|
||||||
|
|
||||||
builder.site.build_all_pages(&builder)?;
|
builder.site.build_all_pages(&builder)?;
|
||||||
builder.build_sass()?;
|
builder.build_sass()?;
|
||||||
builder.build_images()?;
|
|
||||||
builder.build_blog()?;
|
for (_source_path, config) in builder.site.config.resources.iter() {
|
||||||
|
let mut res_builder = resource::ResourceBuilder::new(config.clone());
|
||||||
|
res_builder.load_all(&builder)?;
|
||||||
|
res_builder.build_all(&builder)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
114
src/resource.rs
114
src/resource.rs
|
@ -1,6 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
marker::PhantomData,
|
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -8,14 +7,17 @@ use eyre::Context;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use pulldown_cmark::{Options, Parser};
|
use pulldown_cmark::{Options, Parser};
|
||||||
use rss::{validation::Validate, ChannelBuilder, ItemBuilder};
|
use rss::{validation::Validate, ChannelBuilder, ItemBuilder};
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer};
|
use serde::{Deserialize, Serialize, Serializer};
|
||||||
use time::{format_description::well_known::Rfc2822, OffsetDateTime};
|
use time::{format_description::well_known::Rfc2822, OffsetDateTime};
|
||||||
|
|
||||||
use crate::{builder::SiteBuilder, link_list::Link, PageMetadata, SiteConfig};
|
use crate::{builder::SiteBuilder, link_list::Link, PageMetadata};
|
||||||
|
|
||||||
|
/// Source base path for resources.
|
||||||
|
pub const RESOURCES_PATH: &str = "resources";
|
||||||
|
|
||||||
/// Metadata for resources.
|
/// Metadata for resources.
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct ResourceMetadata<T> {
|
pub struct ResourceMetadata {
|
||||||
/// The resource's title.
|
/// The resource's title.
|
||||||
pub title: String,
|
pub title: String,
|
||||||
/// The resource's timestamp.
|
/// The resource's timestamp.
|
||||||
|
@ -23,9 +25,13 @@ pub struct ResourceMetadata<T> {
|
||||||
pub timestamp: OffsetDateTime,
|
pub timestamp: OffsetDateTime,
|
||||||
/// The resource's tags.
|
/// The resource's tags.
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
|
/// Special field that gets transformed to the full CDN URL for the given path.
|
||||||
|
pub cdn_file: Option<String>,
|
||||||
|
/// The resource's description, if any.
|
||||||
|
pub desc: Option<String>,
|
||||||
/// Extra resource data not included.
|
/// Extra resource data not included.
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub inner: T,
|
pub inner: serde_yml::Value,
|
||||||
/// Whether the resource is a draft. Drafts can be committed without being published to the live site.
|
/// Whether the resource is a draft. Drafts can be committed without being published to the live site.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub draft: bool,
|
pub draft: bool,
|
||||||
|
@ -35,21 +41,18 @@ pub struct ResourceMetadata<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct ResourceTemplateData<'r, M, E> {
|
pub struct ResourceTemplateData<'r> {
|
||||||
/// The resource's metadata.
|
/// The resource's metadata.
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub resource: &'r ResourceMetadata<M>,
|
pub resource: &'r ResourceMetadata,
|
||||||
/// The resource's ID.
|
/// The resource's ID.
|
||||||
pub id: String,
|
pub id: String,
|
||||||
/// Extra data to be passed to the template.
|
|
||||||
#[serde(flatten)]
|
|
||||||
pub extra: E,
|
|
||||||
/// The resource's timestamp. Duplicated to change serialization method.
|
/// The resource's timestamp. Duplicated to change serialization method.
|
||||||
#[serde(serialize_with = "ResourceTemplateData::<M, E>::timestamp_formatter")]
|
#[serde(serialize_with = "ResourceTemplateData::timestamp_formatter")]
|
||||||
pub timestamp: OffsetDateTime,
|
pub timestamp: OffsetDateTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r, M, E> ResourceTemplateData<'r, M, E> {
|
impl<'r> ResourceTemplateData<'r> {
|
||||||
fn timestamp_formatter<S>(timestamp: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Error>
|
fn timestamp_formatter<S>(timestamp: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
S: Serializer,
|
S: Serializer,
|
||||||
|
@ -64,20 +67,6 @@ impl<'r, M, E> ResourceTemplateData<'r, M, E> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait for getting extra template data from resource metadata.
|
|
||||||
pub trait ResourceMethods<E>
|
|
||||||
where
|
|
||||||
E: Serialize,
|
|
||||||
{
|
|
||||||
fn get_short_desc(&self) -> String;
|
|
||||||
|
|
||||||
fn get_extra_resource_template_data(&self, site_config: &SiteConfig) -> eyre::Result<E>;
|
|
||||||
|
|
||||||
fn get_head_data(&self, _site_config: &SiteConfig) -> eyre::Result<String> {
|
|
||||||
Ok(String::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// struct for adding custom meta content embeds
|
/// struct for adding custom meta content embeds
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct EmbedMetadata {
|
pub struct EmbedMetadata {
|
||||||
|
@ -130,8 +119,8 @@ impl EmbedMetadata {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
struct ResourceListTemplateData<'r, M, E> {
|
struct ResourceListTemplateData<'r> {
|
||||||
resources: Vec<&'r ResourceTemplateData<'r, M, E>>,
|
resources: Vec<&'r ResourceTemplateData<'r>>,
|
||||||
tag: Option<&'r str>,
|
tag: Option<&'r str>,
|
||||||
page: usize,
|
page: usize,
|
||||||
page_max: usize,
|
page_max: usize,
|
||||||
|
@ -145,7 +134,7 @@ struct ExtraResourceRenderData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Config for the resource builder.
|
/// Config for the resource builder.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Clone, Default, Deserialize)]
|
||||||
pub struct ResourceBuilderConfig {
|
pub struct ResourceBuilderConfig {
|
||||||
/// Path to where the resources should be loaded from.
|
/// Path to where the resources should be loaded from.
|
||||||
pub source_path: String,
|
pub source_path: String,
|
||||||
|
@ -174,27 +163,20 @@ pub struct ResourceBuilderConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper to genericize resource building.
|
/// Helper to genericize resource building.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub struct ResourceBuilder<M, E> {
|
pub struct ResourceBuilder {
|
||||||
/// The builder's config.
|
/// The builder's config.
|
||||||
pub config: ResourceBuilderConfig,
|
pub config: ResourceBuilderConfig,
|
||||||
/// The currently loaded resource metadata.
|
/// The currently loaded resource metadata.
|
||||||
pub loaded_metadata: Vec<(String, ResourceMetadata<M>)>,
|
pub loaded_metadata: Vec<(String, ResourceMetadata)>,
|
||||||
_extra: PhantomData<E>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M, E> ResourceBuilder<M, E>
|
impl ResourceBuilder {
|
||||||
where
|
|
||||||
M: Serialize + DeserializeOwned,
|
|
||||||
E: Serialize,
|
|
||||||
ResourceMetadata<M>: ResourceMethods<E>,
|
|
||||||
{
|
|
||||||
/// Creates a new resource builder.
|
/// Creates a new resource builder.
|
||||||
pub fn new(config: ResourceBuilderConfig) -> Self {
|
pub fn new(config: ResourceBuilderConfig) -> Self {
|
||||||
Self {
|
Self {
|
||||||
config,
|
config,
|
||||||
loaded_metadata: Default::default(),
|
loaded_metadata: Default::default(),
|
||||||
_extra: Default::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,13 +190,13 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads resource metadata from the given path.
|
/// Loads resource metadata from the given path.
|
||||||
fn load(builder: &SiteBuilder, path: &Path) -> eyre::Result<(String, ResourceMetadata<M>)> {
|
fn load(builder: &SiteBuilder, path: &Path) -> eyre::Result<(String, ResourceMetadata)> {
|
||||||
let id = Self::get_id(path);
|
let id = Self::get_id(path);
|
||||||
|
|
||||||
let input = std::fs::read_to_string(path)?;
|
let input = std::fs::read_to_string(path)?;
|
||||||
let mut page = builder
|
let mut page = builder
|
||||||
.matter
|
.matter
|
||||||
.parse_with_struct::<ResourceMetadata<M>>(&input)
|
.parse_with_struct::<ResourceMetadata>(&input)
|
||||||
.ok_or_else(|| eyre::anyhow!("Failed to parse resource front matter"))?;
|
.ok_or_else(|| eyre::anyhow!("Failed to parse resource front matter"))?;
|
||||||
|
|
||||||
let parser = Parser::new_ext(&page.content, Options::all());
|
let parser = Parser::new_ext(&page.content, Options::all());
|
||||||
|
@ -222,6 +204,9 @@ where
|
||||||
pulldown_cmark::html::push_html(&mut html, parser);
|
pulldown_cmark::html::push_html(&mut html, parser);
|
||||||
|
|
||||||
page.data.content = html;
|
page.data.content = html;
|
||||||
|
if let Some(cdn_file) = page.data.cdn_file {
|
||||||
|
page.data.cdn_file = Some(builder.site.config.cdn_url(&cdn_file)?.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
Ok((id, page.data))
|
Ok((id, page.data))
|
||||||
}
|
}
|
||||||
|
@ -233,6 +218,7 @@ where
|
||||||
for e in builder
|
for e in builder
|
||||||
.site
|
.site
|
||||||
.site_path
|
.site_path
|
||||||
|
.join(RESOURCES_PATH)
|
||||||
.join(&self.config.source_path)
|
.join(&self.config.source_path)
|
||||||
.read_dir()?
|
.read_dir()?
|
||||||
{
|
{
|
||||||
|
@ -262,13 +248,12 @@ where
|
||||||
&self,
|
&self,
|
||||||
builder: &SiteBuilder,
|
builder: &SiteBuilder,
|
||||||
id: String,
|
id: String,
|
||||||
resource: &ResourceMetadata<M>,
|
resource: &ResourceMetadata,
|
||||||
) -> eyre::Result<()> {
|
) -> eyre::Result<()> {
|
||||||
let out_path = self.build_path(&builder.build_path, &id);
|
let out_path = self.build_path(&builder.build_path, &id);
|
||||||
let out = {
|
let out = {
|
||||||
let data = ResourceTemplateData {
|
let data = ResourceTemplateData {
|
||||||
resource,
|
resource,
|
||||||
extra: resource.get_extra_resource_template_data(&builder.site.config)?,
|
|
||||||
id,
|
id,
|
||||||
timestamp: resource.timestamp,
|
timestamp: resource.timestamp,
|
||||||
};
|
};
|
||||||
|
@ -282,7 +267,20 @@ where
|
||||||
},
|
},
|
||||||
&out,
|
&out,
|
||||||
ExtraResourceRenderData {
|
ExtraResourceRenderData {
|
||||||
head: resource.get_head_data(&builder.site.config)?,
|
head: EmbedMetadata {
|
||||||
|
title: resource.title.clone(),
|
||||||
|
site_name: builder.site.config.title.clone(),
|
||||||
|
description: resource.desc.clone(),
|
||||||
|
image: if let Some(cdn_file) = &resource.cdn_file {
|
||||||
|
Some(builder.site.config.cdn_url(cdn_file)?.to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
url: None,
|
||||||
|
theme_color: EmbedMetadata::default_theme_color(),
|
||||||
|
large_image: true,
|
||||||
|
}
|
||||||
|
.build(),
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
std::fs::write(out_path, out)?;
|
std::fs::write(out_path, out)?;
|
||||||
|
@ -309,28 +307,22 @@ where
|
||||||
|
|
||||||
let mut data = Vec::with_capacity(lmd.len());
|
let mut data = Vec::with_capacity(lmd.len());
|
||||||
for (id, resource) in lmd.iter() {
|
for (id, resource) in lmd.iter() {
|
||||||
let extra = resource.get_extra_resource_template_data(&builder.site.config)?;
|
|
||||||
data.push(ResourceTemplateData {
|
data.push(ResourceTemplateData {
|
||||||
resource,
|
resource,
|
||||||
extra,
|
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
timestamp: resource.timestamp,
|
timestamp: resource.timestamp,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_list<M, E>(
|
fn build_list(
|
||||||
builder: &SiteBuilder,
|
builder: &SiteBuilder,
|
||||||
config: &ResourceBuilderConfig,
|
config: &ResourceBuilderConfig,
|
||||||
list: Vec<&ResourceTemplateData<M, E>>,
|
list: Vec<&ResourceTemplateData>,
|
||||||
title: &str,
|
title: &str,
|
||||||
tag: Option<&str>,
|
tag: Option<&str>,
|
||||||
out_path: &Path,
|
out_path: &Path,
|
||||||
items_per_page: usize,
|
items_per_page: usize,
|
||||||
) -> eyre::Result<()>
|
) -> eyre::Result<()> {
|
||||||
where
|
|
||||||
M: Serialize,
|
|
||||||
E: Serialize,
|
|
||||||
{
|
|
||||||
if !out_path.exists() {
|
if !out_path.exists() {
|
||||||
std::fs::create_dir_all(out_path)?;
|
std::fs::create_dir_all(out_path)?;
|
||||||
}
|
}
|
||||||
|
@ -381,7 +373,7 @@ where
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Build resource lists by tag
|
// Build resource lists by tag
|
||||||
let mut tags: BTreeMap<String, Vec<&ResourceTemplateData<M, E>>> = BTreeMap::new();
|
let mut tags: BTreeMap<String, Vec<&ResourceTemplateData>> = BTreeMap::new();
|
||||||
for resource in &data {
|
for resource in &data {
|
||||||
for tag in resource.resource.tags.iter().cloned() {
|
for tag in resource.resource.tags.iter().cloned() {
|
||||||
tags.entry(tag).or_default().push(resource);
|
tags.entry(tag).or_default().push(resource);
|
||||||
|
@ -442,7 +434,7 @@ where
|
||||||
))?
|
))?
|
||||||
.to_string(),
|
.to_string(),
|
||||||
))
|
))
|
||||||
.description(Some(resource.resource.get_short_desc()))
|
.description(resource.resource.desc.clone())
|
||||||
.pub_date(Some(resource.timestamp.format(&Rfc2822)?))
|
.pub_date(Some(resource.timestamp.format(&Rfc2822)?))
|
||||||
.content(Some(
|
.content(Some(
|
||||||
builder.reg.render(&self.config.rss_template, &resource)?,
|
builder.reg.render(&self.config.rss_template, &resource)?,
|
||||||
|
@ -472,13 +464,3 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M, E> Default for ResourceBuilder<M, E> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
config: Default::default(),
|
|
||||||
loaded_metadata: Default::default(),
|
|
||||||
_extra: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -18,7 +18,10 @@ use warp::{
|
||||||
Filter,
|
Filter,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{util::get_name, Site, SiteBuilder, PAGES_PATH, ROOT_PATH, SASS_PATH, TEMPLATES_PATH};
|
use crate::{
|
||||||
|
resource::RESOURCES_PATH, util::get_name, Site, SiteBuilder, PAGES_PATH, ROOT_PATH, SASS_PATH,
|
||||||
|
TEMPLATES_PATH,
|
||||||
|
};
|
||||||
|
|
||||||
fn with_build_path(
|
fn with_build_path(
|
||||||
build_path: PathBuf,
|
build_path: PathBuf,
|
||||||
|
@ -31,6 +34,30 @@ fn rel(path: &Path, prefix: &Path) -> Result<PathBuf, std::path::StripPrefixErro
|
||||||
Ok(path.strip_prefix(prefix)?.to_owned())
|
Ok(path.strip_prefix(prefix)?.to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper to build resources in the case of creation or removal.
|
||||||
|
fn build_resources(builder: &mut SiteBuilder, path: &Path) -> eyre::Result<()> {
|
||||||
|
let paths: Vec<_> = builder
|
||||||
|
.resource_builders
|
||||||
|
.values()
|
||||||
|
.map(|b| {
|
||||||
|
(
|
||||||
|
b.config.source_path.clone(),
|
||||||
|
path.strip_prefix(&b.config.source_path),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.filter_map(|(p, v)| v.ok().map(|v| (p, v)))
|
||||||
|
.collect();
|
||||||
|
if paths.len() > 1 {
|
||||||
|
todo!("handle more than one possible match");
|
||||||
|
}
|
||||||
|
if let Some((prefix, _path)) = paths.first() {
|
||||||
|
// HACK: this could get very inefficient with a larger number of resources. should definitely optimize
|
||||||
|
builder.reload_resource_builder(prefix)?;
|
||||||
|
builder.build_resources(prefix)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates or updates a resource.
|
/// Creates or updates a resource.
|
||||||
fn create(
|
fn create(
|
||||||
builder: &mut SiteBuilder,
|
builder: &mut SiteBuilder,
|
||||||
|
@ -57,12 +84,12 @@ fn create(
|
||||||
builder.refresh_template(&template_name_str, path)?;
|
builder.refresh_template(&template_name_str, path)?;
|
||||||
if build {
|
if build {
|
||||||
builder.site.build_all_pages(builder)?;
|
builder.site.build_all_pages(builder)?;
|
||||||
builder.build_images()?;
|
builder.build_all_resources()?;
|
||||||
builder.build_blog()?;
|
|
||||||
}
|
}
|
||||||
} else if relative_path.display().to_string() == "config.yaml" {
|
} else if relative_path.display().to_string() == "config.yaml" {
|
||||||
let new_config = serde_yml::from_str(&std::fs::read_to_string(path)?)?;
|
let new_config = serde_yml::from_str(&std::fs::read_to_string(path)?)?;
|
||||||
builder.site.config = new_config;
|
builder.site.config = new_config;
|
||||||
|
builder.reload()?;
|
||||||
builder.site.build_all_pages(builder)?;
|
builder.site.build_all_pages(builder)?;
|
||||||
} else if let Ok(_sass_path) = relative_path.strip_prefix(SASS_PATH) {
|
} else if let Ok(_sass_path) = relative_path.strip_prefix(SASS_PATH) {
|
||||||
if build {
|
if build {
|
||||||
|
@ -70,14 +97,8 @@ fn create(
|
||||||
}
|
}
|
||||||
} else if let Ok(root_path) = relative_path.strip_prefix(ROOT_PATH) {
|
} else if let Ok(root_path) = relative_path.strip_prefix(ROOT_PATH) {
|
||||||
std::fs::copy(path, builder.build_path.join(root_path))?;
|
std::fs::copy(path, builder.build_path.join(root_path))?;
|
||||||
} else if let Ok(_image_path) = relative_path.strip_prefix(crate::images::IMAGES_PATH) {
|
} else if let Ok(resources_path) = relative_path.strip_prefix(RESOURCES_PATH) {
|
||||||
// HACK: this could get very inefficient with a larger number of images. should definitely optimize
|
build_resources(builder, resources_path)?;
|
||||||
builder.reload_images_builder()?;
|
|
||||||
builder.build_images()?;
|
|
||||||
} else if let Ok(_blog_path) = relative_path.strip_prefix(crate::blog::BLOG_PATH) {
|
|
||||||
// HACK: same as above
|
|
||||||
builder.reload_blog_builder()?;
|
|
||||||
builder.build_blog()?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -106,14 +127,8 @@ fn remove(builder: &mut SiteBuilder, path: &Path, relative_path: &Path) -> eyre:
|
||||||
builder.build_sass().wrap_err("Failed to rebuild Sass")?;
|
builder.build_sass().wrap_err("Failed to rebuild Sass")?;
|
||||||
} else if let Ok(root_path) = relative_path.strip_prefix(ROOT_PATH) {
|
} else if let Ok(root_path) = relative_path.strip_prefix(ROOT_PATH) {
|
||||||
std::fs::remove_file(builder.build_path.join(root_path))?;
|
std::fs::remove_file(builder.build_path.join(root_path))?;
|
||||||
} else if let Ok(_image_path) = relative_path.strip_prefix(crate::images::IMAGES_PATH) {
|
} else if let Ok(resources_path) = relative_path.strip_prefix(RESOURCES_PATH) {
|
||||||
// HACK: same as in `create`
|
build_resources(builder, resources_path)?;
|
||||||
builder.reload_images_builder()?;
|
|
||||||
builder.build_images()?;
|
|
||||||
} else if let Ok(_blog_path) = relative_path.strip_prefix(crate::blog::BLOG_PATH) {
|
|
||||||
// HACK: same as above
|
|
||||||
builder.reload_blog_builder()?;
|
|
||||||
builder.build_blog()?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -141,9 +156,8 @@ impl Site {
|
||||||
}
|
}
|
||||||
builder.build_sass().wrap_err("Failed to build Sass")?;
|
builder.build_sass().wrap_err("Failed to build Sass")?;
|
||||||
builder
|
builder
|
||||||
.build_images()
|
.build_all_resources()
|
||||||
.wrap_err("Failed to build image pages")?;
|
.wrap_err("Failed to build resources")?;
|
||||||
builder.build_blog().wrap_err("Failed to build blog")?;
|
|
||||||
|
|
||||||
// Map of websocket connections
|
// Map of websocket connections
|
||||||
let peers: Arc<Mutex<HashMap<SocketAddr, WebSocket>>> =
|
let peers: Arc<Mutex<HashMap<SocketAddr, WebSocket>>> =
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue