From 159c8fa5b39cb5e391228af763133f92c4836b3c Mon Sep 17 00:00:00 2001 From: zyl Date: Mon, 4 Nov 2024 13:00:25 -0800 Subject: [PATCH] make resources configurable by the user without modifying code --- site/config.yaml | 31 ++++- .../{ => resources}/blog/being-gay-is-cool.md | 2 +- .../blog/estradiol-delivery-methods.md | 2 +- .../blog/minecraft-classic-server.md | 2 +- .../blog/send-trans-people-money.md | 2 +- .../blog/so-now-i-have-a-blog.md | 2 +- site/{ => resources}/images/amogus.md | 2 +- site/{ => resources}/images/boxtop.md | 2 +- site/{ => resources}/images/cat.md | 2 +- site/{ => resources}/images/cat2.md | 2 +- site/{ => resources}/images/trans-comfy.md | 2 +- site/templates/blog-post.hbs | 4 +- site/templates/image.hbs | 4 +- site/templates/images.hbs | 2 +- src/blog.rs | 98 --------------- src/builder.rs | 74 ++++++------ src/extras.rs | 11 +- src/images.rs | 80 ------------ src/lib.rs | 22 ++-- src/resource.rs | 114 ++++++++---------- src/serving.rs | 58 +++++---- 21 files changed, 181 insertions(+), 337 deletions(-) rename site/{ => resources}/blog/being-gay-is-cool.md (94%) rename site/{ => resources}/blog/estradiol-delivery-methods.md (98%) rename site/{ => resources}/blog/minecraft-classic-server.md (97%) rename site/{ => resources}/blog/send-trans-people-money.md (93%) rename site/{ => resources}/blog/so-now-i-have-a-blog.md (97%) rename site/{ => resources}/images/amogus.md (91%) rename site/{ => resources}/images/boxtop.md (89%) rename site/{ => resources}/images/cat.md (87%) rename site/{ => resources}/images/cat2.md (88%) rename site/{ => resources}/images/trans-comfy.md (90%) delete mode 100644 src/blog.rs delete mode 100644 src/images.rs diff --git a/site/config.yaml b/site/config.yaml index 2458b56..889045e 100644 --- a/site/config.yaml +++ b/site/config.yaml @@ -2,7 +2,32 @@ base_url: "https://zyl.gay" title: zyl is gay description: "zyl's website." sass_styles: [index.scss, pet.scss, click.scss] -images_per_page: 10 -blog_posts_per_page: 20 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 diff --git a/site/blog/being-gay-is-cool.md b/site/resources/blog/being-gay-is-cool.md similarity index 94% rename from site/blog/being-gay-is-cool.md rename to site/resources/blog/being-gay-is-cool.md index 2ee13d6..17162cc 100644 --- a/site/blog/being-gay-is-cool.md +++ b/site/resources/blog/being-gay-is-cool.md @@ -3,7 +3,7 @@ title: being gay is so cool timestamp: 2023-11-14T23:09:00.00Z tags: [gay, lesbiab, trans] 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. --- diff --git a/site/blog/estradiol-delivery-methods.md b/site/resources/blog/estradiol-delivery-methods.md similarity index 98% rename from site/blog/estradiol-delivery-methods.md rename to site/resources/blog/estradiol-delivery-methods.md index b3d9858..debd251 100644 --- a/site/blog/estradiol-delivery-methods.md +++ b/site/resources/blog/estradiol-delivery-methods.md @@ -3,7 +3,7 @@ title: estradiol delivery methods timestamp: 2023-11-12T20:55:00.00Z tags: [trans, hrt, estradiol] 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. --- diff --git a/site/blog/minecraft-classic-server.md b/site/resources/blog/minecraft-classic-server.md similarity index 97% rename from site/blog/minecraft-classic-server.md rename to site/resources/blog/minecraft-classic-server.md index 7b7e5e1..3444f4d 100644 --- a/site/blog/minecraft-classic-server.md +++ b/site/resources/blog/minecraft-classic-server.md @@ -3,7 +3,7 @@ title: i built a minecraft classic server timestamp: 2024-07-08T10:37:00.00Z tags: [minecraft, minecraft classic, dev] 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. --- diff --git a/site/blog/send-trans-people-money.md b/site/resources/blog/send-trans-people-money.md similarity index 93% rename from site/blog/send-trans-people-money.md rename to site/resources/blog/send-trans-people-money.md index 5598919..5e6308c 100644 --- a/site/blog/send-trans-people-money.md +++ b/site/resources/blog/send-trans-people-money.md @@ -3,7 +3,7 @@ title: send trans people money. timestamp: 2024-06-21T14:05:00.00Z tags: [trans, money] 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. --- diff --git a/site/blog/so-now-i-have-a-blog.md b/site/resources/blog/so-now-i-have-a-blog.md similarity index 97% rename from site/blog/so-now-i-have-a-blog.md rename to site/resources/blog/so-now-i-have-a-blog.md index 083bbff..1c7ecad 100644 --- a/site/blog/so-now-i-have-a-blog.md +++ b/site/resources/blog/so-now-i-have-a-blog.md @@ -3,7 +3,7 @@ title: so now i have a blog timestamp: 2023-06-09T22:20:00.00Z tags: [meta, blog] 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. --- diff --git a/site/images/amogus.md b/site/resources/images/amogus.md similarity index 91% rename from site/images/amogus.md rename to site/resources/images/amogus.md index b58294b..51b1c44 100644 --- a/site/images/amogus.md +++ b/site/resources/images/amogus.md @@ -3,7 +3,7 @@ title: among us 😱 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!" desc: aaahhhh -file: amogus.png +cdn_file: amogus.png tags: [pokémon, sussy] --- diff --git a/site/images/boxtop.md b/site/resources/images/boxtop.md similarity index 89% rename from site/images/boxtop.md rename to site/resources/images/boxtop.md index d21bc72..6b03325 100644 --- a/site/images/boxtop.md +++ b/site/resources/images/boxtop.md @@ -2,6 +2,6 @@ title: cat 3 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. -file: boxtop.jpeg +cdn_file: boxtop.jpeg tags: [cat] --- diff --git a/site/images/cat.md b/site/resources/images/cat.md similarity index 87% rename from site/images/cat.md rename to site/resources/images/cat.md index f1f6ebc..46367bb 100644 --- a/site/images/cat.md +++ b/site/resources/images/cat.md @@ -2,6 +2,6 @@ title: cat timestamp: 2022-12-14T00:00:00.00Z alt: Picture of my cat sleeping curled up on top of some pillows. -file: cat.jpeg +cdn_file: cat.jpeg tags: [cat] --- diff --git a/site/images/cat2.md b/site/resources/images/cat2.md similarity index 88% rename from site/images/cat2.md rename to site/resources/images/cat2.md index 16dbcc6..8c09b6a 100644 --- a/site/images/cat2.md +++ b/site/resources/images/cat2.md @@ -2,6 +2,6 @@ title: cat 2 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. -file: cat2.jpeg +cdn_file: cat2.jpeg tags: [cat] --- diff --git a/site/images/trans-comfy.md b/site/resources/images/trans-comfy.md similarity index 90% rename from site/images/trans-comfy.md rename to site/resources/images/trans-comfy.md index 798b5b1..128f3e5 100644 --- a/site/images/trans-comfy.md +++ b/site/resources/images/trans-comfy.md @@ -2,7 +2,7 @@ title: shorts to dresses 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..." -file: trans-comfy.png +cdn_file: trans-comfy.png tags: [pokémon, trans] --- diff --git a/site/templates/blog-post.hbs b/site/templates/blog-post.hbs index 558b54f..cc9dff1 100644 --- a/site/templates/blog-post.hbs +++ b/site/templates/blog-post.hbs @@ -6,8 +6,8 @@ {{/if}}

