//! Version information for the file container format, semver-like internal version format. use super::*; use std::{ fmt, mem::size_of, error, }; /// Represents other states of this container format version #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Copy)] #[repr(u8)] pub enum Tag { None =0, Prerelease =1, Unstable =2, } /// The error used by `Tag` and `Version` when they parse invalid input. #[derive(Debug)] pub struct ParsingError; impl error::Error for ParsingError{} impl fmt::Display for ParsingError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "An attempt was made to parse invalid Version input") } } impl Tag { /// Try to parse a `u8` as `Tag`. fn try_from_u8(from: u8) -> Result { macro_rules! branches { ($($num:path),*) => { match from { $( x if x == $num as u8 => $num, )* _ => return Err(ParsingError), } } } Ok(branches! { Self::Prerelease, Self::Unstable, Self::None }) } } impl fmt::Display for Tag { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Prerelease => write!(f, "*"), Self::Unstable => write!(f, "~"), _ => Ok(()), } } } impl Default for Tag { #[inline] fn default() -> Self { Self::None } } /// Represents a container version number in this format: `Major.Minor.Bugfix.Tag` /// /// Should adhear to this principle /// * Major - If different, fail parsing. /// * Minor - If higher that current, fail parsing. /// * Bugfix - If higher than current, warn user. /// * Tag - Unused, but may represent things like prerelease or unstable #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Copy, Default)] #[repr(packed, C)] pub struct Version(u8,u8,u8,Tag); impl fmt::Display for Version { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}.{}.{}{}", self.0, self.1, self.2, self.3) } } impl Version { /// Is this read version compatable with a system of version `other`? pub fn is_compat(&self, other: &Self) -> bool { self.0 == other.0 && self.1 <= other.1 } /// Should we warn about parsing with this version when system version is `other`? pub fn should_warn(&self, other: &Self) -> bool { self.2 > other.2 || self.3 == Tag::Unstable } /// Create a new instance of `Version`. pub const fn new(major: u8, minor: u8, bugfix: u8, tag: Tag) -> Self { Self(major,minor,bugfix,tag) } /// Encode as u32 pub fn as_u32(&self) -> u32 { debug_assert_eq!(size_of::(), size_of::()); unsafe{*(self as *const Self as *const u32)} } /// Decode u32 as self, assuming `Tag` is valid. pub unsafe fn from_u32_unchecked(from: u32) -> Self { debug_assert_eq!(size_of::(), size_of::()); *(&from as *const u32 as *const Self) } /// Try to parse a `u32` encoded `Version`. pub fn try_from_u32(from: u32) -> Result { debug_assert_eq!(size_of::(), size_of::()); debug_assert_eq!(size_of::(), 4); let parts = &from as *const u32 as *const u8; macro_rules! index { ($array:ident, $index:expr) => { unsafe{*($array.offset($index))} } } Ok(Self::new(index!(parts, 0), index!(parts, 1), index!(parts, 2), Tag::try_from_u8(index!(parts, 3))?)) } } impl TryFrom for Tag { type Error = ParsingError; #[inline] fn try_from(from: u8) -> Result { Self::try_from_u8(from) } } impl From for u8 { #[inline] fn from(from: Tag) -> Self { from as u8 } } impl TryFrom for Version { type Error = ParsingError; #[inline] fn try_from(from: u32) -> Result { Self::try_from_u32(from) } } impl From for u32 { #[inline] fn from(from: Version) -> Self { from.as_u32() } } #[cfg(test)] mod tests { use super::*; #[test] fn vers_encode() { let version1 = Version::new(1,0,0,Tag::Unstable); let version2 = Version::try_from_u32(version1.as_u32()).unwrap(); let version3 = unsafe{Version::from_u32_unchecked(version1.as_u32())}; assert_eq!(format!("{}", version1), "1.0.0~"); assert_eq!(version1, version2); assert_eq!(version2, version3); } }