aboutsummaryrefslogtreecommitdiff
path: root/src/post/frontmatter.rs
blob: 595079f59e3c046bca89eb0d382c545050cb0318 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/// This code was borrowed from @fasterthanlime.
use color_eyre::eyre::Result;
use serde::{Deserialize, Serialize};

#[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>,
}

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 {:?}", ch),
                },
                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))
    }
}