use std::fmt;
use strum::VariantArray;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, VariantArray)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum Unit {
Millimeter,
Centimeter,
Decimeter,
Meter,
Kilometer,
Inch,
Foot,
Yard,
Mile,
Degrees,
Radians,
Percent,
Default,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum UnitCategory {
Length,
Angular,
Dimensionless,
}
impl Unit {
pub const fn name(self) -> &'static str {
self.row().1
}
pub const fn category(self) -> UnitCategory {
self.row().0
}
pub const fn scale(self) -> f64 {
self.row().2
}
pub fn from_name(name: &str) -> Option<Self> {
Unit::VARIANTS.iter().copied().find(|unit| unit.name() == name)
}
const fn row(self) -> (UnitCategory, &'static str, f64) {
match self {
Unit::Millimeter => (UnitCategory::Length, "mm", 0.001),
Unit::Centimeter => (UnitCategory::Length, "cm", 0.01),
Unit::Decimeter => (UnitCategory::Length, "dm", 0.1),
Unit::Meter => (UnitCategory::Length, "m", 1.0),
Unit::Kilometer => (UnitCategory::Length, "km", 1000.0),
Unit::Inch => (UnitCategory::Length, "in", 0.0254),
Unit::Foot => (UnitCategory::Length, "ft", 0.3048),
Unit::Yard => (UnitCategory::Length, "yd", 0.9144),
Unit::Mile => (UnitCategory::Length, "mi", 1609.344),
Unit::Degrees => (UnitCategory::Angular, "deg", 1.0),
Unit::Radians => (UnitCategory::Angular, "rad", 57.295_779_513_082_32),
Unit::Percent => (UnitCategory::Dimensionless, "%", 0.01),
Unit::Default => (UnitCategory::Dimensionless, "default", 1.0),
}
}
}
impl UnitCategory {
pub const fn default_unit(self) -> Unit {
match self {
UnitCategory::Length => Unit::Meter,
UnitCategory::Angular => Unit::Degrees,
UnitCategory::Dimensionless => Unit::Default,
}
}
}
impl fmt::Display for Unit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn names_round_trip() {
for unit in Unit::VARIANTS {
assert_eq!(Unit::from_name(unit.name()), Some(*unit), "{unit}");
}
assert_eq!(Unit::from_name("furlong"), None);
}
#[test]
fn default_unit_scales_one() {
for category in [UnitCategory::Length, UnitCategory::Angular, UnitCategory::Dimensionless] {
let default = category.default_unit();
assert_eq!(default.category(), category);
assert_eq!(default.scale(), 1.0);
let ones = Unit::VARIANTS
.iter()
.filter(|unit| unit.category() == category && unit.scale() == 1.0)
.count();
assert_eq!(ones, 1, "{category:?}");
}
}
#[test]
fn scales_convert() {
assert_eq!(Unit::Centimeter.scale() / Unit::Millimeter.scale(), 10.0);
assert_eq!(Unit::Percent.scale(), 0.01);
}
}