many many changes

This commit is contained in:
Zoey 2023-06-09 21:55:04 -07:00
parent 80bc863cdf
commit efa0d03315
22 changed files with 360 additions and 45 deletions

86
src/blog.rs Normal file
View file

@ -0,0 +1,86 @@
use serde::{Deserialize, Serialize};
use crate::{
builder::SiteBuilder,
resource::{ResourceBuilder, ResourceBuilderConfig, ResourceMetadata, ResourceMethods},
};
pub const BLOG_PATH: &str = "blog";
/// Builds the blog.
pub fn build_blog(site_builder: &SiteBuilder) -> anyhow::Result<()> {
let config = 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: "Zyllian's blog".to_string(),
rss_description: "Feed of recent blog posts on Zyllian'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_builder.site.config.blog_posts_per_page,
};
let mut builder = ResourceBuilder::<BlogPostMetadata, BlogPostTemplateData>::new(config);
builder.load_all(site_builder)?;
builder.build_all(site_builder)?;
Ok(())
}
/// 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>,
}
/// 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: &crate::SiteConfig,
) -> anyhow::Result<BlogPostTemplateData> {
// TODO: render markdown
Ok(BlogPostTemplateData {
header_image: site_config
.cdn_url(&self.inner.header_image_file)?
.to_string(),
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()),
})
}
}

View file

@ -25,7 +25,7 @@ struct TemplateData<'a> {
/// Struct used to build the site.
pub struct SiteBuilder<'a> {
/// The matter instance used to extract front matter.
matter: Matter<YAML>,
pub(crate) matter: Matter<YAML>,
/// The Handlebars registry used to render templates.
pub(crate) reg: Handlebars<'a>,
/// The site info used to build the site.
@ -242,4 +242,9 @@ impl<'a> SiteBuilder<'a> {
pub fn build_images(&self) -> anyhow::Result<()> {
crate::images::build_images(self)
}
/// Builds the site's blog.
pub fn build_blog(&self) -> anyhow::Result<()> {
crate::blog::build_blog(self)
}
}

View file

