overcast 0.1.3

Strongly typed changelogs for projects as changeable as the weather
Documentation
use crate::change::Change;
use std::str::FromStr;

/// A single releases with a collection of changes
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Release {
    pub(crate) summary: Option<String>,
    pub(crate) version: Option<semver::Version>,
    pub(crate) added: Vec<Change>,
    pub(crate) changed: Vec<Change>,
    pub(crate) deprecated: Vec<Change>,
    pub(crate) removed: Vec<Change>,
    pub(crate) fixed: Vec<Change>,
    pub(crate) security: Vec<Change>,
    yanked: bool,
    #[cfg(feature = "dates")]
    date: Option<chrono::NaiveDate>,
}

impl FromStr for Release {
    type Err = semver::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Self {
            version: Some(s.parse()?),
            ..Self::default()
        })
    }
}

impl Release {
    pub(crate) fn format_markdown_title(&self) -> String {
        let mut parts = Vec::new();

        parts.push(match self.version.as_ref() {
            None => "Unreleased".to_string(),
            Some(version) => version.to_string(),
        });

        #[cfg(feature = "dates")]
        if let Some(date) = self.date.as_ref() {
            parts.push(date.to_string())
        }

        if self.yanked {
            parts.push("[YANKED]".to_string())
        }

        parts.join(" - ")
    }

    /// Create a release without a version number. This is used to collect features that have been
    /// developed but not released. See Keep a Changelog for more
    pub fn unreleased() -> Self {
        Self {
            version: None,
            ..Default::default()
        }
    }

    /// Create a release with a version number
    pub fn new(major: u64, minor: u64, patch: u64) -> Self {
        Self {
            version: Some(semver::Version::new(major, minor, patch)),
            ..Default::default()
        }
    }

    /// Add a summary of the release
    pub fn with_summary(self, summary: impl ToString) -> Self {
        Self {
            summary: Some(summary.to_string()),
            ..self
        }
    }

    /// Add a release date to the version.
    ///
    /// # Panics
    /// Panics if passed an invalid date.
    #[cfg(feature = "dates")]
    pub fn with_date(self, year: i32, month: u32, day: u32) -> Self {
        Self {
            date: Some(chrono::NaiveDate::from_ymd_opt(year, month, day).unwrap()),
            ..self
        }
    }

    /// Add to the release's `added` field
    pub fn added(self, change: impl Into<Change>) -> Self {
        Self {
            added: crate::appended_list(self.added, change.into()),
            ..self
        }
    }

    /// Add to the release's `changed` field
    pub fn changed(self, change: impl Into<Change>) -> Self {
        Self {
            changed: crate::appended_list(self.changed, change.into()),
            ..self
        }
    }

    /// Add to the release's `deprecated` field
    pub fn deprecated(self, change: impl Into<Change>) -> Self {
        Self {
            deprecated: crate::appended_list(self.deprecated, change.into()),
            ..self
        }
    }

    /// Add to the release's `removed` field
    pub fn removed(self, change: impl Into<Change>) -> Self {
        Self {
            removed: crate::appended_list(self.removed, change.into()),
            ..self
        }
    }

    /// Add to the release's `fixed` field
    pub fn fixed(self, change: impl Into<Change>) -> Self {
        Self {
            fixed: crate::appended_list(self.fixed, change.into()),
            ..self
        }
    }

    /// Add to the release's `security` field
    pub fn security(self, change: impl Into<Change>) -> Self {
        Self {
            security: crate::appended_list(self.security, change.into()),
            ..self
        }
    }

    /// Mark a release as Yanked
    pub fn yank(self) -> Self {
        Self {
            yanked: true,
            ..self
        }
    }

    pub(crate) fn changes(&self) -> Vec<(&'static str, &[Change])> {
        [
            ("Added", self.added.as_slice()),
            ("Changed", self.changed.as_slice()),
            ("Deprecated", self.deprecated.as_slice()),
            ("Removed", self.removed.as_slice()),
            ("Fixed", self.fixed.as_slice()),
            ("Security", self.security.as_slice()),
        ]
        .into_iter()
        .filter(|(_, c)| !c.is_empty())
        .collect()
    }
}