You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

289 lines
6.6 KiB

//! 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, Serialize, Deserialize)]
#[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<Self, ParsingError>
{
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, Serialize, Deserialize)]
#[repr(align(4))]
pub struct Version(u8,u8,u8,Tag);
static_assert!(std::mem::size_of::<Version>() == std::mem::size_of::<[u8; 4]>(),
"Size of version != size of [u8; 4]");
static_assert!(std::mem::size_of::<Version>() == std::mem::size_of::<u32>(),
"Size of version != size of u32");
static_assert!(std::mem::size_of::<Version>() == 4,
"Size of Version (u32) != 4");
static_assert!(std::mem::align_of::<Version>() == std::mem::align_of::<u32>(),
"Align of version != align of u32 with `repr(align(4))`");
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)
}
/// Get binary representation (4 bytes)
pub fn as_bytes(&self) -> &[u8]
{
unsafe {
std::slice::from_raw_parts(self as *const Self as *const u8, std::mem::size_of::<Self>())
}
}
/// Try to create an instance from bytes
pub fn try_from_bytes(bytes: [u8; 4]) -> Result<Self, ParsingError>
{
Ok(Self::new(bytes[0],
bytes[1],
bytes[2],
Tag::try_from(bytes[3])?))
}
/// Uncheckedly create a version from bytes
#[cfg(nightly)]
#[inline(always)] pub const unsafe fn from_bytes_unchecked(bytes: [u8; 4]) -> Self
{
std::mem::transmute(bytes)
}
/// Uncheckedly create a version from bytes
#[cfg(not(nightly))]
#[inline(always)] pub unsafe fn from_bytes_unchecked(bytes: [u8; 4]) -> Self
{
let mut s = Self::default();
debug_assert_eq!(bytes::copy_slice(s.as_bytes_mut(), &bytes[..]), std::mem::size_of::<Self>());
s
}
/// Convert into 4 byte array
#[inline(always)] pub const fn to_bytes(self) -> [u8; 4]
{
nightly! {
if {
unsafe {std::mem::transmute(self)} // we already statically asserted this to be okay
}
else {
[self.0, self.1, self.2, self.3 as u8]
}
}
}
/// Get mutable binary representation (4 bytes)
pub unsafe fn as_bytes_mut(&mut self) -> &mut [u8]
{
std::slice::from_raw_parts_mut(self as *mut Version as *mut u8, std::mem::size_of::<Self>())
}
/// Encode as u32
#[inline] pub fn as_u32(&self) -> u32
{
nightly! {
if {
self.to_u32()
} else {
debug_assert_eq!(size_of::<Self>(), size_of::<u32>());
unsafe{*(self as *const Self as *const u32)}
}
}
}
/// Convert this `Version` directly to `u32`.
#[inline(always)] pub const fn to_u32(self) -> u32
{
unsafe {
std::mem::transmute(self) //we have already statically asserted that this is safe
}
}
/// Decode u32 as self, assuming `Tag` is valid.
#[cfg(nightly)]
#[inline(always)] pub const unsafe fn from_u32_unchecked(from: u32) -> Self
{
std::mem::transmute(from)
}
#[cfg(not(nightly))]
#[inline(always)] pub unsafe fn from_u32_unchecked(from:u32) -> Self
{
*(&from as *const u32 as *const Self)
}
/// Try to parse a `u32` encoded `Version`.
pub fn try_from_u32(from: u32) -> Result<Self, ParsingError>
{
debug_assert_eq!(size_of::<Self>(), size_of::<u32>());
debug_assert_eq!(size_of::<Self>(), 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<u8> for Tag
{
type Error = ParsingError;
#[inline] fn try_from(from: u8) -> Result<Self, Self::Error>
{
Self::try_from_u8(from)
}
}
impl From<Tag> for u8
{
#[inline] fn from(from: Tag) -> Self
{
from as u8
}
}
impl TryFrom<u32> for Version
{
type Error = ParsingError;
#[inline] fn try_from(from: u32) -> Result<Self, Self::Error>
{
Self::try_from_u32(from)
}
}
impl From<Version> 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);
}
}