summaryrefslogtreecommitdiff
path: root/src/blog/post.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/blog/post.rs')
-rw-r--r--src/blog/post.rs170
1 files changed, 170 insertions, 0 deletions
diff --git a/src/blog/post.rs b/src/blog/post.rs
new file mode 100644
index 0000000..abe57aa
--- /dev/null
+++ b/src/blog/post.rs
@@ -0,0 +1,170 @@
+use std::{cmp::Ordering, path::PathBuf};
+use glob::glob;
+use color_eyre::eyre::{Result, Context, eyre};
+use tokio::fs;
+
+use chrono::prelude::*;
+
+
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub struct Post {
+ pub front_matter: frontmatter::Data,
+ pub body_html: String,
+ pub date: DateTime<FixedOffset>,
+}
+
+impl Ord for Post {
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.partial_cmp(&other).unwrap()
+ }
+}
+
+impl PartialOrd for Post {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ Some(self.date.cmp(&other.date))
+ }
+}
+
+async fn read_post(dir: &str, fname: PathBuf) -> Result<Post> {
+ let body = fs::read_to_string(fname.clone())
+ .await
+ .wrap_err_with(|| format!("couldn't read {:?}", fname))?;
+
+ let (front_matter, content_offset) = frontmatter::Data::parse(body.clone().as_str())
+ .wrap_err_with(|| format!("can't parse frontmatter of {:?}", fname))?;
+
+ let body = &body[content_offset..];
+ let date = NaiveDate::parse_from_str(&front_matter.clone().date, "%Y-%m-%d")
+ .map_err(|why| eyre!("error parsing date in {:?}: {}", fname, why))?;
+ let link = format!("{}/{}", dir, fname.file_stem().unwrap().to_str().unwrap());
+ let body_html = crate::internal::markdown::render(&body)
+ .wrap_err_with(|| format!("can't parse markdown for {:?}", fname))?;
+ let date: DateTime<FixedOffset> = DateTime::<Utc>::from_utc(NaiveDateTime::new(date, NaiveTime::from_hms(0,0,0)), Utc)
+ .with_timezone(&Utc)
+ .into();
+
+ Ok(Post {
+ front_matter,
+ body_html,
+ date
+ } )
+}
+
+pub async fn load(dir: &str) -> Result<Vec<Post>> {
+ let futs = glob(&format!("{}/*.md", dir))?
+ .filter_map(Result::ok)
+ .map(|fname| read_post(dir, fname));
+
+ Ok(Vec::new())
+}
+
+mod frontmatter {
+ use serde::{Serialize, Deserialize};
+ use color_eyre::eyre::Result;
+ #[derive(Eq, PartialEq, Deserialize, Default, Debug, Serialize, Clone)]
+ pub struct Data {
+ pub title: String,
+ pub date: String,
+ pub series: Option<Vec<String>>,
+ pub tags: Option<Vec<String>>,
+ }
+
+ enum ParseState {
+ Searching,
+ ReadingFM { buf: String, line_start: bool },
+ SkipNewLine { end: bool },
+ ReadingMark { count: usize, end: bool },
+ }
+
+ #[derive(Debug, thiserror::Error)]
+ enum Error {
+ #[error("EOF while parsing")]
+ EOF,
+ #[error("Error parsing YAML: {0:?}")]
+ Yaml(#[from] serde_yaml::Error),
+ }
+
+ impl Data {
+ pub fn parse(input: &str) -> Result<(Data, usize)> {
+ let mut state = ParseState::Searching;
+
+ let mut payload = None;
+ let offset;
+
+ let mut chars = input.char_indices();
+ 'parse: loop {
+ let (i, ch) = match chars.next() {
+ Some(x) => x,
+ None => return Err(Error::EOF)?,
+ };
+ match &mut state {
+ ParseState::Searching => match ch {
+ '-' => {
+ state = ParseState::ReadingMark {
+ count: 1,
+ end: false,
+ };
+ },
+ '\n' | '\t' | ' ' => {
+
+ },
+ _ => {
+ panic!("Start of frontmatter not found!");
+ }
+ },
+ ParseState::ReadingMark {count, end } => match ch {
+ '-' => {
+ *count += 1;
+ if *count == 3 {
+ state = ParseState::SkipNewLine{ end: *end };
+ }
+ }
+ _ => {
+ panic!("Malformed frontmatter");
+ }
+ },
+ ParseState::SkipNewLine { end } => match ch {
+ '\n' => {
+ if *end {
+ offset = i + 1;
+ break 'parse;
+ } else {
+ state = ParseState::ReadingFM {
+ buf: String::new(),
+ line_start: true,
+ };
+ }
+ },
+ _ => {
+ panic!("Expected newline, got {:?}", ch);
+ }
+ },
+ ParseState::ReadingFM { buf, line_start } => match ch {
+ '-' if *line_start => {
+ let mut state_tmp = ParseState::ReadingMark {
+ count: 1,
+ end: true,
+ };
+ std::mem::swap(&mut state, &mut state_tmp);
+ if let ParseState::ReadingFM {buf, ..} = state_tmp {
+ payload = Some(buf);
+ } else {
+ unreachable!();
+ }
+ },
+ ch => {
+ buf.push(ch);
+ *line_start = ch == '\n';
+ }
+ }
+ }
+ }
+
+ let payload = payload.unwrap();
+
+ let fm = serde_yaml::from_str(&payload)?;
+
+ Ok((fm, offset))
+ }
+ }
+}