custom front matter parser for showing errors + make all extras configurable from the page side

This commit is contained in:
zyl 2024-11-04 16:20:03 -08:00
parent 159c8fa5b3
commit 76c75a40d9
Signed by: zyl
SSH key fingerprint: SHA256:uxxbSXbdroP/OnKBGnEDk5q7EKB2razvstC/KmzdXXs
9 changed files with 119 additions and 97 deletions

48
Cargo.lock generated
View file

@ -99,12 +99,6 @@ version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13" checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13"
[[package]]
name = "arraydeque"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
[[package]] [[package]]
name = "atom_syndication" name = "atom_syndication"
version = "0.12.4" version = "0.12.4"
@ -749,18 +743,6 @@ dependencies = [
"phf 0.11.2", "phf 0.11.2",
] ]
[[package]]
name = "gray_matter"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31ee6a6070bad7c953b0c8be9367e9372181fed69f3e026c4eb5160d8b3c0222"
dependencies = [
"serde",
"serde_json",
"toml",
"yaml-rust2",
]
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.3.26" version = "0.3.26"
@ -820,15 +802,6 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
[[package]]
name = "hashlink"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
dependencies = [
"hashbrown 0.14.5",
]
[[package]] [[package]]
name = "headers" name = "headers"
version = "0.3.9" version = "0.3.9"
@ -2263,15 +2236,6 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "toml"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "tower-service" name = "tower-service"
version = "0.3.3" version = "0.3.3"
@ -2701,17 +2665,6 @@ version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
name = "yaml-rust2"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8902160c4e6f2fb145dbe9d6760a75e3c9522d8bf796ed7047c85919ac7115f8"
dependencies = [
"arraydeque",
"encoding_rs",
"hashlink",
]
[[package]] [[package]]
name = "yoke" name = "yoke"
version = "0.7.4" version = "0.7.4"
@ -2810,7 +2763,6 @@ dependencies = [
"fs_extra", "fs_extra",
"futures", "futures",
"grass", "grass",
"gray_matter",
"handlebars", "handlebars",
"hotwatch", "hotwatch",
"itertools", "itertools",

View file

@ -10,7 +10,6 @@ eyre = "0.6"
fs_extra = "1.2" fs_extra = "1.2"
futures = {version = "0.3", optional = true} futures = {version = "0.3", optional = true}
grass = {version = "0.13", default-features = false} grass = {version = "0.13", default-features = false}
gray_matter = "0.2"
handlebars = "6" handlebars = "6"
hotwatch = {version = "0.5", optional = true} hotwatch = {version = "0.5", optional = true}
itertools = "0.13" itertools = "0.13"

View file

@ -6,5 +6,7 @@ embed:
title: click title: click
site_name: zyl.gay site_name: zyl.gay
description: click click click description: click click click
extra: click extra:
name: basic
template: extras/click
--- ---

View file

@ -1,5 +1,9 @@
--- ---
extra: index extra:
name: resource-list-outside
template: extras/index-injection
resource: blog
count: 3
--- ---
# zyl is gay # zyl is gay

View file

@ -3,7 +3,6 @@
use std::{collections::HashMap, 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 handlebars::Handlebars; use handlebars::Handlebars;
use lol_html::{element, html_content::ContentType, HtmlRewriter, Settings}; use lol_html::{element, html_content::ContentType, HtmlRewriter, Settings};
use pulldown_cmark::{Options, Parser}; use pulldown_cmark::{Options, Parser};
@ -32,8 +31,6 @@ struct TemplateData<'a, T> {
/// Struct used to build the site. /// Struct used to build the site.
pub struct SiteBuilder<'a> { pub struct SiteBuilder<'a> {
/// The matter instance used to extract front matter.
pub(crate) matter: Matter<YAML>,
/// The Handlebars registry used to render templates. /// The Handlebars registry used to render templates.
pub(crate) reg: Handlebars<'a>, pub(crate) reg: Handlebars<'a>,
/// The site info used to build the site. /// The site info used to build the site.
@ -59,7 +56,6 @@ impl<'a> SiteBuilder<'a> {
} }
Self { Self {
matter: Matter::new(),
reg: Handlebars::new(), reg: Handlebars::new(),
resource_builders: HashMap::new(), resource_builders: HashMap::new(),
site, site,
@ -257,16 +253,14 @@ impl<'a> SiteBuilder<'a> {
/// Helper to build a page without writing it to disk. /// Helper to build a page without writing it to disk.
pub fn build_page_raw_with_extra_data<T>( pub fn build_page_raw_with_extra_data<T>(
&self, &self,
page_metadata: PageMetadata, mut page_metadata: PageMetadata,
page_html: &str, page_html: &str,
extra_data: T, extra_data: T,
) -> eyre::Result<String> ) -> eyre::Result<String>
where where
T: Serialize, T: Serialize,
{ {
let extra = page_metadata let extra = page_metadata.extra.take();
.extra
.and_then(|extra| crate::extras::get_extra(&extra));
let title = match &page_metadata.title { let title = match &page_metadata.title {
Some(page_title) => format!("{} / {}", self.site.config.title, page_title), Some(page_title) => format!("{} / {}", self.site.config.title, page_title),
@ -293,8 +287,10 @@ impl<'a> SiteBuilder<'a> {
// Modify HTML output // Modify HTML output
let mut out = self.rewrite_html(out)?; let mut out = self.rewrite_html(out)?;
if let Some(extra) = extra { if let Some(data) = extra {
out = extra.handle(out, self)?; if let Some(extra) = crate::extras::get_extra(&data.name) {
out = extra.handle(out, self, &data)?;
}
} }
if !self.serving { if !self.serving {
@ -310,19 +306,13 @@ impl<'a> SiteBuilder<'a> {
let input = std::fs::read_to_string(page_path) let input = std::fs::read_to_string(page_path)
.with_context(|| format!("Failed to read page at {}", page_path.display()))?; .with_context(|| format!("Failed to read page at {}", page_path.display()))?;
let page = self.matter.parse(&input); let page = crate::frontmatter::FrontMatter::parse(input)?;
let page_metadata = if let Some(data) = page.data {
data.deserialize()?
} else {
PageMetadata::default()
};
let parser = Parser::new_ext(&page.content, Options::all()); let parser = Parser::new_ext(&page.content, Options::all());
let mut page_html = String::new(); let mut page_html = String::new();
pulldown_cmark::html::push_html(&mut page_html, parser); pulldown_cmark::html::push_html(&mut page_html, parser);
let out = self.build_page_raw(page_metadata, &page_html)?; let out = self.build_page_raw(page.data.unwrap_or_default(), &page_html)?;
let out_path = self.build_path.join(page_name).with_extension("html"); let out_path = self.build_path.join(page_name).with_extension("html");
std::fs::create_dir_all(out_path.parent().unwrap()) std::fs::create_dir_all(out_path.parent().unwrap())

View file

@ -1,32 +1,58 @@
use lol_html::{element, RewriteStrSettings}; use lol_html::{element, RewriteStrSettings};
use serde::Serialize; use serde::{Deserialize, Serialize};
use crate::{builder::SiteBuilder, resource::ResourceTemplateData}; use crate::{builder::SiteBuilder, resource::ResourceTemplateData};
/// Types of extras.
#[derive(Debug)] #[derive(Debug)]
pub enum Extra { pub enum Extra {
Basic(&'static str), /// Simply appends to the page within content.
HtmlModification(fn(page: String, builder: &SiteBuilder) -> eyre::Result<String>), Basic,
/// May modify the HTML output in any way.
HtmlModification(
fn(page: String, builder: &SiteBuilder, data: &ExtraData) -> eyre::Result<String>,
),
} }
impl Extra { impl Extra {
/// runs the handler for the extra /// runs the handler for the extra
pub fn handle(&self, page: String, builder: &SiteBuilder) -> eyre::Result<String> { pub fn handle(
&self,
page: String,
builder: &SiteBuilder,
data: &ExtraData,
) -> eyre::Result<String> {
#[derive(Debug, Deserialize)]
struct BasicData {
template: String,
}
match self { match self {
Self::Basic(template) => { Self::Basic => {
let content = builder.reg.render(template, &())?; let data: BasicData = serde_yml::from_value(data.inner.clone())?;
let content = builder.reg.render(&data.template, &())?;
append_to(&page, &content, "main.page") append_to(&page, &content, "main.page")
} }
Self::HtmlModification(f) => (f)(page, builder), Self::HtmlModification(f) => (f)(page, builder, data),
} }
} }
} }
/// Data for extras.
#[derive(Debug, Deserialize)]
pub struct ExtraData {
/// The name of the extra to run.
pub name: String,
/// The inner data for the extra.
#[serde(flatten)]
pub inner: serde_yml::Value,
}
/// Gets the extra for the given value. /// Gets the extra for the given value.
pub fn get_extra(extra: &str) -> Option<Extra> { pub fn get_extra(extra: &str) -> Option<Extra> {
match extra { match extra {
"index" => Some(Extra::HtmlModification(index)), "basic" => Some(Extra::Basic),
"click" => Some(Extra::Basic("extras/click")), "resource-list-outside" => Some(Extra::HtmlModification(resource_list_outside)),
_ => None, _ => None,
} }
} }
@ -46,22 +72,35 @@ fn append_to(page: &str, content: &str, selector: &str) -> eyre::Result<String>
} }
/// Extra to add a sidebar to the index page with recent blog posts on it. /// Extra to add a sidebar to the index page with recent blog posts on it.
fn index(page: String, builder: &SiteBuilder) -> eyre::Result<String> { fn resource_list_outside(
page: String,
builder: &SiteBuilder,
data: &ExtraData,
) -> eyre::Result<String> {
#[derive(Debug, Deserialize)]
struct ResourceListData {
template: String,
resource: String,
count: usize,
}
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
struct SidebarTemplateData<'r> { struct ResourceListTemplateData<'r> {
resources: Vec<ResourceTemplateData<'r>>, resources: Vec<ResourceTemplateData<'r>>,
} }
let sidebar = builder.reg.render( let data: ResourceListData = serde_yml::from_value(data.inner.clone())?;
"extras/index-injection",
&SidebarTemplateData { let resource_list = builder.reg.render(
&data.template,
&ResourceListTemplateData {
resources: builder resources: builder
.resource_builders .resource_builders
.get("blog") .get(&data.resource)
.expect("missing blog builder") .ok_or_else(|| eyre::eyre!("missing resource builder: {}", data.resource))?
.loaded_metadata .loaded_metadata
.iter() .iter()
.take(3) .take(data.count)
.map(|(id, v)| ResourceTemplateData { .map(|(id, v)| ResourceTemplateData {
resource: v, resource: v,
id: id.clone(), id: id.clone(),
@ -71,5 +110,5 @@ fn index(page: String, builder: &SiteBuilder) -> eyre::Result<String> {
}, },
)?; )?;
append_to(&page, &sidebar, "#content") append_to(&page, &resource_list, "#content")
} }

32
src/frontmatter.rs Normal file
View file

@ -0,0 +1,32 @@
use serde::de::DeserializeOwned;
/// Very basic YAML front matter parser.
#[derive(Debug)]
pub struct FrontMatter<T> {
/// The content past the front matter.
pub content: String,
/// The front matter found, if any.
pub data: Option<T>,
}
impl<T> FrontMatter<T>
where
T: DeserializeOwned,
{
/// Parses the given input for front matter.
pub fn parse(input: String) -> eyre::Result<Self> {
if input.starts_with("---\n") {
if let Some((frontmatter, content)) = input[3..].split_once("---\n") {
let data = serde_yml::from_str(frontmatter)?;
return Ok(Self {
content: content.to_string(),
data,
});
}
}
Ok(Self {
content: input,
data: None,
})
}
}

View file

@ -1,5 +1,6 @@
mod builder; mod builder;
mod extras; mod extras;
mod frontmatter;
mod link_list; mod link_list;
mod resource; mod resource;
#[cfg(feature = "serve")] #[cfg(feature = "serve")]
@ -11,6 +12,7 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use extras::ExtraData;
use eyre::Context; use eyre::Context;
use rayon::prelude::*; use rayon::prelude::*;
use resource::{EmbedMetadata, ResourceBuilderConfig}; use resource::{EmbedMetadata, ResourceBuilderConfig};
@ -73,7 +75,8 @@ pub struct PageMetadata {
#[serde(default)] #[serde(default)]
pub styles: Vec<String>, pub styles: Vec<String>,
/// The extra stuff to run for the page, if any. /// The extra stuff to run for the page, if any.
pub extra: Option<String>, #[serde(default)]
pub extra: Option<ExtraData>,
} }
/// Struct containing information about the site. /// Struct containing information about the site.

View file

@ -10,7 +10,7 @@ use rss::{validation::Validate, ChannelBuilder, ItemBuilder};
use serde::{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}; use crate::{builder::SiteBuilder, frontmatter::FrontMatter, link_list::Link, PageMetadata};
/// Source base path for resources. /// Source base path for resources.
pub const RESOURCES_PATH: &str = "resources"; pub const RESOURCES_PATH: &str = "resources";
@ -194,21 +194,22 @@ impl ResourceBuilder {
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 page = FrontMatter::<ResourceMetadata>::parse(input)
.matter .wrap_err_with(|| eyre::eyre!("Failed to parse resource front matter"))?;
.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()); let parser = Parser::new_ext(&page.content, Options::all());
let mut html = String::new(); let mut html = String::new();
pulldown_cmark::html::push_html(&mut html, parser); pulldown_cmark::html::push_html(&mut html, parser);
page.data.content = html; let mut data = page
if let Some(cdn_file) = page.data.cdn_file { .data
page.data.cdn_file = Some(builder.site.config.cdn_url(&cdn_file)?.to_string()); .ok_or_else(|| eyre::eyre!("missing front matter for file at {path:?}"))?;
data.content = html;
if let Some(cdn_file) = data.cdn_file {
data.cdn_file = Some(builder.site.config.cdn_url(&cdn_file)?.to_string());
} }
Ok((id, page.data)) Ok((id, data))
} }
/// Loads all resource metadata from the given config. /// Loads all resource metadata from the given config.