use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, schemars::JsonSchema)]
pub struct Entry {
pub sha: String,
pub description: String,
}
#[derive(Serialize, Deserialize, Clone, schemars::JsonSchema)]
pub struct Category {
pub name: String,
pub icon: String,
pub summary: String,
#[serde(default)]
pub entries: Vec<Entry>,
}
pub fn render(categories: &[Category], commit_url_prefix: &str) -> String {
let mut out = String::new();
for cat in categories {
if cat.entries.is_empty() {
continue;
}
out.push_str(&format!("## {} {}\n", cat.icon, cat.name));
out.push_str(&format!("#### {}\n", cat.summary));
for e in &cat.entries {
let sha7 = &e.sha[..e.sha.len().min(7)];
out.push_str(&format!(
"- {} ([{}]({}{}))\n",
e.description, sha7, commit_url_prefix, e.sha
));
}
out.push('\n');
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn cat(name: &str, icon: &str, summary: &str, entries: &[(&str, &str)]) -> Category {
Category {
name: name.into(),
icon: icon.into(),
summary: summary.into(),
entries: entries
.iter()
.map(|(sha, desc)| Entry {
sha: (*sha).into(),
description: (*desc).into(),
})
.collect(),
}
}
#[test]
fn renders_two_categories_with_blank_line_between() {
let cats = vec![
cat(
"Features",
":sparkles:",
"New features",
&[("abc1234def", "**cli**: add flag")],
),
cat(
"Bug Fixes",
":bug:",
"Fixes",
&[("def5678abc", "fix crash")],
),
];
let md = render(&cats, "https://github.com/o/r/commit/");
assert_eq!(
md,
"## :sparkles: Features\n\
#### New features\n\
- **cli**: add flag ([abc1234](https://github.com/o/r/commit/abc1234def))\n\
\n\
## :bug: Bug Fixes\n\
#### Fixes\n\
- fix crash ([def5678](https://github.com/o/r/commit/def5678abc))\n\
\n"
);
}
#[test]
fn single_category_ends_with_trailing_blank_line() {
let md = render(
&[cat("Features", ":sparkles:", "New", &[("0123456789", "x")])],
"https://github.com/o/r/commit/",
);
assert_eq!(
md,
"## :sparkles: Features\n#### New\n- x ([0123456](https://github.com/o/r/commit/0123456789))\n\n"
);
}
#[test]
fn empty_categories_are_skipped() {
let cats = vec![
cat("Empty", ":ghost:", "none", &[]),
cat("Features", ":sparkles:", "New", &[("abcdef1234", "y")]),
];
let md = render(&cats, "https://github.com/o/r/commit/");
assert!(!md.contains("Empty"));
assert!(md.starts_with("## :sparkles: Features\n"));
}
#[test]
fn all_empty_categories_render_to_empty_string() {
let md = render(&[cat("Empty", ":ghost:", "none", &[])], "https://x/commit/");
assert_eq!(md, "");
}
#[test]
fn category_serializes_to_the_generate_changelog_shape() {
let value = serde_json::to_value(cat(
"Features",
":sparkles:",
"New features",
&[("abc1234def", "**cli**: add flag")],
))
.unwrap();
assert_eq!(value["name"], "Features");
assert_eq!(value["icon"], ":sparkles:");
assert_eq!(value["summary"], "New features");
assert_eq!(value["entries"][0]["sha"], "abc1234def");
assert_eq!(value["entries"][0]["description"], "**cli**: add flag");
}
}