diff options
author | Cara Salter <cara@devcara.com> | 2021-12-19 12:48:36 -0500 |
---|---|---|
committer | Cara Salter <cara@devcara.com> | 2021-12-19 12:48:36 -0500 |
commit | c4d7f8f50d53057005d6bb28ac487f69ea45bd5e (patch) | |
tree | 34968e205ebbb97264a2df4e8951af5a01db22cb /src/blog/post.rs | |
parent | ef01d3badb9765271a5bf55a4f0702b68f099e2e (diff) | |
download | site-c4d7f8f50d53057005d6bb28ac487f69ea45bd5e.tar.gz site-c4d7f8f50d53057005d6bb28ac487f69ea45bd5e.zip |
Add blog post parsing and templates
templates WIP, not building properly
Diffstat (limited to 'src/blog/post.rs')
-rw-r--r-- | src/blog/post.rs | 170 |
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)) + } + } +} |