add _xesite_frontmatter extension
Signed-off-by: Xe Iaso <me@christine.website>
This commit is contained in:
parent
8b6056fc09
commit
7f6de2cb09
|
@ -3205,6 +3205,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"xesite_types",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3260,9 +3261,18 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
"uuid 0.8.2",
|
"uuid 0.8.2",
|
||||||
"xe_jsonfeed",
|
"xe_jsonfeed",
|
||||||
|
"xesite_types",
|
||||||
"xml-rs",
|
"xml-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xesite_types"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xml-rs"
|
name = "xml-rs"
|
||||||
version = "0.8.4"
|
version = "0.8.4"
|
||||||
|
|
|
@ -48,6 +48,8 @@ xml-rs = "0.8"
|
||||||
url = "2"
|
url = "2"
|
||||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||||
|
|
||||||
|
xesite_types = { path = "./lib/xesite_types" }
|
||||||
|
|
||||||
# workspace dependencies
|
# workspace dependencies
|
||||||
cfcache = { path = "./lib/cfcache" }
|
cfcache = { path = "./lib/cfcache" }
|
||||||
xe_jsonfeed = { path = "./lib/jsonfeed" }
|
xe_jsonfeed = { path = "./lib/jsonfeed" }
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
# JSON Feed Extensions
|
||||||
|
|
||||||
|
Here is the documentation of all of my JSON Feed extensions. I have created
|
||||||
|
these JSON Feed extensions in order to give users more metadata about my
|
||||||
|
articles and talks.
|
||||||
|
|
||||||
|
## `_xesite_frontmatter`
|
||||||
|
|
||||||
|
This extension is added to [JSON Feed
|
||||||
|
Items](https://www.jsonfeed.org/version/1.1/#items-a-name-items-a) and gives
|
||||||
|
readers a copy of the frontmatter data that I annotate my posts with. The
|
||||||
|
contents of this will vary by post, but will have any of the following fields:
|
||||||
|
|
||||||
|
* `about` (required, string) is a link to this documentation. It gives readers
|
||||||
|
of the JSON Feed information about what this extension does. This is for
|
||||||
|
informational purposes only and can safely be ignored by programs.
|
||||||
|
* `series` (optional, string) is the optional blogpost series name that this
|
||||||
|
item belongs to. When I post multiple posts about the same topic, I will
|
||||||
|
usually set the `series` to the same value so that it is more discoverable [on
|
||||||
|
my series index page](https://xeiaso.net/blog/series).
|
||||||
|
* `slides_link` (optional, string) is a link to the PDF containing the slides
|
||||||
|
for a given talk. This is always set on talks, but is technically optional
|
||||||
|
because not everything I do is a talk.
|
||||||
|
* `vod` (optional, string) is an object that describes where you can watch the
|
||||||
|
Video On Demand (vod) for the writing process of a post. This is an object
|
||||||
|
that always contains the fields `twitch` and `youtube`. These will be URLs to
|
||||||
|
the videos so that you can watch them on demand.
|
|
@ -13,3 +13,5 @@ error-chain = "0.12"
|
||||||
serde = "1"
|
serde = "1"
|
||||||
serde_derive = "1"
|
serde_derive = "1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
||||||
|
xesite_types = { path = "../xesite_types" }
|
||||||
|
|
|
@ -90,6 +90,7 @@ pub struct ItemBuilder {
|
||||||
pub author: Option<Author>,
|
pub author: Option<Author>,
|
||||||
pub tags: Option<Vec<String>>,
|
pub tags: Option<Vec<String>>,
|
||||||
pub attachments: Option<Vec<Attachment>>,
|
pub attachments: Option<Vec<Attachment>>,
|
||||||
|
pub xesite_frontmater: Option<xesite_types::Frontmatter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ItemBuilder {
|
impl ItemBuilder {
|
||||||
|
@ -108,6 +109,7 @@ impl ItemBuilder {
|
||||||
author: None,
|
author: None,
|
||||||
tags: None,
|
tags: None,
|
||||||
attachments: None,
|
attachments: None,
|
||||||
|
xesite_frontmater: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,6 +182,11 @@ impl ItemBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn xesite_frontmatter(mut self, fm: xesite_types::Frontmatter) -> ItemBuilder {
|
||||||
|
self.xesite_frontmater = Some(fm);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build(self) -> Result<Item> {
|
pub fn build(self) -> Result<Item> {
|
||||||
if self.id.is_none() || self.content.is_none() {
|
if self.id.is_none() || self.content.is_none() {
|
||||||
return Err("missing field 'id' or 'content_*'".into());
|
return Err("missing field 'id' or 'content_*'".into());
|
||||||
|
@ -198,6 +205,7 @@ impl ItemBuilder {
|
||||||
author: self.author,
|
author: self.author,
|
||||||
tags: self.tags,
|
tags: self.tags,
|
||||||
attachments: self.attachments,
|
attachments: self.attachments,
|
||||||
|
xesite_frontmatter: self.xesite_frontmater,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
@ -31,6 +32,9 @@ pub struct Item {
|
||||||
pub author: Option<Author>,
|
pub author: Option<Author>,
|
||||||
pub tags: Option<Vec<String>>,
|
pub tags: Option<Vec<String>>,
|
||||||
pub attachments: Option<Vec<Attachment>>,
|
pub attachments: Option<Vec<Attachment>>,
|
||||||
|
|
||||||
|
// xesite extensions
|
||||||
|
pub xesite_frontmatter: Option<xesite_types::Frontmatter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Item {
|
impl Item {
|
||||||
|
@ -55,6 +59,7 @@ impl Default for Item {
|
||||||
author: None,
|
author: None,
|
||||||
tags: None,
|
tags: None,
|
||||||
attachments: None,
|
attachments: None,
|
||||||
|
xesite_frontmatter: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,6 +118,9 @@ impl Serialize for Item {
|
||||||
if self.attachments.is_some() {
|
if self.attachments.is_some() {
|
||||||
state.serialize_field("attachments", &self.attachments)?;
|
state.serialize_field("attachments", &self.attachments)?;
|
||||||
}
|
}
|
||||||
|
if self.xesite_frontmatter.is_some() {
|
||||||
|
state.serialize_field("_xesite_frontmatter", &self.xesite_frontmatter)?;
|
||||||
|
}
|
||||||
state.end()
|
state.end()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -319,6 +327,7 @@ impl<'de> Deserialize<'de> for Item {
|
||||||
author,
|
author,
|
||||||
tags,
|
tags,
|
||||||
attachments,
|
attachments,
|
||||||
|
xesite_frontmatter: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "xesite_types"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
chrono = { version = "0.4", features = [ "serde" ] }
|
||||||
|
serde = { version = "1.0", features = [ "derive" ] }
|
|
@ -0,0 +1,37 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Deserialize, Default, Debug, Serialize, Clone)]
|
||||||
|
pub struct Frontmatter {
|
||||||
|
#[serde(default = "frontmatter_about")]
|
||||||
|
pub about: String,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
pub title: String,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
pub date: String,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
pub author: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub series: Option<String>,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
pub tags: Option<Vec<String>>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub slides_link: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub image: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub thumb: Option<String>,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
pub redirect_to: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub vod: Option<Vod>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frontmatter_about() -> String {
|
||||||
|
"https://xeiaso.net/blog/api-jsonfeed-extensions#_xesite_frontmatter".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Deserialize, Default, Debug, Serialize, Clone)]
|
||||||
|
pub struct Vod {
|
||||||
|
pub twitch: String,
|
||||||
|
pub youtube: String,
|
||||||
|
}
|
|
@ -1,26 +1,6 @@
|
||||||
/// This code was borrowed from @fasterthanlime.
|
/// This code was borrowed from @fasterthanlime.
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use serde::{Deserialize, Serialize};
|
pub use xesite_types::Frontmatter as Data;
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Deserialize, Default, Debug, Serialize, Clone)]
|
|
||||||
pub struct Data {
|
|
||||||
pub title: String,
|
|
||||||
pub date: String,
|
|
||||||
pub series: Option<String>,
|
|
||||||
pub tags: Option<Vec<String>>,
|
|
||||||
pub slides_link: Option<String>,
|
|
||||||
pub image: Option<String>,
|
|
||||||
pub thumb: Option<String>,
|
|
||||||
pub show: Option<bool>,
|
|
||||||
pub redirect_to: Option<String>,
|
|
||||||
pub vod: Option<Vod>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Deserialize, Default, Debug, Serialize, Clone)]
|
|
||||||
pub struct Vod {
|
|
||||||
pub twitch: String,
|
|
||||||
pub youtube: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum State {
|
enum State {
|
||||||
SearchForStart,
|
SearchForStart,
|
||||||
|
@ -37,8 +17,7 @@ enum Error {
|
||||||
Yaml(#[from] serde_yaml::Error),
|
Yaml(#[from] serde_yaml::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Data {
|
pub fn parse(input: &str) -> Result<(Data, usize)> {
|
||||||
pub fn parse(input: &str) -> Result<(Data, usize)> {
|
|
||||||
let mut state = State::SearchForStart;
|
let mut state = State::SearchForStart;
|
||||||
|
|
||||||
let mut payload = None;
|
let mut payload = None;
|
||||||
|
@ -114,8 +93,7 @@ impl Data {
|
||||||
// unwrap justification: option set in state machine, Rust can't statically analyze it
|
// unwrap justification: option set in state machine, Rust can't statically analyze it
|
||||||
let payload = payload.unwrap();
|
let payload = payload.unwrap();
|
||||||
|
|
||||||
let fm: Self = serde_yaml::from_str(&payload)?;
|
let fm: Data = serde_yaml::from_str(&payload)?;
|
||||||
|
|
||||||
Ok((fm, offset))
|
Ok((fm, offset))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ pub struct NewPost {
|
||||||
impl Into<xe_jsonfeed::Item> for Post {
|
impl Into<xe_jsonfeed::Item> for Post {
|
||||||
fn into(self) -> xe_jsonfeed::Item {
|
fn into(self) -> xe_jsonfeed::Item {
|
||||||
let mut result = xe_jsonfeed::Item::builder()
|
let mut result = xe_jsonfeed::Item::builder()
|
||||||
.title(self.front_matter.title)
|
.title(self.front_matter.title.clone())
|
||||||
.content_html(self.body_html)
|
.content_html(self.body_html)
|
||||||
.id(format!("https://xeiaso.net/{}", self.link))
|
.id(format!("https://xeiaso.net/{}", self.link))
|
||||||
.url(format!("https://xeiaso.net/{}", self.link))
|
.url(format!("https://xeiaso.net/{}", self.link))
|
||||||
|
@ -40,7 +40,8 @@ impl Into<xe_jsonfeed::Item> for Post {
|
||||||
.name("Xe Iaso")
|
.name("Xe Iaso")
|
||||||
.url("https://xeiaso.net")
|
.url("https://xeiaso.net")
|
||||||
.avatar("https://xeiaso.net/static/img/avatar.png"),
|
.avatar("https://xeiaso.net/static/img/avatar.png"),
|
||||||
);
|
)
|
||||||
|
.xesite_frontmatter(self.front_matter.clone());
|
||||||
|
|
||||||
let mut tags: Vec<String> = vec![];
|
let mut tags: Vec<String> = vec![];
|
||||||
|
|
||||||
|
@ -96,7 +97,7 @@ async fn read_post(
|
||||||
let body = fs::read_to_string(fname.clone())
|
let body = fs::read_to_string(fname.clone())
|
||||||
.await
|
.await
|
||||||
.wrap_err_with(|| format!("can't read {:?}", fname))?;
|
.wrap_err_with(|| format!("can't read {:?}", fname))?;
|
||||||
let (front_matter, content_offset) = frontmatter::Data::parse(body.clone().as_str())
|
let (front_matter, content_offset) = frontmatter::parse(body.clone().as_str())
|
||||||
.wrap_err_with(|| format!("can't parse frontmatter of {:?}", fname))?;
|
.wrap_err_with(|| format!("can't parse frontmatter of {:?}", fname))?;
|
||||||
let body = &body[content_offset..];
|
let body = &body[content_offset..];
|
||||||
let date = NaiveDate::parse_from_str(&front_matter.clone().date, "%Y-%m-%d")
|
let date = NaiveDate::parse_from_str(&front_matter.clone().date, "%Y-%m-%d")
|
||||||
|
|
Loading…
Reference in New Issue