use core::str::FromStr;
use derive_more::{Debug, Display};
#[cfg(feature = "zerocopy")]
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
use crate::spelling::{Count, read_count};
use crate::{ParseTickdeltaError, TickRate, Timedelta};
#[repr(transparent)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[display("{}", Count(*_0, " ticks"))]
#[debug("{self}")]
#[cfg_attr(feature = "zerocopy", derive(FromBytes, IntoBytes, Immutable, KnownLayout))]
pub struct Tickstamp(pub(crate) i64);
impl Tickstamp {
#[inline]
#[must_use]
pub const fn from_ticks(ticks: i64) -> Self {
Self(ticks)
}
#[inline]
#[must_use]
pub const fn as_ticks(self) -> i64 {
self.0
}
}
#[repr(transparent)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[display("{}", Count(*_0, " ticks"))]
#[debug("{self}")]
#[cfg_attr(feature = "zerocopy", derive(FromBytes, IntoBytes, Immutable, KnownLayout))]
pub struct Tickdelta(pub(crate) i64);
impl Tickdelta {
#[inline]
#[must_use]
pub const fn from_ticks(ticks: i64) -> Self {
Self(ticks)
}
#[inline]
#[must_use]
pub const fn as_ticks(self) -> i64 {
self.0
}
#[inline]
#[must_use]
pub const fn to_timedelta(self, rate: TickRate) -> Timedelta {
Timedelta(rate.ticks_to_nanos(self.0))
}
}
macro_rules! read_as_ticks {
($count:ident) => {
impl FromStr for $count {
type Err = ParseTickdeltaError;
fn from_str(text: &str) -> Result<Self, Self::Err> {
let count = text.strip_suffix(" ticks").and_then(read_count);
count.map(Self).ok_or(ParseTickdeltaError)
}
}
};
}
read_as_ticks!(Tickstamp);
read_as_ticks!(Tickdelta);
#[cfg(test)]
mod tests {
use alloc::format;
use alloc::string::ToString as _;
use rstest::rstest;
use crate::{ParseTickdeltaError, Tickdelta, Tickstamp};
#[test]
fn a_count_of_ticks_reads_back_from_its_spelling() {
let span = Tickdelta::from_ticks(-24);
assert_eq!(span.to_string(), "-24 ticks", "written as its count");
assert_eq!("-24 ticks".parse(), Ok(span), "and read back");
assert_eq!(
"1000 ticks".parse(),
Ok(Tickstamp::from_ticks(1_000)),
"a reading the same way"
);
}
#[test]
fn a_width_pads_the_count_with_its_unit() {
assert_eq!(format!("[{:>10}]", Tickdelta::from_ticks(24)), "[ 24 ticks]", "a span");
assert_eq!(format!("[{:<10}]", Tickstamp::from_ticks(24)), "[24 ticks ]", "and a reading");
}
#[rstest]
#[case::no_unit("24")]
#[case::a_sign_display_never_writes("+24 ticks")]
#[case::one_tick("1 tick")]
#[case::no_space("24ticks")]
#[case::past_the_range("9223372036854775808 ticks")]
fn a_malformed_count_of_ticks_is_refused(#[case] text: &str) {
assert_eq!(text.parse::<Tickdelta>(), Err(ParseTickdeltaError), "{text:?} is refused");
}
}