{{desc}}

- {{header_image_alt}} + {{header_image_alt}}
{{{content}}} diff --git a/site/templates/image.hbs b/site/templates/image.hbs index 158038e..0d5c672 100644 --- a/site/templates/image.hbs +++ b/site/templates/image.hbs @@ -1,9 +1,9 @@

{{title}}

published {{timestamp}} - {{alt}} + {{alt}} {{{content}}} -

view full size image

+

view full size image

tags

{{#each tags}} diff --git a/site/templates/images.hbs b/site/templates/images.hbs index fd421c4..062121a 100644 --- a/site/templates/images.hbs +++ b/site/templates/images.hbs @@ -16,7 +16,7 @@
{{#each resources}} - {{alt}} + {{alt}} {{title}} {{/each}} diff --git a/src/blog.rs b/src/blog.rs deleted file mode 100644 index 34ea2ab..0000000 --- a/src/blog.rs +++ /dev/null @@ -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, - /// Optional custom object position value. - pub image_center: Option, -} - -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 { - 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 for ResourceMetadata { - fn get_short_desc(&self) -> String { - self.inner.desc.clone() - } - - fn get_extra_resource_template_data( - &self, - site_config: &SiteConfig, - ) -> eyre::Result { - // 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 { - 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()) - } -} diff --git a/src/builder.rs b/src/builder.rs index 8d29625..bede56e 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,6 +1,6 @@ //! Module containing the site builder. -use std::path::PathBuf; +use std::{collections::HashMap, path::PathBuf}; use eyre::{eyre, Context, OptionExt}; 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. serving: bool, - /// Resource builder for the site's images section. - pub images_builder: - ResourceBuilder, - /// Resource builder for the site's blog section. - pub blog_builder: - ResourceBuilder, + /// The resource builders available to the builder. + pub resource_builders: HashMap, } impl<'a> SiteBuilder<'a> { @@ -65,8 +61,7 @@ impl<'a> SiteBuilder<'a> { Self { matter: Matter::new(), reg: Handlebars::new(), - images_builder: ResourceBuilder::new(crate::images::get_images_resource_config(&site)), - blog_builder: ResourceBuilder::new(crate::blog::get_blog_resource_config(&site)), + resource_builders: HashMap::new(), site, build_path, serving, @@ -112,34 +107,35 @@ impl<'a> SiteBuilder<'a> { } } - let images_path = self.build_path.join(crate::images::IMAGES_OUT_PATH); - 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()?; + self.reload()?; Ok(self) } - /// Reloads the images builder's metadata. - pub fn reload_images_builder(&mut self) -> eyre::Result<()> { - let mut images_builder = std::mem::take(&mut self.images_builder); - images_builder - .load_all(self) - .wrap_err("Failed to load images metadata")?; - self.images_builder = images_builder; + /// Performs actions that need to be done when the config changes while serving. + pub fn reload(&mut self) -> eyre::Result<()> { + self.resource_builders.clear(); + for (prefix, config) in &self.site.config.resources { + self.resource_builders + .insert(prefix.to_owned(), ResourceBuilder::new(config.clone())); + } + + for prefix in self.resource_builders.keys().cloned().collect::>() { + self.reload_resource_builder(&prefix)?; + } + Ok(()) } - /// Reloads the blog builder's metadata. - pub fn reload_blog_builder(&mut self) -> eyre::Result<()> { - let mut blog_builder = std::mem::take(&mut self.blog_builder); - blog_builder - .load_all(self) - .wrap_err("Failed to load blog metadata")?; - self.blog_builder = blog_builder; + /// Reloads a particular resource builder's metadata. + pub fn reload_resource_builder(&mut self, builder: &str) -> eyre::Result<()> { + let mut resource_builder = self + .resource_builders + .remove(builder) + .ok_or_else(|| eyre!("missing resource builder: {builder}"))?; + resource_builder.load_all(self)?; + self.resource_builders + .insert(builder.to_string(), resource_builder); Ok(()) } @@ -384,13 +380,19 @@ impl<'a> SiteBuilder<'a> { Ok(()) } - /// Builds the site's various image pages. - pub fn build_images(&self) -> eyre::Result<()> { - self.images_builder.build_all(self) + /// Builds all resource types. + pub fn build_all_resources(&self) -> eyre::Result<()> { + for builder in self.resource_builders.values() { + builder.build_all(self)?; + } + Ok(()) } - /// Builds the site's blog. - pub fn build_blog(&self) -> eyre::Result<()> { - self.blog_builder.build_all(self) + /// Builds a resource type from the site. + pub fn build_resources(&self, resource: &str) -> eyre::Result<()> { + self.resource_builders + .get(resource) + .ok_or_else(|| eyre!("missing resource: {resource}"))? + .build_all(self) } } diff --git a/src/extras.rs b/src/extras.rs index 9525585..54ceb5d 100644 --- a/src/extras.rs +++ b/src/extras.rs @@ -1,7 +1,7 @@ use lol_html::{element, RewriteStrSettings}; use serde::Serialize; -use crate::{blog::BlogPostMetadata, builder::SiteBuilder, resource::ResourceTemplateData}; +use crate::{builder::SiteBuilder, resource::ResourceTemplateData}; #[derive(Debug)] pub enum Extra { @@ -31,6 +31,7 @@ pub fn get_extra(extra: &str) -> Option { } } +/// Extra to append a tempalte to the page. fn append_to(page: &str, content: &str, selector: &str) -> eyre::Result { Ok(lol_html::rewrite_str( page, @@ -48,22 +49,22 @@ fn append_to(page: &str, content: &str, selector: &str) -> eyre::Result fn index(page: String, builder: &SiteBuilder) -> eyre::Result { #[derive(Debug, Serialize)] struct SidebarTemplateData<'r> { - // resources: Vec<&'r ResourceMetadata>, - resources: Vec>, + resources: Vec>, } let sidebar = builder.reg.render( "extras/index-injection", &SidebarTemplateData { resources: builder - .blog_builder + .resource_builders + .get("blog") + .expect("missing blog builder") .loaded_metadata .iter() .take(3) .map(|(id, v)| ResourceTemplateData { resource: v, id: id.clone(), - extra: (), timestamp: v.timestamp, }) .collect(), diff --git a/src/images.rs b/src/images.rs deleted file mode 100644 index 720c624..0000000 --- a/src/images.rs +++ /dev/null @@ -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, - /// The image's file path. - pub file: String, -} - -impl ImageMetadata { - fn get_image_url(&self, site_config: &SiteConfig) -> eyre::Result { - 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 for ResourceMetadata { - 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 { - Ok(ImageTemplateData { - src: self.inner.get_image_url(site_config)?, - }) - } - - fn get_head_data(&self, site_config: &SiteConfig) -> eyre::Result { - 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()) - } -} diff --git a/src/lib.rs b/src/lib.rs index 531d244..cb72b23 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,5 @@ -mod blog; mod builder; mod extras; -mod images; mod link_list; mod resource; #[cfg(feature = "serve")] @@ -15,7 +13,7 @@ use std::{ use eyre::Context; use rayon::prelude::*; -use resource::EmbedMetadata; +use resource::{EmbedMetadata, ResourceBuilderConfig}; use serde::Deserialize; use url::Url; use util::get_name; @@ -41,20 +39,16 @@ pub struct SiteConfig { pub build: Option, /// A list of Sass stylesheets that will be built. pub sass_styles: Vec, - /// 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. pub cdn_url: Url, - /// Prefix applied to all files uploaded to the site's S3 space. - pub s3_prefix: String, + /// List of resources the site should build. + pub resources: HashMap, } impl SiteConfig { /// Gets a CDN url from the given file name. pub fn cdn_url(&self, file: &str) -> eyre::Result { - 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.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(()) } diff --git a/src/resource.rs b/src/resource.rs index eb87085..f80839f 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,6 +1,5 @@ use std::{ collections::BTreeMap, - marker::PhantomData, path::{Path, PathBuf}, }; @@ -8,14 +7,17 @@ use eyre::Context; use itertools::Itertools; use pulldown_cmark::{Options, Parser}; 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 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. #[derive(Debug, Deserialize, Serialize)] -pub struct ResourceMetadata { +pub struct ResourceMetadata { /// The resource's title. pub title: String, /// The resource's timestamp. @@ -23,9 +25,13 @@ pub struct ResourceMetadata { pub timestamp: OffsetDateTime, /// The resource's tags. pub tags: Vec, + /// Special field that gets transformed to the full CDN URL for the given path. + pub cdn_file: Option, + /// The resource's description, if any. + pub desc: Option, /// Extra resource data not included. #[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. #[serde(default)] pub draft: bool, @@ -35,21 +41,18 @@ pub struct ResourceMetadata { } #[derive(Debug, Serialize)] -pub struct ResourceTemplateData<'r, M, E> { +pub struct ResourceTemplateData<'r> { /// The resource's metadata. #[serde(flatten)] - pub resource: &'r ResourceMetadata, + pub resource: &'r ResourceMetadata, /// The resource's ID. 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. - #[serde(serialize_with = "ResourceTemplateData::::timestamp_formatter")] + #[serde(serialize_with = "ResourceTemplateData::timestamp_formatter")] pub timestamp: OffsetDateTime, } -impl<'r, M, E> ResourceTemplateData<'r, M, E> { +impl<'r> ResourceTemplateData<'r> { fn timestamp_formatter(timestamp: &OffsetDateTime, serializer: S) -> Result where 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 -where - E: Serialize, -{ - fn get_short_desc(&self) -> String; - - fn get_extra_resource_template_data(&self, site_config: &SiteConfig) -> eyre::Result; - - fn get_head_data(&self, _site_config: &SiteConfig) -> eyre::Result { - Ok(String::new()) - } -} - /// struct for adding custom meta content embeds #[derive(Debug, Deserialize)] pub struct EmbedMetadata { @@ -130,8 +119,8 @@ impl EmbedMetadata { } #[derive(Debug, Serialize)] -struct ResourceListTemplateData<'r, M, E> { - resources: Vec<&'r ResourceTemplateData<'r, M, E>>, +struct ResourceListTemplateData<'r> { + resources: Vec<&'r ResourceTemplateData<'r>>, tag: Option<&'r str>, page: usize, page_max: usize, @@ -145,7 +134,7 @@ struct ExtraResourceRenderData { } /// Config for the resource builder. -#[derive(Debug, Default)] +#[derive(Debug, Clone, Default, Deserialize)] pub struct ResourceBuilderConfig { /// Path to where the resources should be loaded from. pub source_path: String, @@ -174,27 +163,20 @@ pub struct ResourceBuilderConfig { } /// Helper to genericize resource building. -#[derive(Debug)] -pub struct ResourceBuilder { +#[derive(Debug, Default)] +pub struct ResourceBuilder { /// The builder's config. pub config: ResourceBuilderConfig, /// The currently loaded resource metadata. - pub loaded_metadata: Vec<(String, ResourceMetadata)>, - _extra: PhantomData, + pub loaded_metadata: Vec<(String, ResourceMetadata)>, } -impl ResourceBuilder -where - M: Serialize + DeserializeOwned, - E: Serialize, - ResourceMetadata: ResourceMethods, -{ +impl ResourceBuilder { /// Creates a new resource builder. pub fn new(config: ResourceBuilderConfig) -> Self { Self { config, loaded_metadata: Default::default(), - _extra: Default::default(), } } @@ -208,13 +190,13 @@ where } /// Loads resource metadata from the given path. - fn load(builder: &SiteBuilder, path: &Path) -> eyre::Result<(String, ResourceMetadata)> { + fn load(builder: &SiteBuilder, path: &Path) -> eyre::Result<(String, ResourceMetadata)> { let id = Self::get_id(path); let input = std::fs::read_to_string(path)?; let mut page = builder .matter - .parse_with_struct::>(&input) + .parse_with_struct::(&input) .ok_or_else(|| eyre::anyhow!("Failed to parse resource front matter"))?; let parser = Parser::new_ext(&page.content, Options::all()); @@ -222,6 +204,9 @@ where pulldown_cmark::html::push_html(&mut html, parser); 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)) } @@ -233,6 +218,7 @@ where for e in builder .site .site_path + .join(RESOURCES_PATH) .join(&self.config.source_path) .read_dir()? { @@ -262,13 +248,12 @@ where &self, builder: &SiteBuilder, id: String, - resource: &ResourceMetadata, + resource: &ResourceMetadata, ) -> eyre::Result<()> { let out_path = self.build_path(&builder.build_path, &id); let out = { let data = ResourceTemplateData { resource, - extra: resource.get_extra_resource_template_data(&builder.site.config)?, id, timestamp: resource.timestamp, }; @@ -282,7 +267,20 @@ where }, &out, 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)?; @@ -309,28 +307,22 @@ where let mut data = Vec::with_capacity(lmd.len()); for (id, resource) in lmd.iter() { - let extra = resource.get_extra_resource_template_data(&builder.site.config)?; data.push(ResourceTemplateData { resource, - extra, id: id.clone(), timestamp: resource.timestamp, }); } - fn build_list( + fn build_list( builder: &SiteBuilder, config: &ResourceBuilderConfig, - list: Vec<&ResourceTemplateData>, + list: Vec<&ResourceTemplateData>, title: &str, tag: Option<&str>, out_path: &Path, items_per_page: usize, - ) -> eyre::Result<()> - where - M: Serialize, - E: Serialize, - { + ) -> eyre::Result<()> { if !out_path.exists() { std::fs::create_dir_all(out_path)?; } @@ -381,7 +373,7 @@ where )?; // Build resource lists by tag - let mut tags: BTreeMap>> = BTreeMap::new(); + let mut tags: BTreeMap> = BTreeMap::new(); for resource in &data { for tag in resource.resource.tags.iter().cloned() { tags.entry(tag).or_default().push(resource); @@ -442,7 +434,7 @@ where ))? .to_string(), )) - .description(Some(resource.resource.get_short_desc())) + .description(resource.resource.desc.clone()) .pub_date(Some(resource.timestamp.format(&Rfc2822)?)) .content(Some( builder.reg.render(&self.config.rss_template, &resource)?, @@ -472,13 +464,3 @@ where Ok(()) } } - -impl Default for ResourceBuilder { - fn default() -> Self { - Self { - config: Default::default(), - loaded_metadata: Default::default(), - _extra: Default::default(), - } - } -} diff --git a/src/serving.rs b/src/serving.rs index ef44df6..c19796d 100644 --- a/src/serving.rs +++ b/src/serving.rs @@ -18,7 +18,10 @@ use warp::{ 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( build_path: PathBuf, @@ -31,6 +34,30 @@ fn rel(path: &Path, prefix: &Path) -> Result 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. fn create( builder: &mut SiteBuilder, @@ -57,12 +84,12 @@ fn create( builder.refresh_template(&template_name_str, path)?; if build { builder.site.build_all_pages(builder)?; - builder.build_images()?; - builder.build_blog()?; + builder.build_all_resources()?; } } else if relative_path.display().to_string() == "config.yaml" { let new_config = serde_yml::from_str(&std::fs::read_to_string(path)?)?; builder.site.config = new_config; + builder.reload()?; builder.site.build_all_pages(builder)?; } else if let Ok(_sass_path) = relative_path.strip_prefix(SASS_PATH) { if build { @@ -70,14 +97,8 @@ fn create( } } else if let Ok(root_path) = relative_path.strip_prefix(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) { - // HACK: this could get very inefficient with a larger number of images. should definitely optimize - 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()?; + } else if let Ok(resources_path) = relative_path.strip_prefix(RESOURCES_PATH) { + build_resources(builder, resources_path)?; } 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")?; } else if let Ok(root_path) = relative_path.strip_prefix(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) { - // HACK: same as in `create` - 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()?; + } else if let Ok(resources_path) = relative_path.strip_prefix(RESOURCES_PATH) { + build_resources(builder, resources_path)?; } Ok(()) @@ -141,9 +156,8 @@ impl Site { } builder.build_sass().wrap_err("Failed to build Sass")?; builder - .build_images() - .wrap_err("Failed to build image pages")?; - builder.build_blog().wrap_err("Failed to build blog")?; + .build_all_resources() + .wrap_err("Failed to build resources")?; // Map of websocket connections let peers: Arc>> =