overcast/
changelog.rs

1use crate::release::Release;
2use std::collections::BTreeMap;
3
4/// Contains the entire changelog structure
5#[derive(Debug, Clone, Default)]
6#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7pub struct Changelog {
8    pub(crate) title: String,
9    pub(crate) description: Option<String>,
10    releases: BTreeMap<Option<semver::Version>, Release>,
11    pub(crate) keep_a_changelog_message: bool,
12    pub(crate) semantic_versioning_message: bool,
13}
14
15impl Changelog {
16    /// Create a new changelog with specified title
17    pub fn new(title: impl ToString) -> Self {
18        Self {
19            title: title.to_string(),
20            description: None,
21            releases: Default::default(),
22            keep_a_changelog_message: false,
23            semantic_versioning_message: false,
24        }
25    }
26
27    /// Set the changelog's description field
28    pub fn with_description(self, description: impl ToString) -> Self {
29        Self {
30            description: Some(description.to_string()),
31            ..self
32        }
33    }
34
35    /// Add to the changelog's releases field
36    pub fn add_release(self, release: Release) -> Self {
37        let mut releases = self.releases;
38        releases.insert(release.version.clone(), release);
39
40        Self { releases, ..self }
41    }
42
43    /// Add the keep a changelog message to the top of human oriented renderings
44    pub fn add_keep_a_changelog_message(self) -> Self {
45        Self {
46            keep_a_changelog_message: true,
47            ..self
48        }
49    }
50
51    /// Add the keep a semantic versioning message to the top of human oriented renderings
52    pub fn add_semantic_versioning_message(self) -> Self {
53        Self {
54            semantic_versioning_message: true,
55            ..self
56        }
57    }
58
59    /// Return an iterator of all releases in the changelog sorted by their `version` field
60    pub fn releases(&self) -> impl Iterator<Item = &Release> {
61        self.releases.values()
62    }
63
64    /// Return a sorted iterator of all version numbers in the changelog. This doesn't include
65    /// unreleased as a version number
66    pub fn versions(&self) -> impl Iterator<Item = &semver::Version> {
67        self.releases.keys().filter_map(|v| v.as_ref())
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn ensure_releases_are_sorted_by_version_number() {
77        let changelog = Changelog::new("")
78            .add_release(Release::new(0, 2, 0))
79            .add_release(Release::new(0, 1, 0))
80            .add_release(Release::new(0, 1, 1))
81            .add_release(Release::unreleased())
82            .add_release(Release::new(0, 2, 1))
83            .add_release(Release::new(0, 1, 2));
84
85        assert_eq!(
86            changelog
87                .releases()
88                .map(|v| v.version.clone())
89                .collect::<Vec<_>>(),
90            vec![
91                None,
92                Some(semver::Version::new(0, 1, 0)),
93                Some(semver::Version::new(0, 1, 1)),
94                Some(semver::Version::new(0, 1, 2)),
95                Some(semver::Version::new(0, 2, 0)),
96                Some(semver::Version::new(0, 2, 1)),
97            ]
98        );
99
100        assert_eq!(
101            changelog.versions().cloned().collect::<Vec<_>>(),
102            vec![
103                semver::Version::new(0, 1, 0),
104                semver::Version::new(0, 1, 1),
105                semver::Version::new(0, 1, 2),
106                semver::Version::new(0, 2, 0),
107                semver::Version::new(0, 2, 1),
108            ]
109        );
110    }
111}