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
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);
|
|
}
|
|
}
|
|
|
|
|