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 fmt.write_str("## ")?;
186
187 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 let mut changes = self.changes.clone();
204
205 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 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}