refactor: move commands and misc out of main binary package

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
2023-08-01 15:34:24 +02:00
parent 8b83b9c14d
commit c7793f7422
24 changed files with 580 additions and 460 deletions

View File

@@ -0,0 +1,187 @@
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);
}
}

View File

@@ -0,0 +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())
}
}

View File

@@ -0,0 +1,159 @@
use std::cmp::Reverse;
use crate::gitea_client::Tag;
use semver::Version;
pub fn get_most_significant_version<'a>(tags: Vec<&'a Tag>) -> Option<&'a Tag> {
let mut versions: Vec<(&'a Tag, Version)> = tags
.into_iter()
.filter_map(|c| {
if let Ok(version) = c.name.trim_start_matches('v').parse::<Version>() {
Some((c, version))
} else {
None
}
})
.collect();
versions.sort_unstable_by_key(|(_, version)| Reverse(version.clone()));
let tag = versions.first().map(|(tag, _)| *tag);
if let Some(tag) = tag {
tracing::trace!(name = &tag.name, "found most significant tag with version");
}
tag
}
#[cfg(test)]
mod test {
use tracing_test::traced_test;
use crate::{
gitea_client::{Tag, TagCommit},
versioning::semver::get_most_significant_version,
};
fn create_tag(version: impl Into<String>) -> Tag {
let version = version.into();
Tag {
id: "some-id".into(),
message: version.clone(),
name: version,
commit: TagCommit {
created: "date".into(),
sha: "sha".into(),
url: "url".into(),
},
}
}
#[test]
#[traced_test]
fn gets_most_significant_version() {
let most_significant = create_tag("3.1.1");
let tags = vec![
create_tag("1.0.1"),
create_tag("1.2.1"),
most_significant.clone(),
create_tag("0.0.1"),
create_tag("0.0.2"),
];
let actual = get_most_significant_version(tags.iter().collect()).unwrap();
assert_eq!(&most_significant, actual)
}
#[test]
#[traced_test]
fn gets_most_significant_version_patch() {
let most_significant = create_tag("0.0.8");
let tags = vec![
create_tag("0.0.1"),
create_tag("0.0.7"),
create_tag("0.0.2"),
most_significant.clone(),
create_tag("0.0.0"),
];
let actual = get_most_significant_version(tags.iter().collect()).unwrap();
assert_eq!(&most_significant, actual)
}
#[test]
#[traced_test]
fn gets_most_significant_version_minor() {
let most_significant = create_tag("0.8.0");
let tags = vec![
create_tag("0.1.1"),
create_tag("0.2.7"),
create_tag("0.7.2"),
most_significant.clone(),
create_tag("0.3.0"),
];
let actual = get_most_significant_version(tags.iter().collect()).unwrap();
assert_eq!(&most_significant, actual)
}
#[test]
#[traced_test]
fn gets_most_significant_version_major() {
let most_significant = create_tag("7.8.0");
let tags = vec![
create_tag("6.1.1"),
create_tag("1.2.7"),
create_tag("2.7.2"),
most_significant.clone(),
create_tag("3.3.0"),
];
let actual = get_most_significant_version(tags.iter().collect()).unwrap();
assert_eq!(&most_significant, actual)
}
#[test]
#[traced_test]
fn ignored_invalid_tags() {
let tags = vec![
create_tag("something-3.3.0"),
create_tag("bla bla bla"),
create_tag("main"),
create_tag("master"),
create_tag("develop"),
];
let actual = get_most_significant_version(tags.iter().collect()).is_none();
assert!(actual)
}
#[test]
#[traced_test]
fn mix_v_prefix() {
let most_significant = create_tag("v7.8.0");
let tags = vec![
create_tag("6.1.1"),
create_tag("v1.2.7"),
create_tag("2.7.2"),
most_significant.clone(),
create_tag("v3.3.0"),
];
let actual = get_most_significant_version(tags.iter().collect()).unwrap();
assert_eq!(&most_significant, actual)
}
#[test]
#[traced_test]
fn mix_v_prefix_2() {
let most_significant = create_tag("7.8.0");
let tags = vec![
create_tag("6.1.1"),
create_tag("v1.2.7"),
create_tag("2.7.2"),
most_significant.clone(),
create_tag("v3.3.0"),
];
let actual = get_most_significant_version(tags.iter().collect()).unwrap();
assert_eq!(&most_significant, actual)
}
}