logicaffeine_web/ui/pages/
changelog.rs1#[derive(Debug, Default, Clone, PartialEq, Eq)]
15pub struct Unreleased {
16 pub added: Vec<String>,
18 pub changed: usize,
20 pub fixed: usize,
22}
23
24pub fn section<'a>(changelog: &'a str, version: &str) -> Option<&'a str> {
27 let heading = format!("## [{version}]");
28 let dated = format!("{heading} ");
29 let mut offset = 0;
30 let mut start = None;
31 for line in changelog.split_inclusive('\n') {
32 let text = line.trim_end_matches(['\n', '\r']);
33 if text.starts_with("## ") {
34 if let Some(start) = start {
35 return Some(&changelog[start..offset]);
36 }
37 if text == heading || text.starts_with(&dated) {
38 start = Some(offset + line.len());
39 }
40 }
41 offset += line.len();
42 }
43 start.map(|start| &changelog[start..])
44}
45
46pub fn release_title(section: &str) -> String {
49 section
50 .lines()
51 .find(|line| !line.trim().is_empty() && !line.starts_with("### "))
52 .map(|line| headline(line.strip_prefix("- ").unwrap_or(line)))
53 .unwrap_or_default()
54}
55
56pub fn headline(line: &str) -> String {
61 let line = line.strip_prefix("- ").unwrap_or(line);
62 let raw = match line.find("**") {
63 Some(open) => {
64 let lead = &line[open + 2..];
65 &lead[..lead.find('*').unwrap_or(lead.len())]
66 }
67 None => {
68 let line = line.strip_prefix('(').unwrap_or(line);
69 line.strip_suffix(')').unwrap_or(line)
70 }
71 };
72 let unticked = raw.replace('`', "");
73 let joined = unticked.split_whitespace().collect::<Vec<_>>().join(" ");
74 let text = joined.strip_suffix('.').unwrap_or(&joined);
75 if text.chars().count() <= 72 {
76 return text.to_string();
77 }
78 let cut: String = text.chars().take(72).collect();
79 let whole = match cut.rfind(' ') {
80 Some(space) => &cut[..space],
81 None => &cut[..],
82 };
83 format!("{whole}…")
84}
85
86pub fn unreleased(changelog: &str) -> Unreleased {
89 let Some(body) = section(changelog, "Unreleased") else { return Unreleased::default() };
90 let mut next = Unreleased::default();
91 let mut kind = "";
92 for line in body.lines() {
93 if let Some(heading) = line.strip_prefix("### ") {
94 kind = heading.trim();
95 } else if line.starts_with("- ") {
96 match kind {
97 "Added" => next.added.push(headline(line)),
98 "Changed" | "Deprecated" | "Removed" => next.changed += 1,
99 "Fixed" | "Security" => next.fixed += 1,
100 _ => {}
101 }
102 }
103 }
104 next
105}
106
107#[cfg(test)]
108#[path = "../../../../../crates/logicaffeine_tests/unit/logicaffeine_web/ui/pages/changelog/tests.rs"]
109mod tests;