markdown-parser 0.1.2

😎 This a crate that can parse a markdown file
Documentation
use enum_iterator_derive::IntoEnumIterator;
use getset::{Getters, Setters};

use crate::{error::Error, Adapter};

pub type MarkdownResult = Result<Markdown, Error>;

/// Markdown
/// it is a struct refer a true md file
///
/// including `format`,`content` and `front_matter`
#[derive(Debug, Getters, Setters, Clone)]
pub struct Markdown {
    #[getset(get = "pub", set = "pub")]
    content: String,
    #[getset(get = "pub", set = "pub")]
    front_matter: String,
    #[getset(get = "pub", set = "pub")]
    format: Format,
}

impl Markdown {
    #[inline]
    pub fn new(content: String, front_matter: String, format: Format) -> Self {
        Markdown {
            format,
            content,
            front_matter,
        }
    }

    /// write data into a file
    /// # Examples
    /// ```
    /// use md_parser::*;
    /// Markdown::write_file("to.md").unwrap();
    /// ```
    pub fn write_file<P>(&self, path: P) -> Result<(), crate::error::Error>
    where
        P: AsRef<std::path::Path>,
    {
        crate::fs::write_file(self, path)
    }

    #[inline]
    /// write data into `Vec<u8>`
    pub fn bytes(&self) -> Vec<u8> {
        let mut v = vec![];
        let sp = self.format().separator();
        v.extend_from_slice(sp.as_bytes());
        v.extend_from_slice(self.front_matter().as_bytes());
        v.extend_from_slice(b"\n");
        v.extend_from_slice(sp.as_bytes());
        v.extend_from_slice(b"\n");
        v.extend_from_slice(self.content().as_bytes());

        v
    }

    #[inline]
    /// display all data as md format into a String
    pub fn display(&self) -> String {
        unsafe { String::from_utf8_unchecked(self.bytes()) }
    }

    /// transform a Markdown struct into another format
    ///
    /// require two types: Data Object and Adapter
    /// # Examples
    ///```
    /// use markdown_parser::*;
    /// use markdown_parser::adapt::{SafeFM, JsonAdapter};
    /// let origin = read_file("toml.md").unwrap();
    /// let md = origin.adapt::<JsonAdapter, SafeFM>().unwrap();
    ///```
    ///
    #[cfg(feature = "adapt")]
    pub fn adapt<A, T>(self) -> MarkdownResult
    where
        A: Adapter + Default,
        T: serde::ser::Serialize + serde::de::DeserializeOwned,
    {
        A::default().adapt::<T>(self)
    }
}

/// format of format matters
///
/// - json
/// - yaml
/// - toml
#[derive(Debug, Clone, IntoEnumIterator, PartialEq)]
pub enum Format {
    JSON,
    YAML,
    TOML,
}

impl Format {
    #[inline]
    /// internal format separator
    fn separator(&self) -> &str {
        match self {
            Format::YAML => "---\n",
            Format::TOML => "+++\n",
            _ => "",
        }
    }

    #[inline]
    /// internal format regex patten
    fn regex_patten(&self) -> &str {
        match self {
            Format::YAML => r"^[[:space:]]*\-\-\-\r?\n((?s).*?(?-s))\-\-\-\r?\n((?s).*(?-s))$",
            Format::TOML => r"^[[:space:]]*\+\+\+\r?\n((?s).*?(?-s))\+\+\+\r?\n((?s).*(?-s))$",
            Format::JSON => r"^[[:space:]]*\{\r?\n((?s).*?(?-s))\}\r?\n((?s).*(?-s))$",
        }
    }
}

/// parse data and guess the `Format`
pub fn parse(input: &str) -> MarkdownResult {
    use enum_iterator::IntoEnumIterator;
    for format in Format::into_enum_iter() {
        let md = parse_format(input, format);
        if md.is_ok() {
            return md;
        }
    }

    Err(crate::ParseError::MissingAllFormat.into())
}

/// parse data to the given `Format`
pub fn parse_format(input: &str, format: Format) -> MarkdownResult {
    let cap = regex::Regex::new(format.regex_patten())?
        .captures(input)
        .ok_or(crate::ParseError::BadFormat(format.clone()))?;

    // json should have `{` and `}`
    let front_matter = if Format::JSON.eq(&format) {
        format!("{{\n{0}\n}}", cap[1].trim())
    } else {
        cap[1].trim().to_string()
    };

    Ok(Markdown {
        format,
        front_matter,
        content: cap[2].trim().to_string(),
    })
}