feat: with conventional parse
Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
@@ -15,6 +15,8 @@ serde.workspace = true
|
||||
reqwest = {workspace = true, features = ["blocking", "json"]}
|
||||
url.workspace = true
|
||||
semver.workspace = true
|
||||
conventional_commit_parser.workspace = true
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
tracing-test = {workspace = true, features = ["no-env-filter"]}
|
||||
|
191
crates/cuddle-please/src/versioning/conventional_parse.rs
Normal file
191
crates/cuddle-please/src/versioning/conventional_parse.rs
Normal file
@@ -0,0 +1,191 @@
|
||||
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();
|
||||
if commits.peek().is_none() {
|
||||
return None;
|
||||
}
|
||||
if let Some(prerelease) = Self::is_prerelease(cur_version) {
|
||||
return Some(prerelease);
|
||||
}
|
||||
|
||||
let commits: Vec<ConventionalCommit> = Self::parse_commits::<C>(commits);
|
||||
|
||||
return 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;
|
||||
|
||||
use crate::{environment::get_from_environment, gitea_client::Commit};
|
||||
|
||||
#[test]
|
||||
#[traced_test]
|
||||
fn is_prerelease() {
|
||||
let mut 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 mut 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 mut 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 mut 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 mut 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 mut 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 mut 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 mut version = Version::parse("0.1.0").unwrap();
|
||||
|
||||
let commits: Vec<&str> = Vec::new();
|
||||
|
||||
let actual = VersionIncrement::from(&version, commits).is_none();
|
||||
assert_eq!(true, actual);
|
||||
}
|
||||
}
|
@@ -1 +1,3 @@
|
||||
pub mod conventional_parse;
|
||||
pub mod next_version;
|
||||
pub mod semver;
|
||||
|
170
crates/cuddle-please/src/versioning/next_version.rs
Normal file
170
crates/cuddle-please/src/versioning/next_version.rs
Normal file
@@ -0,0 +1,170 @@
|
||||
use semver::{Prerelease, Version};
|
||||
|
||||
use super::conventional_parse::VersionIncrement;
|
||||
|
||||
pub trait NextVersion {
|
||||
fn next<I>(&self, commits: I) -> Self
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: AsRef<str>;
|
||||
}
|
||||
|
||||
impl NextVersion for Version {
|
||||
fn next<I>(&self, commits: I) -> Self
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: AsRef<str>,
|
||||
{
|
||||
let increment = VersionIncrement::from(self, commits);
|
||||
|
||||
match increment {
|
||||
Some(increment) => match increment {
|
||||
VersionIncrement::Major => Self {
|
||||
major: self.major + 1,
|
||||
minor: 0,
|
||||
patch: 0,
|
||||
pre: Prerelease::EMPTY,
|
||||
..self.clone()
|
||||
},
|
||||
VersionIncrement::Minor => Self {
|
||||
minor: self.minor + 1,
|
||||
patch: 0,
|
||||
pre: Prerelease::EMPTY,
|
||||
..self.clone()
|
||||
},
|
||||
VersionIncrement::Patch => Self {
|
||||
patch: self.patch + 1,
|
||||
pre: Prerelease::EMPTY,
|
||||
..self.clone()
|
||||
},
|
||||
VersionIncrement::Prerelease => Self {
|
||||
pre: {
|
||||
let release = &self.pre;
|
||||
let release_version = match release.rsplit_once(".") {
|
||||
Some((tag, version)) => match version.parse::<usize>() {
|
||||
Ok(version) => format!("{tag}.{}", version + 1),
|
||||
Err(_) => format!("{tag}.1"),
|
||||
},
|
||||
None => format!("{release}.1"),
|
||||
};
|
||||
Prerelease::new(&release_version).expect("prerelease is not valid semver")
|
||||
},
|
||||
..self.clone()
|
||||
},
|
||||
},
|
||||
None => self.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use semver::Version;
|
||||
use tracing_test::traced_test;
|
||||
|
||||
use crate::versioning::next_version::NextVersion;
|
||||
|
||||
#[test]
|
||||
#[traced_test]
|
||||
fn is_no_bump() {
|
||||
let version = Version::parse("0.0.0-prerelease").unwrap();
|
||||
let commits: Vec<&str> = vec![];
|
||||
|
||||
let actual = version.next(commits);
|
||||
|
||||
assert_eq!("0.0.0-prerelease", actual.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[traced_test]
|
||||
fn is_prerelease_initial() {
|
||||
let version = Version::parse("0.0.0-prerelease").unwrap();
|
||||
let commits: Vec<&str> = vec!["feat: something"];
|
||||
|
||||
let actual = version.next(commits);
|
||||
|
||||
assert_eq!("0.0.0-prerelease.1", actual.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[traced_test]
|
||||
fn is_prerelease_invalid() {
|
||||
let version = Version::parse("0.0.0-prerelease.invalid").unwrap();
|
||||
let commits: Vec<&str> = vec!["feat: something"];
|
||||
|
||||
let actual = version.next(commits);
|
||||
|
||||
assert_eq!("0.0.0-prerelease.1", actual.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[traced_test]
|
||||
fn is_prerelease_next() {
|
||||
let version = Version::parse("0.0.0-prerelease.1").unwrap();
|
||||
let commits: Vec<&str> = vec!["feat: something"];
|
||||
|
||||
let actual = version.next(commits);
|
||||
|
||||
assert_eq!("0.0.0-prerelease.2", actual.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[traced_test]
|
||||
fn is_patch() {
|
||||
let version = Version::parse("0.0.0").unwrap();
|
||||
let commits: Vec<&str> = vec!["fix: something"];
|
||||
|
||||
let actual = version.next(commits);
|
||||
|
||||
assert_eq!("0.0.1", actual.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[traced_test]
|
||||
fn is_minor() {
|
||||
let version = Version::parse("0.1.0").unwrap();
|
||||
let commits: Vec<&str> = vec!["feat: something"];
|
||||
|
||||
let actual = version.next(commits);
|
||||
|
||||
assert_eq!("0.2.0", actual.to_string())
|
||||
}
|
||||
#[test]
|
||||
#[traced_test]
|
||||
fn is_minor_clears_patch() {
|
||||
let version = Version::parse("0.1.1").unwrap();
|
||||
let commits: Vec<&str> = vec!["feat: something"];
|
||||
|
||||
let actual = version.next(commits);
|
||||
|
||||
assert_eq!("0.2.0", actual.to_string())
|
||||
}
|
||||
#[test]
|
||||
#[traced_test]
|
||||
fn is_major() {
|
||||
let version = Version::parse("0.0.0").unwrap();
|
||||
let commits: Vec<&str> = vec![
|
||||
"feat: something
|
||||
|
||||
BREAKING CHANGE: something",
|
||||
];
|
||||
|
||||
let actual = version.next(commits);
|
||||
|
||||
assert_eq!("1.0.0", actual.to_string())
|
||||
}
|
||||
#[test]
|
||||
#[traced_test]
|
||||
fn is_major_clears_minor_patch() {
|
||||
let version = Version::parse("1.2.3").unwrap();
|
||||
let commits: Vec<&str> = vec![
|
||||
"feat: something
|
||||
|
||||
BREAKING CHANGE: something",
|
||||
];
|
||||
|
||||
let actual = version.next(commits);
|
||||
|
||||
assert_eq!("2.0.0", actual.to_string())
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user