cuddle-please-misc 0.1.0

A release-please inspired release manager tool, built on top of cuddle, but also useful standalone, cuddle-please supports, your ci of choice, as well as gitea, github
Documentation
use conventional_commit_parser::commit::{CommitType, ConventionalCommit};
use semver::Version;

#[derive(Debug, Clone, PartialEq)]
pub enum VersionIncrement {
    Major,
    Minor,
    Patch,
    Prerelease,
}

impl VersionIncrement {
    pub fn from<C>(cur_version: &Version, commits: C) -> Option<Self>
    where
        C: IntoIterator,
        C::Item: AsRef<str>,
    {
        let mut commits = commits.into_iter().peekable();
        commits.peek()?;
        if let Some(prerelease) = Self::is_prerelease(cur_version) {
            return Some(prerelease);
        }

        let commits: Vec<ConventionalCommit> = Self::parse_commits::<C>(commits);

        Some(Self::from_conventional_commits(commits))
    }

    #[inline]
    fn parse_commits<C>(
        commits: std::iter::Peekable<<C as IntoIterator>::IntoIter>,
    ) -> Vec<ConventionalCommit>
    where
        C: IntoIterator,
        C::Item: AsRef<str>,
    {
        commits
            .filter_map(|c| conventional_commit_parser::parse(c.as_ref()).ok())
            .collect()
    }

    // Find most significant change
    fn from_conventional_commits(commits: Vec<ConventionalCommit>) -> VersionIncrement {
        let found_breaking = || commits.iter().any(|c| c.is_breaking_change);
        let found_feature = || {
            commits
                .iter()
                .any(|c| matches!(c.commit_type, CommitType::Feature))
        };

        match (found_breaking(), found_feature()) {
            (true, _) => Self::Major,
            (_, true) => Self::Minor,
            (_, false) => Self::Patch,
        }
    }

    fn is_prerelease(cur_version: &Version) -> Option<VersionIncrement> {
        if !cur_version.pre.is_empty() {
            return Some(Self::Prerelease);
        }
        None
    }
}

#[cfg(test)]
mod tests {
    use crate::versioning::conventional_parse::VersionIncrement;
    use semver::Version;
    use tracing_test::traced_test;

    #[test]
    #[traced_test]
    fn is_prerelease() {
        let version = Version::parse("0.0.0-alpha.1").unwrap();

        let commits = vec![
            "feat: something",
            "fix: something",
            "feat(something): something",
            "feat(breaking): some
            
            BREAKING CHANGE: something",
        ];

        let actual = VersionIncrement::from(&version, commits).unwrap();
        assert_eq!(VersionIncrement::Prerelease, actual);
    }
    #[test]
    #[traced_test]
    fn is_patch() {
        let version = Version::parse("0.0.1").unwrap();

        let commits = vec![
            "fix: something",
            "fix: something",
            "fix: something",
            "fix: something",
            "fix: something",
            "fix: something",
        ];

        let actual = VersionIncrement::from(&version, commits).unwrap();
        assert_eq!(VersionIncrement::Patch, actual);
    }

    #[test]
    #[traced_test]
    fn is_minor() {
        let version = Version::parse("0.1.0").unwrap();

        let commits = vec![
            "feat: something",
            "feat: something",
            "fix: something",
            "fix: something",
            "fix: something",
            "fix: something",
        ];

        let actual = VersionIncrement::from(&version, commits).unwrap();
        assert_eq!(VersionIncrement::Minor, actual);
    }
    #[test]
    #[traced_test]
    fn is_major() {
        let version = Version::parse("0.1.0").unwrap();

        let commits = vec![
            "feat: something",
            "feat: something
            
            BREAKING CHANGE: something",
            "fix: something",
            "fix: something",
            "fix: something",
            "fix: something",
        ];

        let actual = VersionIncrement::from(&version, commits).unwrap();
        assert_eq!(VersionIncrement::Major, actual);
    }

    #[test]
    #[traced_test]
    fn chore_is_patch() {
        let version = Version::parse("0.1.0").unwrap();

        let commits = vec!["chore: something"];

        let actual = VersionIncrement::from(&version, commits).unwrap();
        assert_eq!(VersionIncrement::Patch, actual);
    }

    #[test]
    #[traced_test]
    fn refactor_is_patch() {
        let version = Version::parse("0.1.0").unwrap();

        let commits = vec!["refactor: something"];

        let actual = VersionIncrement::from(&version, commits).unwrap();
        assert_eq!(VersionIncrement::Patch, actual);
    }

    #[test]
    #[traced_test]
    fn unknown_commits_are_patch() {
        let version = Version::parse("0.1.0").unwrap();

        let commits = vec!["blablabla some commit"];

        let actual = VersionIncrement::from(&version, commits).unwrap();
        assert_eq!(VersionIncrement::Patch, actual);
    }

    #[test]
    #[traced_test]
    fn nothing_returns_none() {
        let version = Version::parse("0.1.0").unwrap();

        let commits: Vec<&str> = Vec::new();

        let actual = VersionIncrement::from(&version, commits).is_none();
        assert!(actual);
    }
}