quatrain 0.6.0

Not intended to be a static site generator
Documentation
use super::readable::ReadableDate;
use serde::{self, Deserialize, Deserializer, Serialize, Serializer};

#[derive(Debug, Clone, PartialEq)]
pub enum Lang {
    Apple,
    Ringo,
    Pinggo,
    Apfel,
}

impl Default for Lang {
    fn default() -> Self {
        Self::Apple
    }
}

impl Serialize for Lang {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(match *self {
            Self::Apple => "Apple",
            Self::Ringo => "林檎",
            Self::Pinggo => "苹果",
            Self::Apfel => "Apfel",
        })
    }
}

impl<'de> Deserialize<'de> for Lang {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        match String::deserialize(deserializer)?.as_ref() {
            "Apple" | "Apples" | "apple" | "apples" => Ok(Self::Apple),
            "林檎" | "リンゴ" | "りんご" | "Ringo" => Ok(Self::Ringo),
            "苹果" | "蘋果" | "Pinggo" => Ok(Self::Pinggo),
            "Apfel" | "Äpfel" => Ok(Self::Apfel),
            s @ _ => Err(serde::de::Error::custom(format!(
                "key [status] value not supported value {}",
                s
            ))),
        }
    }
}

/// The status of the file. This is reserved for future usage,
#[derive(Debug, Clone, PartialEq)]
pub enum Status {
    /// Mark the file as a draft.
    ///
    /// WIP, not finished yet.
    Draft,

    /// File could be viewed in public scope.
    ///
    /// Finished and could be hosted publicly.
    Public,

    /// File could be viewed in private scope.
    ///
    /// Finished but concern about the privacy.
    Internal,
}

impl Default for Status {
    fn default() -> Self {
        Self::Draft
    }
}

impl Serialize for Status {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(match *self {
            Self::Draft => "Draft",
            Self::Public => "Public",
            Self::Internal => "Internal",
        })
    }
}

impl<'de> Deserialize<'de> for Status {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        match String::deserialize(deserializer)?.as_ref() {
            "Draft" => Ok(Self::Draft),
            "Public" => Ok(Self::Public),
            "Internal" => Ok(Self::Internal),
            s @ _ => Err(serde::de::Error::custom(format!(
                "key [status] value not supported value {}",
                s
            ))),
        }
    }
}

#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct Banner {
    /// Create the outdate banner once the duration since last update is larger than
    /// this value in day.
    #[serde(rename = "Outdate", default)]
    pub outdate: i64,
}

fn default_template() -> String {
    "Article".into()
}

fn default_stylesheet() -> String {
    "latest".into()
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Render {
    /// Pick the template for current source file.
    #[serde(rename = "Template", default = "default_template")]
    pub template: String,

    /// The output file path.
    #[serde(rename = "Target", default)]
    pub target: Option<std::path::PathBuf>,

    /// Stylesheet
    #[serde(rename = "Stylesheet", default = "default_stylesheet")]
    pub stylesheet: String,
}

impl Default for Render {
    fn default() -> Self {
        Self {
            template: "Article".to_owned(),
            target: None,
            stylesheet: "latest".to_owned(),
        }
    }
}

/// The frontmatter in a Markdown file.
///
/// **Note**, mercurius only parses the frontmatter, it would never modify the parsing result
/// itself. Functionalities below is implemented by another binary package named [quatrain].
///
/// [quatrain]: https://github.com/equt/quatrain
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct FrontMatter {
    /// The title of the current file.
    ///
    /// ## HTML Rendering
    ///
    /// 1. Title would be used in the `<title>` tag.
    /// 2. It would also be prepended as an `<h1>` at the front of the article.
    #[serde(rename = "Title")]
    pub title: String,

    /// The create date of the file.
    ///
    /// This should be the exact date the file was created, even empty.
    #[serde(rename = "Create Date")]
    pub create_date: ReadableDate,

    /// The last modified date of the file.
    ///
    /// > Formatting or _rewriting_ should not be taken into account.
    ///
    /// ## HTML Render
    ///
    /// 1. This field is now used to calculate the [_Outdate Info_].
    ///
    /// [_Outdate Info_]: struct.Banner.html#structfield.outdate
    #[serde(rename = "Last Update", default)]
    pub last_update: Option<ReadableDate>,

    /// Main language of the file.
    ///
    /// This language should be the one used most in this file. Use the word `Apple` to
    /// refer that language, e.g., `林檎`.
    #[serde(rename = "Language", default)]
    pub lang: Lang,

    /// The description of the file.
    ///
    /// Currently this field will not be used in any place. This is reserved for future usage,
    /// e.g., the `<meta>` tag.
    #[serde(rename = "Description", default)]
    pub description: Option<String>,

    /// Tagging the file.
    ///
    /// Currently this field will not be used in any place. This is reserved for future usage,
    /// e.g., filtering by tags.
    #[serde(rename = "Tags")]
    pub tags: Option<Vec<String>>,

    /// The status of the file.
    ///
    /// Currently this field will not be used in any place. This is reserved for future usage,
    /// e.g., conditional rendering or publishing.
    #[serde(rename = "Status", default)]
    pub status: Status,

    /// Control the rendering behavior.
    #[serde(rename = "Render", default)]
    pub render: Render,

    /// The banner of the file.
    #[serde(rename = "Banner", default)]
    pub banner: Banner,
}

#[cfg(test)]
mod test {
    use super::*;
    use serde_yaml;

    #[test]
    fn deserialize() {
        let fm: FrontMatter = serde_yaml::from_str(
            r"
Title: Foo

Create Date: Jan 12, 2001
Last Update: Feb 02, 2002
",
        )
        .unwrap();
        assert_eq!(
            fm,
            FrontMatter {
                title: "Foo".to_owned(),
                create_date: ReadableDate::new(2001, 1, 12),
                last_update: Some(ReadableDate::new(2002, 2, 02)),
                status: Status::Draft,
                ..Default::default()
            }
        )
    }
}