use std::cmp::Ordering;
use std::fmt::{self, Display};
#[derive(Clone, Debug, Default)]
pub struct Percent(f32);
impl Percent {
#[rustfmt::skip]
pub fn try_new(val: f32) -> Option<Self> {
(0.0 < val && val <= 100.0).then_some(Percent(val))
}
pub fn val(&self) -> f32 { self.0 }
}
impl Eq for Percent {}
impl PartialEq for Percent {
fn eq(&self, right: &Self) -> bool { self.0 == right.0 }
}
impl PartialOrd for Percent {
fn partial_cmp(&self, right: &Self) -> Option<Ordering> {
self.0.partial_cmp(&right.0)
}
}
impl Ord for Percent {
fn cmp(&self, right: &Self) -> Ordering {
self.partial_cmp(right).unwrap_or(Ordering::Equal)
}
}
impl Display for Percent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(self.0 < 1.0)
.then(|| write!(f, "{:.1}%", self.0))
.unwrap_or_else(|| write!(f, "{:.0}%", self.0))
}
}
#[derive(Debug, Clone)]
pub struct TagPercent {
percent: Percent,
label: String
}
impl TagPercent {
#[rustfmt::skip]
pub fn new(label: &str, percent: f32) -> Option<Self> {
Some(
Self { percent: Percent::try_new(percent)?, label: label.to_string() }
)
}
pub fn label(&self) -> &str { &self.label }
pub fn display_label(&self) -> String { format!("{} - {}", self.percent, self.label) }
pub fn percent(&self) -> &Percent { &self.percent }
pub fn percent_val(&self) -> f32 { self.percent.val() }
}
impl PartialOrd for TagPercent {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(
other.percent
.cmp(&self.percent)
.then_with(|| self.label.cmp(&other.label))
)
}
}
impl PartialEq for TagPercent {
fn eq(&self, other: &Self) -> bool {
(self.percent == other.percent) && (self.label == other.label)
}
}
impl Eq for TagPercent {}
#[cfg(test)]
mod tests {
use spectral::prelude::*;
use super::*;
#[test]
fn test_new() {
assert_that!(TagPercent::new("foo", 50.0))
.contains(TagPercent { percent: Percent(50.0), label: "foo".to_string() });
}
#[test]
fn test_display_label() {
assert_that!(TagPercent::new("foo", 100.0).unwrap().display_label())
.is_equal_to("100% - foo".to_string());
assert_that!(TagPercent::new("foo", 50.0).unwrap().display_label())
.is_equal_to("50% - foo".to_string());
assert_that!(TagPercent::new("foo", 4.0).unwrap().display_label())
.is_equal_to("4% - foo".to_string());
}
#[test]
#[rustfmt::skip]
fn test_new_zero() {
assert_that!(TagPercent::new("foo", 0.0)).is_none();
}
#[test]
#[rustfmt::skip]
fn test_new_negative() {
assert_that!(TagPercent::new("foo", -10.0)).is_none();
}
#[test]
#[rustfmt::skip]
fn test_new_too_large() {
assert_that!(TagPercent::new("foo", 100.1)).is_none();
}
#[test]
fn test_accessors() {
let val = TagPercent::new("bar", 37.5).unwrap();
assert_that!(val.label()).is_equal_to("bar");
assert_that!(val.percent_val()).is_equal_to(37.5);
}
#[test]
fn test_order_by_percentage_same_label() {
let val1 = TagPercent::new("bar", 10.0).unwrap();
let val2 = TagPercent::new("bar", 15.0).unwrap();
assert_that!(val1.partial_cmp(&val2))
.is_some()
.is_equal_to(Ordering::Greater);
assert_that!(val2.partial_cmp(&val1))
.is_some()
.is_equal_to(Ordering::Less);
}
#[test]
fn test_order_by_percentage_diff_label() {
let val1 = TagPercent::new("bar", 10.0).unwrap();
let val2 = TagPercent::new("foo", 15.0).unwrap();
assert_that!(val1.partial_cmp(&val2))
.is_some()
.is_equal_to(Ordering::Greater);
assert_that!(val2.partial_cmp(&val1))
.is_some()
.is_equal_to(Ordering::Less);
}
#[test]
fn test_order_by_label_same_percentage() {
let val1 = TagPercent::new("bar", 10.0).unwrap();
let val2 = TagPercent::new("foo", 10.0).unwrap();
assert_that!(val1.partial_cmp(&val2))
.is_some()
.is_equal_to(Ordering::Less);
assert_that!(val2.partial_cmp(&val1))
.is_some()
.is_equal_to(Ordering::Greater);
}
}