use core_extensions::{SelfOps, StringExt};
use std::{
error,
fmt::{self, Display},
num::ParseIntError,
};
use crate::std_types::RStr;
#[derive(Debug, Copy, Clone, PartialEq, Eq, StableAbi)]
#[repr(transparent)]
pub struct VersionStrings {
pub version: RStr<'static>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, StableAbi)]
#[repr(C)]
pub struct VersionNumber {
pub major: u32,
pub minor: u32,
pub patch: u32,
}
impl VersionStrings {
pub const fn new(version: &'static str) -> Self {
Self {
version: RStr::from_str(version),
}
}
pub fn parsed(self) -> Result<VersionNumber, ParseVersionError> {
VersionNumber::new(self)
}
}
impl VersionNumber {
pub fn new(vn: VersionStrings) -> Result<Self, ParseVersionError> {
let mut iter = vn.version.splitn(3, '.');
VersionNumber {
major: iter
.next()
.unwrap_or("")
.parse()
.map_err(|x| ParseVersionError::new(vn, "major", x))?,
minor: iter
.next()
.unwrap_or("")
.parse()
.map_err(|x| ParseVersionError::new(vn, "minor", x))?,
patch: iter
.next()
.unwrap_or("")
.split_while(|x| ('0'..='9').contains(&x))
.find(|x| x.key)
.map_or("0", |x| x.str)
.parse()
.map_err(|x| ParseVersionError::new(vn, "patch", x))?,
}
.piped(Ok)
}
pub const fn is_compatible(self, library_implementor: VersionNumber) -> bool {
if self.major == 0 && library_implementor.major == 0 {
self.minor == library_implementor.minor && self.patch <= library_implementor.patch
} else {
self.major == library_implementor.major && self.minor <= library_implementor.minor
}
}
pub const fn is_loosely_compatible(self, library_implementor: VersionNumber) -> bool {
if self.major == 0 && library_implementor.major == 0 {
self.minor == library_implementor.minor
} else {
self.major == library_implementor.major
}
}
}
impl fmt::Display for VersionNumber {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
impl fmt::Display for VersionStrings {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.version, f)
}
}
#[macro_export]
macro_rules! package_version_strings {
() => {{
$crate::sabi_types::VersionStrings::new(env!("CARGO_PKG_VERSION"))
}};
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseVersionError {
version_strings: VersionStrings,
which_field: &'static str,
parse_error: ParseIntError,
}
impl ParseVersionError {
const fn new(
version_strings: VersionStrings,
which_field: &'static str,
parse_error: ParseIntError,
) -> Self {
Self {
version_strings,
which_field,
parse_error,
}
}
pub const fn version_strings(&self) -> VersionStrings {
self.version_strings
}
}
impl Display for ParseVersionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"\nInvalid version string:'{}'\nerror at the {} field:{}",
self.version_strings, self.which_field, self.parse_error,
)
}
}
impl error::Error for ParseVersionError {}