clparse/
changelog.rs

1use anyhow::Result;
2use chrono::NaiveDate;
3use derive_builder::Builder;
4use derive_getters::Getters;
5use err_derive::Error;
6use indexmap::indexmap;
7use versions::Version;
8use serde::ser::Serializer;
9use serde_derive::{Deserialize, Serialize};
10use std::fmt;
11use textwrap::wrap;
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14#[serde(rename_all = "lowercase")]
15pub enum Change {
16    Added(String),
17    Changed(String),
18    Deprecated(String),
19    Removed(String),
20    Fixed(String),
21    Security(String),
22}
23
24#[derive(Debug, Error)]
25pub enum ChangeError {
26    #[error(display = "invalid change type specified: {}", _0)]
27    InvalidChangeType(String),
28}
29
30fn version_serialize<S>(x: &Option<Version>, s: S) -> Result<S::Ok, S::Error>
31where
32    S: Serializer,
33{
34    match x {
35        Some(ref version) => s.serialize_str(&version.to_string()),
36        None => s.serialize_none(),
37    }
38}
39
40#[derive(Debug, Clone, Builder, Getters, Serialize, Deserialize, PartialEq)]
41pub struct Release {
42    #[builder(setter(strip_option), default)]
43    #[serde(serialize_with = "version_serialize")]
44    version: Option<Version>,
45    #[builder(setter(strip_option, into), default)]
46    link: Option<String>,
47    #[builder(setter(strip_option), default)]
48    date: Option<NaiveDate>,
49    #[builder(default)]
50    changes: Vec<Change>,
51    #[builder(default = "false")]
52    yanked: bool,
53    #[serde(skip)]
54    #[builder(default = "self.default_separator()")]
55    separator: String,
56    #[serde(skip)]
57    #[builder(default = "80.into()")]
58    wrap: Option<usize>,
59}
60
61impl ReleaseBuilder {
62    fn default_separator(&self) -> String {
63        "-".into()
64    }
65}
66
67impl Release {
68    pub fn version_mut(&mut self) -> &mut Option<Version> {
69        &mut self.version
70    }
71
72    pub fn set_version(&mut self, version: Version) -> &mut Self {
73        self.version = Some(version);
74        self
75    }
76
77    pub fn link_mut(&mut self) -> &mut Option<String> {
78        &mut self.link
79    }
80
81    pub fn set_link(&mut self, link: String) -> &mut Self {
82        self.link = Some(link);
83        self
84    }
85
86    pub fn date_mut(&mut self) -> &mut Option<NaiveDate> {
87        &mut self.date
88    }
89
90    pub fn set_date(&mut self, date: NaiveDate) -> &mut Self {
91        self.date = Some(date);
92        self
93    }
94
95    pub fn changes_mut(&mut self) -> &mut Vec<Change> {
96        &mut self.changes
97    }
98
99    pub fn set_changes(&mut self, changes: Vec<Change>) -> &mut Self {
100        self.changes = changes;
101        self
102    }
103
104    pub fn yank(&mut self, yanked: bool) {
105        if !self.yanked && yanked {
106            self.link = None;
107        }
108
109        self.yanked = yanked;
110    }
111}
112
113#[derive(Debug, Clone, Builder, Getters, Serialize, Deserialize)]
114pub struct Changelog {
115    #[builder(setter(into))]
116    title: String,
117    #[builder(setter(into))]
118    description: String,
119    #[builder(default)]
120    releases: Vec<Release>,
121}
122
123impl Changelog {
124    pub fn unreleased_changes(&self) -> Vec<Change> {
125        self.releases
126            .clone()
127            .into_iter()
128            .filter(|r| r.version.is_none())
129            .map(|r| r.changes.clone())
130            .flatten()
131            .collect()
132    }
133
134    pub fn unreleased_mut(&mut self) -> Option<&mut Release> {
135        self.releases.iter_mut().find(|r| r.version == None)
136    }
137
138    pub fn release_mut(&mut self, release: Version) -> Option<&mut Release> {
139        self.releases
140            .iter_mut()
141            .find(|r| r.version == Some(release.clone()))
142    }
143}
144
145impl Change {
146    pub fn new(change_type: &str, description: String) -> Result<Self> {
147        use self::Change::*;
148
149        match change_type.to_lowercase().as_str() {
150            "added" => Ok(Added(description)),
151            "changed" => Ok(Changed(description)),
152            "deprecated" => Ok(Deprecated(description)),
153            "removed" => Ok(Removed(description)),
154            "fixed" => Ok(Fixed(description)),
155            "security" => Ok(Security(description)),
156            _ => Err(ChangeError::InvalidChangeType(change_type.to_string()).into()),
157        }
158    }
159}
160
161impl fmt::Display for Change {
162    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
163        use self::Change::*;
164
165        let description = match self {
166            Added(description) => description,
167            Changed(description) => description,
168            Deprecated(description) => description,
169            Removed(description) => description,
170            Fixed(description) => description,
171            Security(description) => description,
172        };
173
174        fmt.write_str(&format!("- {}\n", description))?;
175
176        Ok(())
177    }
178}
179
180impl fmt::Display for Release {
181    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
182        use self::Change::*;
183
184        // Release Heading.
185        fmt.write_str("## ")?;
186
187        // Release Version.
188        if let (Some(version), Some(date)) = (self.version.as_ref(), self.date) {
189            if self.yanked {
190                fmt.write_str(&format!("{} {} {} [YANKED]\n", version, self.separator, date))?;
191            } else {
192                fmt.write_str(&format!("[{}] {} {}\n", version, self.separator, date))?;
193            }
194        } else {
195            fmt.write_str("[Unreleased]\n")?;
196
197            if self.changes.is_empty() {
198                fmt.write_str("\n")?;
199            }
200        }
201
202        // Release changes.
203        let mut changes = self.changes.clone();
204
205        // If wrapping is enabled, we regenerate the list of changes and wrap
206        // them.
207        if let Some(wrap_at) = self.wrap {
208            changes = changes.into_iter().map(|change| {
209                let (change_type, mut description) = match change {
210                    Added(description) => ("added", description),
211                    Changed(description) => ("changed", description),
212                    Deprecated(description) => ("deprecated", description),
213                    Removed(description) => ("removed", description),
214                    Fixed(description) => ("fixed", description),
215                    Security(description) => ("security", description),
216                };
217
218                description = description.replace("\n", " ");
219                // The first 3 characters are not included in this change description,
220                // so we need to wrap at 3 less characters than expected.
221                description = wrap(&description, wrap_at - 3).join("\n  ");
222
223                Change::new(change_type, description.to_string()).unwrap()
224            }).collect();
225        }
226
227        let mut changesets = indexmap! {
228            "Added" => Vec::new(),
229            "Changed" => Vec::new(),
230            "Deprecated" => Vec::new(),
231            "Removed" => Vec::new(),
232            "Fixed" => Vec::new(),
233            "Security" => Vec::new(),
234        };
235        changes.iter().for_each(|change| match change {
236            Added(_) => changesets.get_mut("Added").unwrap().push(change),
237            Changed(_) => changesets.get_mut("Changed").unwrap().push(change),
238            Deprecated(_) => changesets.get_mut("Deprecated").unwrap().push(change),
239            Removed(_) => changesets.get_mut("Removed").unwrap().push(change),
240            Fixed(_) => changesets.get_mut("Fixed").unwrap().push(change),
241            Security(_) => changesets.get_mut("Security").unwrap().push(change),
242        });
243
244        changesets = changesets
245            .into_iter()
246            .filter(|(_, changes)| changes.clone().iter().count() > 0)
247            .collect();
248
249        for (name, changes) in changesets {
250            fmt.write_str(&format!("### {}\n", name))?;
251
252            for change in changes {
253                fmt.write_str(&change.to_string())?;
254            }
255
256            fmt.write_str("\n")?;
257        }
258
259        Ok(())
260    }
261}
262
263impl fmt::Display for Changelog {
264    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
265        fmt.write_str(&format!("# {}\n", self.title))?;
266        fmt.write_str(&self.description)?;
267
268        let mut links: Vec<(Version, String)> = Vec::new();
269        for release in self.releases.clone() {
270            fmt.write_str(&release.to_string())?;
271
272            if let (Some(version), Some(link)) = (release.version, release.link) {
273                links.push((version, link));
274            }
275        }
276
277        links.sort_by(|(a, _), (b, _)| b.cmp(a));
278
279        if let Some(release) = self.releases.clone().first() {
280            if let (None, Some(link)) = (release.version.as_ref(), release.link.as_ref()) {
281                fmt.write_str(&format!("[Unreleased]: {}\n", link))?
282            }
283        }
284
285        for (version, link) in links {
286            fmt.write_str(&format!("[{}]: {}\n", version, link))?;
287        }
288
289        Ok(())
290    }
291}