overcast 0.1.3

Strongly typed changelogs for projects as changeable as the weather
Documentation
use crate::release::Release;
use std::collections::BTreeMap;

/// Contains the entire changelog structure
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Changelog {
    pub(crate) title: String,
    pub(crate) description: Option<String>,
    releases: BTreeMap<Option<semver::Version>, Release>,
    pub(crate) keep_a_changelog_message: bool,
    pub(crate) semantic_versioning_message: bool,
}

impl Changelog {
    /// Create a new changelog with specified title
    pub fn new(title: impl ToString) -> Self {
        Self {
            title: title.to_string(),
            description: None,
            releases: Default::default(),
            keep_a_changelog_message: false,
            semantic_versioning_message: false,
        }
    }

    /// Set the changelog's description field
    pub fn with_description(self, description: impl ToString) -> Self {
        Self {
            description: Some(description.to_string()),
            ..self
        }
    }

    /// Add to the changelog's releases field
    pub fn add_release(self, release: Release) -> Self {
        let mut releases = self.releases;
        releases.insert(release.version.clone(), release);

        Self { releases, ..self }
    }

    /// Add the keep a changelog message to the top of human oriented renderings
    pub fn add_keep_a_changelog_message(self) -> Self {
        Self {
            keep_a_changelog_message: true,
            ..self
        }
    }

    /// Add the keep a semantic versioning message to the top of human oriented renderings
    pub fn add_semantic_versioning_message(self) -> Self {
        Self {
            semantic_versioning_message: true,
            ..self
        }
    }

    /// Return an iterator of all releases in the changelog sorted by their `version` field
    pub fn releases(&self) -> impl Iterator<Item = &Release> {
        self.releases.values()
    }

    /// Return a sorted iterator of all version numbers in the changelog. This doesn't include
    /// unreleased as a version number
    pub fn versions(&self) -> impl Iterator<Item = &semver::Version> {
        self.releases.keys().filter_map(|v| v.as_ref())
    }
}

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

    #[test]
    fn ensure_releases_are_sorted_by_version_number() {
        let changelog = Changelog::new("")
            .add_release(Release::new(0, 2, 0))
            .add_release(Release::new(0, 1, 0))
            .add_release(Release::new(0, 1, 1))
            .add_release(Release::unreleased())
            .add_release(Release::new(0, 2, 1))
            .add_release(Release::new(0, 1, 2));

        assert_eq!(
            changelog
                .releases()
                .map(|v| v.version.clone())
                .collect::<Vec<_>>(),
            vec![
                None,
                Some(semver::Version::new(0, 1, 0)),
                Some(semver::Version::new(0, 1, 1)),
                Some(semver::Version::new(0, 1, 2)),
                Some(semver::Version::new(0, 2, 0)),
                Some(semver::Version::new(0, 2, 1)),
            ]
        );

        assert_eq!(
            changelog.versions().cloned().collect::<Vec<_>>(),
            vec![
                semver::Version::new(0, 1, 0),
                semver::Version::new(0, 1, 1),
                semver::Version::new(0, 1, 2),
                semver::Version::new(0, 2, 0),
                semver::Version::new(0, 2, 1),
            ]
        );
    }
}