use crate::{ConstInit, Display, FmtResult, Formatter, impl_trait};
use crate::{Digits, Slice, Str, is, unwrap, whilst, write_at};
#[doc = crate::_tags!(code)]
#[doc = crate::_doc_meta!{location("code")}]
#[must_use]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Version {
pub major: u16,
pub minor: u16,
pub patch: u16,
}
impl ConstInit for Version {
const INIT: Self = Self::ZERO;
}
impl_trait! { fmt::Display for Version |self, f| {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}}
impl Version {
pub const MAX_LEN: usize = 17;
pub const ZERO: Self = Self::new(0, 0, 0);
pub const ONE: Self = Self::new(1, 0, 0);
pub const fn new(major: u16, minor: u16, patch: u16) -> Self {
Self { major, minor, patch }
}
pub const fn from_array(version: [u16; 3]) -> Self {
let [major, minor, patch] = version;
Self::new(major, minor, patch)
}
#[must_use]
pub const fn to_array(self) -> [u16; 3] {
[self.major, self.minor, self.patch]
}
#[must_use]
pub const fn is_zero(self) -> bool {
self.major == 0 && self.minor == 0 && self.patch == 0
}
pub const fn with_major(self, major: u16) -> Self {
Self { major, ..self }
}
pub const fn with_minor(self, minor: u16) -> Self {
Self { minor, ..self }
}
pub const fn with_patch(self, patch: u16) -> Self {
Self { patch, ..self }
}
pub const fn next_major(self) -> Self {
Self::new(self.major.saturating_add(1), 0, 0)
}
pub const fn next_minor(self) -> Self {
Self::new(self.major, self.minor.saturating_add(1), 0)
}
pub const fn next_patch(self) -> Self {
Self::new(self.major, self.minor, self.patch.saturating_add(1))
}
#[must_use]
#[allow(clippy::len_without_is_empty)]
pub const fn len(self) -> usize {
Digits(self.major).count_digits10() as usize
+ 1
+ Digits(self.minor).count_digits10() as usize
+ 1
+ Digits(self.patch).count_digits10() as usize
}
pub const fn write_to(self, buf: &mut [u8]) -> Result<usize, usize> {
let needed = self.len();
is! { buf.len() < needed, return Err(needed) }
let mut pos = 0;
pos += Digits(self.major).write_digits10_fast(buf, pos);
write_at![buf, +=pos, b'.'];
pos += Digits(self.minor).write_digits10(buf, pos);
write_at![buf, +=pos, b'.'];
pos += Digits(self.patch).write_digits10(buf, pos);
Ok(pos)
}
pub const fn to_str(self, buf: &mut [u8]) -> Result<&str, usize> {
let len = unwrap![ok? self.write_to(buf)];
let slice = Slice::range_to(buf, len);
cfg_select! { all(feature = "unsafe_str", not(feature = "safe_text")) => {
unsafe { Ok(Str::from_utf8_unchecked(slice)) }
} _ => { Ok(unwrap![ok_guaranteed_or_ub Str::from_utf8(slice)]) }}
}
}
#[doc = crate::_tags!(code)]
#[doc = crate::_doc_meta!{location("code")}]
#[must_use]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct VersionFull<'a> {
pub version: Version,
pub pre: Option<&'a str>,
pub build: Option<&'a str>,
}
impl<'a> ConstInit for VersionFull<'a> {
const INIT: Self = Self::ZERO;
}
impl Display for VersionFull<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult<()> {
Display::fmt(&self.version, f)?;
is! { let Some(pre) = self.pre, write!(f, "-{pre}")?; }
is! { let Some(build) = self.build, write!(f, "+{build}")?; }
Ok(())
}
}
impl<'a> VersionFull<'a> {
pub const ZERO: Self = Self::from_version(Version::ZERO);
pub const fn new(major: u16, minor: u16, patch: u16) -> Self {
Self::from_version(Version::new(major, minor, patch))
}
pub const fn from_version(version: Version) -> Self {
Self { version, pre: None, build: None }
}
pub const fn with(version: Version, pre: Option<&'a str>, build: Option<&'a str>) -> Self {
Self { version, pre, build }
}
pub const fn version(self) -> Version {
self.version
}
pub const fn with_pre(mut self, pre: &'a str) -> Self {
self.pre = Some(pre);
self
}
pub const fn without_pre(mut self) -> Self {
self.pre = None;
self
}
pub const fn with_build(mut self, build: &'a str) -> Self {
self.build = Some(build);
self
}
pub const fn without_build(mut self) -> Self {
self.build = None;
self
}
#[must_use]
pub const fn is_core(self) -> bool {
self.pre.is_none() && self.build.is_none()
}
}
impl VersionFull<'_> {
#[must_use]
#[allow(clippy::len_without_is_empty)]
pub const fn len(self) -> usize {
let mut len = self.version.len();
is! { let Some(pre) = self.pre, len += 1 + pre.len(); } is! { let Some(build) = self.build, len += 1 + build.len(); } len
}
pub const fn write_to(self, buf: &mut [u8]) -> Result<usize, usize> {
let needed = self.len();
is! { buf.len() < needed, return Err(needed) }
let mut pos = unwrap![ok_guaranteed_or_ub self.version.write_to(buf)];
if let Some(pre) = self.pre {
write_at![buf, +=pos, b'-'];
let bytes = pre.as_bytes();
whilst! { i in 0..bytes.len(); { write_at![buf, +=pos, bytes[i]]; }}
}
if let Some(build) = self.build {
write_at![buf, +=pos, b'+'];
let bytes = build.as_bytes();
whilst! { i in 0..bytes.len(); { write_at![buf, +=pos, bytes[i]]; }}
}
Ok(pos)
}
pub const fn to_str(self, buf: &mut [u8]) -> Result<&str, usize> {
let len = unwrap![ok? self.write_to(buf)];
let slice = Slice::range_to(buf, len);
cfg_select! { all(feature = "unsafe_str", not(feature = "safe_text")) => {
unsafe { Ok(Str::from_utf8_unchecked(slice)) }
} _ => {
Ok(unwrap![ok_guaranteed_or_ub Str::from_utf8(slice)])
}}
}
}
impl From<Version> for VersionFull<'_> {
fn from(v: Version) -> Self {
Self::from_version(v)
}
}
impl From<VersionFull<'_>> for Version {
fn from(v: VersionFull) -> Self {
v.version()
}
}