mirror of
https://github.com/zyllian/zyllian.github.io.git
synced 2025-05-10 02:26:45 -07:00
many many changes
This commit is contained in:
parent
80bc863cdf
commit
efa0d03315
22 changed files with 360 additions and 45 deletions
86
src/blog.rs
Normal file
86
src/blog.rs
Normal 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()),
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
11
src/lib.rs
11
src/lib.rs
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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>>> =
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue