Skip to content

Commit ab98793

Browse files
committed
Add front_matter crate
We might want to move the blog to Zola. In that case, it will still be useful to have a crate that validates and even fixes our front matter. This will be especially important if we decide to duplicate the date of a post in the two front matter fields `date` and `path`, which may be necessary to preserve the existing URL scheme. While the custom static site generator is still present, it can reuse the front matter parsing logic.
1 parent 3d982bf commit ab98793

File tree

5 files changed

+64
-31
lines changed

5 files changed

+64
-31
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[workspace]
2-
members = ["serve"]
2+
members = ["front_matter", "serve"]
33

44
[workspace.package]
55
edition = "2024"
@@ -10,6 +10,7 @@ chrono = "=0.4.40"
1010
color-eyre = "=0.6.3"
1111
comrak = "=0.36.0"
1212
eyre = "=0.6.12"
13+
front_matter = { path = "front_matter" }
1314
insta = "=1.42.2"
1415
rayon = "=1.10.0"
1516
regex = "=1.11.1"
@@ -32,6 +33,7 @@ chrono.workspace = true
3233
color-eyre.workspace = true
3334
comrak = { workspace = true, features = ["bon"] }
3435
eyre.workspace = true
36+
front_matter.workspace = true
3537
rayon.workspace = true
3638
regex.workspace = true
3739
sass-rs.workspace = true

front_matter/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "front_matter"
3+
version = "0.1.0"
4+
edition.workspace = true
5+
6+
[dependencies]
7+
eyre.workspace = true
8+
serde = { workspace = true, features = ["derive"] }
9+
toml.workspace = true

front_matter/src/lib.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use eyre::bail;
2+
use serde::{Deserialize, Serialize};
3+
4+
/// The front matter of a markdown blog post.
5+
#[derive(Debug, PartialEq, Serialize, Deserialize)]
6+
pub struct FrontMatter {
7+
pub title: String,
8+
pub author: String,
9+
#[serde(default)]
10+
pub release: bool,
11+
pub team: Option<String>,
12+
pub layout: String,
13+
}
14+
15+
/// Extracts the front matter from a markdown file.
16+
///
17+
/// The remaining normal markdown content is returned as the second element of
18+
/// the tuple.
19+
pub fn parse(markdown: &str) -> eyre::Result<(FrontMatter, &str)> {
20+
if !markdown.starts_with("+++\n") {
21+
bail!("markdown file must start with the line `+++`");
22+
}
23+
let (front_matter, content) = markdown
24+
.trim_start_matches("+++\n")
25+
.split_once("\n+++\n")
26+
.expect("couldn't find the end of the front matter: `+++`");
27+
28+
Ok((toml::from_str(front_matter)?, content))
29+
}

src/posts.rs

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,12 @@
11
use super::blogs::Manifest;
2-
use eyre::eyre;
2+
use front_matter::FrontMatter;
33
use regex::Regex;
4-
use serde::{Deserialize, Serialize};
4+
use serde::Serialize;
55
use std::{
66
path::{Path, PathBuf},
77
sync::LazyLock,
88
};
99

10-
#[derive(Debug, PartialEq, Deserialize)]
11-
struct TomlHeader {
12-
title: String,
13-
author: String,
14-
#[serde(default)]
15-
release: bool,
16-
team: Option<String>,
17-
layout: String,
18-
}
19-
2010
#[derive(Debug, Clone, Serialize)]
2111
pub struct Post {
2212
pub(crate) filename: String,
@@ -52,23 +42,17 @@ impl Post {
5242
let filename = split.next().unwrap().to_string();
5343

5444
let contents = std::fs::read_to_string(path)?;
55-
if contents.len() < 5 {
56-
return Err(eyre!(
57-
"{path:?} is empty, or too short to have valid front matter"
58-
));
59-
}
6045

61-
// toml headers.... we know the first four bytes of each file are "+++\n"
62-
// so we need to find the end. we need the fours to adjust for those first bytes
63-
let end_of_toml = contents[4..].find("+++").unwrap() + 4;
64-
let toml = &contents[4..end_of_toml];
65-
let TomlHeader {
66-
author,
67-
title,
68-
release,
69-
team: team_string,
70-
layout,
71-
} = toml::from_str(toml)?;
46+
let (
47+
FrontMatter {
48+
author,
49+
title,
50+
release,
51+
team: team_string,
52+
layout,
53+
},
54+
contents,
55+
) = front_matter::parse(&contents)?;
7256

7357
let options = comrak::Options {
7458
render: comrak::RenderOptions::builder().unsafe_(true).build(),
@@ -81,8 +65,7 @@ impl Post {
8165
..comrak::Options::default()
8266
};
8367

84-
// Content starts after "+++\n" (we don't assume an extra newline)
85-
let contents = comrak::markdown_to_html(&contents[end_of_toml + 4..], &options);
68+
let contents = comrak::markdown_to_html(contents, &options);
8669

8770
// finally, the url.
8871
let mut url = PathBuf::from(&*filename);

0 commit comments

Comments
 (0)