markdown_parser/
parser.rs

1use enum_iterator_derive::IntoEnumIterator;
2use getset::{Getters, Setters};
3
4use crate::{error::Error, Adapter};
5
6pub type MarkdownResult = Result<Markdown, Error>;
7
8/// Markdown
9/// it is a struct refer a true md file
10///
11/// including `format`,`content` and `front_matter`
12#[derive(Debug, Getters, Setters, Clone)]
13pub struct Markdown {
14    #[getset(get = "pub", set = "pub")]
15    content: String,
16    #[getset(get = "pub", set = "pub")]
17    front_matter: String,
18    #[getset(get = "pub", set = "pub")]
19    format: Format,
20}
21
22impl Markdown {
23    #[inline]
24    pub fn new(content: String, front_matter: String, format: Format) -> Self {
25        Markdown {
26            format,
27            content,
28            front_matter,
29        }
30    }
31
32    /// write data into a file
33    /// # Examples
34    /// ```
35    /// use md_parser::*;
36    /// Markdown::write_file("to.md").unwrap();
37    /// ```
38    pub fn write_file<P>(&self, path: P) -> Result<(), crate::error::Error>
39    where
40        P: AsRef<std::path::Path>,
41    {
42        crate::fs::write_file(self, path)
43    }
44
45    #[inline]
46    /// write data into `Vec<u8>`
47    pub fn bytes(&self) -> Vec<u8> {
48        let mut v = vec![];
49        let sp = self.format().separator();
50        v.extend_from_slice(sp.as_bytes());
51        v.extend_from_slice(self.front_matter().as_bytes());
52        v.extend_from_slice(b"\n");
53        v.extend_from_slice(sp.as_bytes());
54        v.extend_from_slice(b"\n");
55        v.extend_from_slice(self.content().as_bytes());
56
57        v
58    }
59
60    #[inline]
61    /// display all data as md format into a String
62    pub fn display(&self) -> String {
63        unsafe { String::from_utf8_unchecked(self.bytes()) }
64    }
65
66    /// transform a Markdown struct into another format
67    ///
68    /// require two types: Data Object and Adapter
69    /// # Examples
70    ///```
71    /// use markdown_parser::*;
72    /// use markdown_parser::adapt::{SafeFM, JsonAdapter};
73    /// let origin = read_file("toml.md").unwrap();
74    /// let md = origin.adapt::<JsonAdapter, SafeFM>().unwrap();
75    ///```
76    ///
77    #[cfg(feature = "adapt")]
78    pub fn adapt<A, T>(self) -> MarkdownResult
79    where
80        A: Adapter + Default,
81        T: serde::ser::Serialize + serde::de::DeserializeOwned,
82    {
83        A::default().adapt::<T>(self)
84    }
85}
86
87/// format of format matters
88///
89/// - json
90/// - yaml
91/// - toml
92#[derive(Debug, Clone, IntoEnumIterator, PartialEq)]
93pub enum Format {
94    JSON,
95    YAML,
96    TOML,
97}
98
99impl Format {
100    #[inline]
101    /// internal format separator
102    fn separator(&self) -> &str {
103        match self {
104            Format::YAML => "---\n",
105            Format::TOML => "+++\n",
106            _ => "",
107        }
108    }
109
110    #[inline]
111    /// internal format regex patten
112    fn regex_patten(&self) -> &str {
113        match self {
114            Format::YAML => r"^[[:space:]]*\-\-\-\r?\n((?s).*?(?-s))\-\-\-\r?\n((?s).*(?-s))$",
115            Format::TOML => r"^[[:space:]]*\+\+\+\r?\n((?s).*?(?-s))\+\+\+\r?\n((?s).*(?-s))$",
116            Format::JSON => r"^[[:space:]]*\{\r?\n((?s).*?(?-s))\}\r?\n((?s).*(?-s))$",
117        }
118    }
119}
120
121/// parse data and guess the `Format`
122pub fn parse(input: &str) -> MarkdownResult {
123    use enum_iterator::IntoEnumIterator;
124    for format in Format::into_enum_iter() {
125        let md = parse_format(input, format);
126        if md.is_ok() {
127            return md;
128        }
129    }
130
131    Err(crate::ParseError::MissingAllFormat.into())
132}
133
134/// parse data to the given `Format`
135pub fn parse_format(input: &str, format: Format) -> MarkdownResult {
136    let cap = regex::Regex::new(format.regex_patten())?
137        .captures(input)
138        .ok_or(crate::ParseError::BadFormat(format.clone()))?;
139
140    // json should have `{` and `}`
141    let front_matter = if Format::JSON.eq(&format) {
142        format!("{{\n{0}\n}}", cap[1].trim())
143    } else {
144        cap[1].trim().to_string()
145    };
146
147    Ok(Markdown {
148        format,
149        front_matter,
150        content: cap[2].trim().to_string(),
151    })
152}