make resources configurable by the user without modifying code

This commit is contained in:
zyl 2024-11-04 13:00:25 -08:00
parent 50574bf352
commit 159c8fa5b3
Signed by: zyl
SSH key fingerprint: SHA256:uxxbSXbdroP/OnKBGnEDk5q7EKB2razvstC/KmzdXXs
21 changed files with 181 additions and 337 deletions

View file

@ -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

View file

@ -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.
---

View file

@ -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.
---

View file

@ -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.
---

View file

@ -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.
---

View file

@ -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.
---

View file

@ -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]
---

View file

@ -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]
---

View file

@ -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]
---

View file

@ -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]
---

View file

@ -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]
---

View file

@ -6,8 +6,8 @@
{{/if}}
<div class="header-image-wrapper">
<p class="short-desc">{{desc}}</p>
<img class="header-image" src="{{header_image}}" alt="{{header_image_alt}}"
style="object-fit: {{object_fit}}; object-position: {{object_position}};">
<img class="header-image" src="{{cdn_file}}" alt="{{header_image_alt}}"
style="object-fit: cover; object-position: 50% 50%">
</div>
<div class="content">
{{{content}}}

View file

@ -1,9 +1,9 @@
<div class="image-full">
<h1 class="title">{{title}}</h1>
<span class="timestamp">published {{timestamp}}</span>
<img class="image-actual" src="{{src}}" alt="{{alt}}">
<img class="image-actual" src="{{cdn_file}}" alt="{{alt}}">
{{{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>
<div class="image-tags">
{{#each tags}}

View file

@ -16,7 +16,7 @@
<div class="images-list">
{{#each resources}}
<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>
</a>
{{/each}}

View file

@ -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())
}
}

View file

@ -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<crate::images::ImageMetadata, crate::images::ImageTemplateData>,
/// Resource builder for the site's blog section.
pub blog_builder:
ResourceBuilder<crate::blog::BlogPostMetadata, crate::blog::BlogPostTemplateData>,
/// The resource builders available to the builder.
pub resource_builders: HashMap<String, ResourceBuilder>,
}
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::<Vec<_>>() {
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)
}
}

View file

@ -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> {
}
}
/// Extra to append a tempalte to the page.
fn append_to(page: &str, content: &str, selector: &str) -> eyre::Result<String> {
Ok(lol_html::rewrite_str(
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> {
#[derive(Debug, Serialize)]
struct SidebarTemplateData<'r> {
// resources: Vec<&'r ResourceMetadata<BlogPostMetadata>>,
resources: Vec<ResourceTemplateData<'r, BlogPostMetadata, ()>>,
resources: Vec<ResourceTemplateData<'r>>,
}
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(),

View file

@ -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())
}
}

View file

@ -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<String>,
/// A list of Sass stylesheets that will be built.
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.
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<String, ResourceBuilderConfig>,
}
impl SiteConfig {
/// Gets a CDN url from the given file name.
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.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(())
}

View file

@ -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<T> {
pub struct ResourceMetadata {
/// The resource's title.
pub title: String,
/// The resource's timestamp.
@ -23,9 +25,13 @@ pub struct ResourceMetadata<T> {
pub timestamp: OffsetDateTime,
/// The resource's tags.
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.
#[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<T> {
}
#[derive(Debug, Serialize)]
pub struct ResourceTemplateData<'r, M, E> {
pub struct ResourceTemplateData<'r> {
/// The resource's metadata.
#[serde(flatten)]
pub resource: &'r ResourceMetadata<M>,
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::<M, E>::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<S>(timestamp: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Error>
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<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
#[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<M, E> {
#[derive(Debug, Default)]
pub struct ResourceBuilder {
/// The builder's config.
pub config: ResourceBuilderConfig,
/// The currently loaded resource metadata.
pub loaded_metadata: Vec<(String, ResourceMetadata<M>)>,
_extra: PhantomData<E>,
pub loaded_metadata: Vec<(String, ResourceMetadata)>,
}
impl<M, E> ResourceBuilder<M, E>
where
M: Serialize + DeserializeOwned,
E: Serialize,
ResourceMetadata<M>: ResourceMethods<E>,
{
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<M>)> {
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::<ResourceMetadata<M>>(&input)
.parse_with_struct::<ResourceMetadata>(&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<M>,
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<M, E>(
fn build_list(
builder: &SiteBuilder,
config: &ResourceBuilderConfig,
list: Vec<&ResourceTemplateData<M, E>>,
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<String, Vec<&ResourceTemplateData<M, E>>> = BTreeMap::new();
let mut tags: BTreeMap<String, Vec<&ResourceTemplateData>> = 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<M, E> Default for ResourceBuilder<M, E> {
fn default() -> Self {
Self {
config: Default::default(),
loaded_metadata: Default::default(),
_extra: Default::default(),
}
}
}

View file

@ -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<PathBuf, std::path::StripPrefixErro
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.
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<Mutex<HashMap<SocketAddr, WebSocket>>> =