feat: with conventional parse

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
Kasper Juul Hermansen 2023-07-30 17:24:33 +02:00
parent 62cafa4a9e
commit 51e3ea3e2f
Signed by: kjuulh
GPG Key ID: 9AA7BC13CE474394
6 changed files with 518 additions and 0 deletions

152
Cargo.lock generated
View File

@ -120,6 +120,15 @@ version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.13.0"
@ -191,6 +200,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "conventional_commit_parser"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58660f9e1d5eeeeec9c33d1473ea8bba000c673a2189edaeedb4523ec7d6f7cb"
dependencies = [
"pest",
"pest_derive",
]
[[package]]
name = "core-foundation"
version = "0.9.3"
@ -207,12 +226,32 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "cpufeatures"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "cuddle-please"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"conventional_commit_parser",
"dotenv",
"reqwest",
"semver",
@ -225,6 +264,16 @@ dependencies = [
"url",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "dotenv"
version = "0.15.0"
@ -351,6 +400,16 @@ dependencies = [
"slab",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "gimli"
version = "0.27.3"
@ -740,6 +799,50 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]]
name = "pest"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d2d1d55045829d65aad9d389139882ad623b33b904e7c9f1b10c5b8927298e5"
dependencies = [
"thiserror",
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f94bca7e7a599d89dea5dfa309e217e7906c3c007fb9c3299c40b10d6a315d3"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d490fe7e8556575ff6911e45567ab95e71617f43781e5c05490dc8d75c965c"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn 2.0.27",
]
[[package]]
name = "pest_meta"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2674c66ebb4b4d9036012091b537aae5878970d6999f81a265034d85b136b341"
dependencies = [
"once_cell",
"pest",
"sha2",
]
[[package]]
name = "pin-project-lite"
version = "0.2.10"
@ -991,6 +1094,17 @@ dependencies = [
"unsafe-libyaml",
]
[[package]]
name = "sha2"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "sharded-slab"
version = "0.1.4"
@ -1075,6 +1189,26 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "thiserror"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
]
[[package]]
name = "thread_local"
version = "1.1.7"
@ -1253,6 +1387,18 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
[[package]]
name = "typenum"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "ucd-trie"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
[[package]]
name = "unicode-bidi"
version = "0.3.13"
@ -1309,6 +1455,12 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "want"
version = "0.3.1"

View File

@ -16,6 +16,7 @@ url = {version = "*"}
serde_yaml = {version = "*"}
serde = {version = "*", features = ["derive"]}
semver = "1.0.18"
conventional_commit_parser = "0.9.4"
reqwest = {version = "*"}

View File

@ -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"]}

View 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);
}
}

View File

@ -1 +1,3 @@
pub mod conventional_parse;
pub mod next_version;
pub mod semver;

View 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())
}
}