jirust_cli/utils/
changelog_extractor.rs

1use regex::{Match, Regex};
2use std::error::Error;
3use std::fs;
4
5/// Extracts the changelog text for a specific version to be used in the version description as release note
6///
7/// # Fields
8///
9/// * `changelog_file` - The path to the changelog file
10pub struct ChangelogExtractor {
11    /// Path to the changelog file used to derive release notes.
12    pub changelog_file: String,
13}
14
15/// Implementation of the ChangelogExtractor
16impl ChangelogExtractor {
17    /// Creates a new ChangelogExtractor
18    ///
19    /// # Arguments
20    ///
21    /// * `changelog_file` - The path to the changelog file
22    ///
23    /// # Returns
24    ///
25    /// * A new ChangelogExtractor
26    ///
27    /// # Examples
28    ///
29    /// ```
30    /// use jirust_cli::utils::changelog_extractor::ChangelogExtractor;
31    ///
32    /// let changelog_extractor = ChangelogExtractor::new("CHANGELOG.md".to_string());
33    ///
34    /// assert_eq!(changelog_extractor.changelog_file, "CHANGELOG.md");
35    /// ```
36    pub fn new(changelog_file: String) -> Self {
37        Self { changelog_file }
38    }
39
40    /// Extracts the changelog text for a specific version to be used in the version description as release note
41    /// The version changelog text is extracted from the changelog file using the notes between the first and second version headers in markdown
42    /// "Keep a Changelog" changelog formatted file
43    ///
44    /// # Returns
45    ///
46    /// * The version changelog text
47    ///
48    /// # Errors
49    ///
50    /// * If the changelog file cannot be read or the version changelog text cannot be extracted
51    ///
52    /// # Examples
53    ///
54    /// ```no_run
55    /// use jirust_cli::utils::changelog_extractor::ChangelogExtractor;
56    ///
57    /// let changelog_extractor = ChangelogExtractor::new("CHANGELOG.md".to_string());
58    ///
59    /// let version_changelog_text = changelog_extractor.extract_version_changelog();
60    /// ```
61    pub fn extract_version_changelog(&self) -> Result<String, Box<dyn Error>> {
62        let version_re = Regex::new(r"## \[\d+.\d+.\d+\] \d+\-\d+\-\d+\n").unwrap();
63        let changelog = fs::read_to_string(&self.changelog_file)?;
64        let matches: Vec<Match> = version_re.find_iter(&changelog).collect();
65        if matches.is_empty() {
66            return Err("No version changelog available".into());
67        }
68        let changelog_version_text_start = matches[0].range().end;
69        let changelog_version_text_end = if matches.len() > 1 {
70            matches[1].range().start
71        } else {
72            changelog.len()
73        };
74        let version_changelog_text = changelog
75            [changelog_version_text_start..changelog_version_text_end]
76            .replace("\\n", "\n")
77            .replace("\\r", "\r");
78
79        Ok(version_changelog_text.to_string())
80    }
81
82    /// Extracts the issues from the version changelog text
83    /// The issues are extracted from the version changelog text using the project key and issue number
84    ///
85    /// # Arguments
86    ///
87    /// * `version_string` - The version changelog text
88    /// * `project_key` - The project key
89    ///
90    /// # Returns
91    ///
92    /// * `Result<Vec<String>, Box<dyn Error>>` - The issues extracted from the version changelog text
93    ///
94    /// # Examples
95    ///
96    /// ```no_run
97    /// use jirust_cli::utils::changelog_extractor::ChangelogExtractor;
98    ///
99    /// let changelog_extractor = ChangelogExtractor::new("CHANGELOG.md".to_string());
100    ///
101    /// let version_changelog_text = changelog_extractor.extract_version_changelog();
102    /// let project_key = "JIR".to_string();
103    /// let issues = changelog_extractor.extract_issues_from_changelog(&version_changelog_text.unwrap(), &project_key);
104    /// ```
105    pub fn extract_issues_from_changelog(
106        &self,
107        version_string: &String,
108        project_key: &String,
109    ) -> Result<Vec<String>, Box<dyn Error>> {
110        let issue_re = Regex::new(format!(r"({}\-\d+)", *project_key).as_str()).unwrap();
111        let mut issues: Vec<String> = vec![];
112        for (_, [issue]) in issue_re
113            .captures_iter((*version_string).as_str())
114            .map(|issue| issue.extract())
115        {
116            issues.push(issue.to_string());
117        }
118        Ok(issues)
119    }
120}