@ -1,5 +1,4 @@
use serde::{Deserialize, Serialize};
use url::Url;
use crate::{
builder::SiteBuilder,
@ -28,7 +27,7 @@ pub fn build_images(site_builder: &SiteBuilder) -> anyhow::Result<()> {
};
let mut builder = ResourceBuilder::<ImageMetadata, ImageTemplateData>::new(config);
builder.load_all(&site_builder.site.site_path)?;
builder.load_all(site_builder)?;
builder.build_all(site_builder)?;
Ok(())
@ -45,13 +44,6 @@ pub struct ImageMetadata {
pub file: String,
}
impl ImageMetadata {
/// Gets an image's CDN url.
pub fn cdn_url(&self, config: &SiteConfig) -> anyhow::Result<Url> {
Ok(config.cdn_url.join(&config.s3_prefix)?.join(&self.file)?)
}
}
/// Template data for a specific image.
#[derive(Debug, Serialize)]
struct ImageTemplateData {
@ -70,7 +62,7 @@ impl ResourceMethods<ImageTemplateData> for ResourceMetadata<ImageMetadata> {
site_config: &SiteConfig,
) -> anyhow::Result<ImageTemplateData> {
Ok(ImageTemplateData {
src: self.inner.cdn_url(site_config)?.to_string(),
src: site_config.cdn_url(&self.inner.file)?.to_string(),
})
}
}

View file

@ -1,3 +1,4 @@
mod blog;
mod builder;
mod images;
mod link_list;
@ -38,12 +39,21 @@ pub struct SiteConfig {
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,
}
impl SiteConfig {
/// Gets a CDN url from the given file name.
pub fn cdn_url(&self, file: &str) -> anyhow::Result<Url> {
Ok(self.cdn_url.join(&self.s3_prefix)?.join(file)?)
}
}
/// Struct for the front matter in templates. (nothing here yet)
#[derive(Debug, Default, Deserialize)]
pub struct TemplateMetadata {}
@ -134,6 +144,7 @@ impl Site {
builder.site.build_all_pages(&builder)?;
builder.build_sass()?;
builder.build_images()?;
builder.build_blog()?;
Ok(())
}

View file

@ -6,6 +6,7 @@ use std::{
use anyhow::Context;
use itertools::Itertools;
use pulldown_cmark::{Options, Parser};
use rss::{validation::Validate, ChannelBuilder, ItemBuilder};
use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer};
use time::{format_description::well_known::Rfc2822, OffsetDateTime};
@ -25,6 +26,12 @@ pub struct ResourceMetadata<T> {
/// Extra resource data not included.
#[serde(flatten)]
pub inner: T,
/// Whether the resource is a draft. Drafts can be committed without being published to the live site.
#[serde(default)]
pub draft: bool,
/// The resource's content. Defaults to nothing until loaded in another step.
#[serde(default)]
pub content: String,
}
#[derive(Debug, Serialize)]
@ -141,19 +148,39 @@ where
}
/// Loads resource metadata from the given path.
fn load(path: &Path) -> anyhow::Result<(String, ResourceMetadata<M>)> {
fn load(builder: &SiteBuilder, path: &Path) -> anyhow::Result<(String, ResourceMetadata<M>)> {
let id = Self::get_id(path);
let metadata = serde_yaml::from_str(&std::fs::read_to_string(path)?)?;
Ok((id, metadata))
let input = std::fs::read_to_string(path)?;
let mut page = builder
.matter
.parse_with_struct::<ResourceMetadata<M>>(&input)
.ok_or_else(|| anyhow::anyhow!("Failed to parse resource front matter"))?;
let parser = Parser::new_ext(&page.content, Options::all());
let mut html = String::new();
pulldown_cmark::html::push_html(&mut html, parser);
page.data.content = html;
Ok((id, page.data))
}
/// Loads all resource metadata from the given config.
pub fn load_all(&mut self, site_path: &Path) -> anyhow::Result<()> {
pub fn load_all(&mut self, builder: &SiteBuilder) -> anyhow::Result<()> {
self.loaded_metadata.clear();
for e in site_path.join(&self.config.source_path).read_dir()? {
for e in builder
.site
.site_path
.join(&self.config.source_path)
.read_dir()?
{
let p = e?.path();
if let Some("yml") = p.extension().and_then(|e| e.to_str()) {
let (id, metadata) = Self::load(&p)?;
if let Some("md") = p.extension().and_then(|e| e.to_str()) {
let (id, metadata) = Self::load(builder, &p)?;
if cfg!(not(debug_assertions)) && metadata.draft {
continue;
}
self.loaded_metadata.push((id, metadata));
}
}
@ -201,6 +228,16 @@ where
}
pub fn build_all(&self, builder: &SiteBuilder) -> anyhow::Result<()> {
let out_short = builder.build_path.join(&self.config.output_path_short);
let out_long = builder.build_path.join(&self.config.output_path_long);
if !out_short.exists() {
std::fs::create_dir_all(&out_short)?;
}
if !out_long.exists() {
std::fs::create_dir_all(&out_long)?;
}
for (id, resource) in &self.loaded_metadata {
self.build(builder, id.clone(), resource)?;
}
@ -267,8 +304,6 @@ where
Ok(())
}
let out_path = builder.build_path.join(&self.config.output_path_short);
// Build main list of resources
build_list(
builder,
@ -276,7 +311,7 @@ where
data.iter().collect(),
&self.config.list_title,
None,
&builder.build_path.join(&self.config.output_path_long),
&out_long,
self.config.resources_per_page,
)?;
@ -310,7 +345,7 @@ where
links,
&self.config.tag_list_title,
)?;
std::fs::write(out_path.join("tags.html"), out)?;
std::fs::write(out_short.join("tags.html"), out)?;
}
for (tag, data) in tags {
@ -320,7 +355,7 @@ where
data,
&format!("{} tagged {tag}", self.config.resource_name_plural),
Some(tag.as_str()),
&out_path.join("tag").join(&tag),
&out_short.join("tag").join(&tag),
self.config.resources_per_page,
)?;
}
@ -367,13 +402,7 @@ where
.build();
channel.validate().context("Failed to validate RSS feed")?;
let out = channel.to_string();
std::fs::write(
builder
.build_path
.join(&self.config.output_path_long)
.join("rss.xml"),
out,
)?;
std::fs::write(out_long.join("rss.xml"), out)?;
Ok(())
}

View file

@ -64,6 +64,7 @@ fn create(
if build {
builder.site.build_all_pages(builder)?;
builder.build_images()?;
builder.build_blog()?;
}
} else if relative_path.display().to_string() == "config.yaml" {
let new_config = serde_yaml::from_str(&std::fs::read_to_string(path)?)?;
@ -78,6 +79,9 @@ fn create(
} 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.build_images()?;
} else if let Ok(_blog_path) = relative_path.strip_prefix(crate::blog::BLOG_PATH) {
// HACK: same as above
builder.build_blog()?;
}
Ok(())
@ -109,6 +113,8 @@ fn remove(builder: &mut SiteBuilder, path: &Path, relative_path: &Path) -> anyho
} else if let Ok(_image_path) = relative_path.strip_prefix(crate::images::IMAGES_PATH) {
// HACK: same as in `create`
builder.build_images()?;
} else if let Ok(_blog_path) = relative_path.strip_prefix(crate::blog::BLOG_PATH) {
// HACK: same as above
}
Ok(())
@ -138,6 +144,7 @@ impl Site {
builder
.build_images()
.context("Failed to build image pages")?;
builder.build_blog().context("Failed to build blog")?;
// Map of websocket connections
let peers: Arc<Mutex<HashMap<SocketAddr, WebSocket>>> =