diff options
Diffstat (limited to 'src/post/frontmatter.rs')
| -rw-r--r-- | src/post/frontmatter.rs | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/src/post/frontmatter.rs b/src/post/frontmatter.rs new file mode 100644 index 0000000..1cc8032 --- /dev/null +++ b/src/post/frontmatter.rs @@ -0,0 +1,114 @@ +/// This code was borrowed from @fasterthanlime. + +use anyhow::{Result}; +use serde::{Serialize, Deserialize}; + +#[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>, +} + +enum State { + SearchForStart, + ReadingMarker { count: usize, end: bool }, + ReadingFrontMatter { buf: String, line_start: bool }, + SkipNewline { end: bool }, +} + +#[derive(Debug, thiserror::Error)] +enum Error { + #[error("EOF while parsing frontmatter")] + EOF, + #[error("Error parsing yaml: {0:?}")] + Yaml(#[from] serde_yaml::Error), +} + +impl Data { + pub fn parse(input: &str) -> Result<(Data, usize)> { + let mut state = State::SearchForStart; + + let mut payload = None; + let offset; + + let mut chars = input.char_indices(); + 'parse: loop { + let (idx, ch) = match chars.next() { + Some(x) => x, + None => return Err(Error::EOF)?, + }; + match &mut state { + State::SearchForStart => match ch { + '-' => { + state = State::ReadingMarker { + count: 1, + end: false, + }; + } + '\n' | '\t' | ' ' => { + // ignore whitespace + } + _ => { + panic!("Start of frontmatter not found"); + } + }, + State::ReadingMarker { count, end } => match ch { + '-' => { + *count += 1; + if *count == 3 { + state = State::SkipNewline { end: *end }; + } + } + _ => { + panic!("Malformed frontmatter marker"); + } + }, + State::SkipNewline { end } => match ch { + '\n' => { + if *end { + offset = idx + 1; + break 'parse; + } else { + state = State::ReadingFrontMatter { + buf: String::new(), + line_start: true, + }; + } + } + _ => panic!("Expected newline, got {:?}",), + }, + State::ReadingFrontMatter { buf, line_start } => match ch { + '-' if *line_start => { + let mut state_temp = State::ReadingMarker { + count: 1, + end: true, + }; + std::mem::swap(&mut state, &mut state_temp); + if let State::ReadingFrontMatter { buf, .. } = state_temp { + payload = Some(buf); + } else { + unreachable!(); + } + } + ch => { + buf.push(ch); + *line_start = ch == '\n'; + } + }, + } + } + + // unwrap justification: option set in state machine, Rust can't statically analyze it + let payload = payload.unwrap(); + + let fm: Self = serde_yaml::from_str(&payload)?; + + Ok((fm, offset)) + } +} |
