use std::{
cmp::Ordering,
fmt::{Display, Formatter, Result as FmtResult},
str::FromStr,
};
use sysexits::ExitCode;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Range {
Major,
Minor,
Patch,
}
impl Display for Range {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(
f,
"{}",
match self {
Self::Major => "major",
Self::Minor => "minor",
Self::Patch => "patch",
}
)
}
}
impl FromStr for Range {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"major" => Ok(Self::Major),
"minor" => Ok(Self::Minor),
"patch" => Ok(Self::Patch),
_ => {
Err("please specify either 'major', 'minor', or 'patch'".to_string())
}
}
}
}
#[derive(
Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize,
)]
pub struct Version {
major: usize,
minor: usize,
patch: usize,
}
impl Version {
crate::getters!(@fn @cp major: usize, minor: usize, patch: usize);
pub fn increment(&mut self, range: Range) -> &mut Self {
match range {
Range::Major => {
self.major += 1;
self.minor = 0;
self.patch = 0;
}
Range::Minor => {
self.minor += 1;
self.patch = 0;
}
Range::Patch => self.patch += 1,
}
self
}
#[must_use]
pub const fn new(major: usize, minor: usize, patch: usize) -> Self {
Self {
major,
minor,
patch,
}
}
pub fn set_major(&mut self, major: usize) {
self.major = major;
}
pub fn set_minor(&mut self, minor: usize) {
self.minor = minor;
}
pub fn set_patch(&mut self, patch: usize) {
self.patch = patch;
}
}
impl Ord for Version {
fn cmp(&self, other: &Self) -> Ordering {
match self.major.cmp(&other.major) {
Ordering::Equal => match self.minor.cmp(&other.minor) {
Ordering::Equal => self.patch.cmp(&other.patch),
cmp => cmp,
},
cmp => cmp,
}
}
}
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Display for Version {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "v{}.{}.{}", self.major, self.minor, self.patch)
}
}
impl FromStr for Version {
type Err = ExitCode;
fn from_str(string: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = if string.starts_with('v') {
string.strip_prefix('v').unwrap()
} else {
string
}
.split('.')
.collect();
let (major_version, minor_version, patch_level) = match parts.len() {
1 => (parts[0].parse::<usize>(), Ok(0), Ok(0)),
2 => (parts[0].parse::<usize>(), parts[1].parse::<usize>(), Ok(0)),
_ => (
parts[0].parse::<usize>(),
parts[1].parse::<usize>(),
parts[2].parse::<usize>(),
),
};
if let (Ok(major), Ok(minor), Ok(patch)) =
(major_version, minor_version, patch_level)
{
Ok(Self {
major,
minor,
patch,
})
} else {
eprintln!("This version is invalid.");
Err(ExitCode::DataErr)
}
}
}