use core::{
fmt::{self, Debug, Display},
num::ParseFloatError,
str::FromStr
};
use crate::xml::TrustedNoEscape;
#[derive(Debug, PartialEq)]
pub enum TimeDesignationError {
BadUnit,
BadLength,
Negative,
ParseFloat(ParseFloatError)
}
impl Display for TimeDesignationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TimeDesignationError::BadUnit => f.write_str("time designation has invalid unit (allowed are ms, s)"),
TimeDesignationError::BadLength => f.write_str("string is too short to be a valid time designation"),
TimeDesignationError::Negative => f.write_str("time designations cannot be negative"),
TimeDesignationError::ParseFloat(e) => f.write_fmt(format_args!("couldn't parse float: {e}"))
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for TimeDesignationError {}
#[derive(Default, Clone, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TimeDesignation {
millis: f32
}
impl TimeDesignation {
pub fn from_millis(millis: f32) -> Self {
Self { millis }
}
pub fn to_millis(&self) -> f32 {
self.millis
}
}
impl FromStr for TimeDesignation {
type Err = TimeDesignationError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let len = s.len();
if len < 2 {
return Err(TimeDesignationError::BadLength);
}
let (unit, skip) = if s.ends_with("ms") {
(1., 2)
} else if s.ends_with('s') && matches!(s.chars().nth(len - 2), Some('0'..='9') | Some('.')) {
(1000., 1)
} else {
return Err(TimeDesignationError::BadUnit);
};
let f = s[..len - skip].parse::<f32>().map_err(TimeDesignationError::ParseFloat)?;
if f.is_sign_negative() {
return Err(TimeDesignationError::Negative);
}
Ok(Self::from_millis(f * unit))
}
}
impl From<&str> for TimeDesignation {
fn from(value: &str) -> Self {
value.parse().unwrap_or_default()
}
}
impl Display for TimeDesignation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!("{:+}ms", self.to_millis()))
}
}
impl TrustedNoEscape for TimeDesignation {}
impl Debug for TimeDesignation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(self, f)
}
}
#[derive(Debug, PartialEq)]
pub enum DecibelsError {
BadUnit,
BadLength,
ParseFloat(ParseFloatError)
}
impl Display for DecibelsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DecibelsError::BadUnit => f.write_str("decibels has invalid unit (allowed are dB)"),
DecibelsError::BadLength => f.write_str("string is too short to be a valid decibel value"),
DecibelsError::ParseFloat(e) => f.write_fmt(format_args!("couldn't parse float: {e}"))
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for DecibelsError {}
#[derive(Default, Clone, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Decibels(f32);
impl Decibels {
pub fn new(value: f32) -> Self {
Self(value)
}
pub fn value(&self) -> f32 {
self.0
}
}
impl FromStr for Decibels {
type Err = DecibelsError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let len = s.len();
if len < 2 {
return Err(DecibelsError::BadLength);
}
if !s.ends_with("dB") {
return Err(DecibelsError::BadUnit);
}
let f = s[..len - 2].parse::<f32>().map_err(DecibelsError::ParseFloat)?;
Ok(Self(f))
}
}
impl From<f32> for Decibels {
fn from(value: f32) -> Self {
Decibels(value)
}
}
impl From<&str> for Decibels {
fn from(value: &str) -> Self {
value.parse().unwrap_or_default()
}
}
impl Display for Decibels {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!("{:+}dB", self.0))
}
}
impl TrustedNoEscape for Decibels {}
impl Debug for Decibels {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(self, f)
}
}
pub(crate) struct SpeedFormatter(pub(crate) f32);
impl Display for SpeedFormatter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!("{}%", self.0 * 100.))
}
}
impl TrustedNoEscape for SpeedFormatter {}
#[cfg(test)]
mod tests {
use super::{Decibels, TimeDesignation};
#[test]
fn parse_time_designation() {
assert_eq!("+7s".parse::<TimeDesignation>(), Ok(TimeDesignation::from_millis(7000.0)));
assert_eq!("700ms".parse::<TimeDesignation>(), Ok(TimeDesignation::from_millis(700.0)));
assert!("-.7s".parse::<TimeDesignation>().is_err());
}
#[test]
fn parse_decibels() {
assert_eq!("+6dB".parse::<Decibels>(), Ok(Decibels(6.0)));
assert_eq!("-.6dB".parse::<Decibels>(), Ok(Decibels(-0.6)));
assert!("6".parse::<Decibels>().is_err());
assert!("6db".parse::<Decibels>().is_err());
}
}