use enum_map::EnumMap;
use serde::Deserialize;
use std::{collections::HashMap, fmt::Debug, sync::Arc};
use super::{FractionsConfig, PhysicalQuantity, System};
#[derive(Debug, Deserialize, Clone, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct UnitsFile {
pub default_system: Option<System>,
pub si: Option<SI>,
pub fractions: Option<Fractions>,
pub extend: Option<Extend>,
#[serde(default)]
pub quantity: Vec<QuantityGroup>,
}
#[derive(Debug, Deserialize, Default, Clone, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct SI {
pub prefixes: Option<EnumMap<SIPrefix, Vec<String>>>,
pub symbol_prefixes: Option<EnumMap<SIPrefix, Vec<String>>>,
#[serde(default)]
pub precedence: Precedence,
}
#[derive(
Debug, Deserialize, Clone, Copy, strum::Display, strum::AsRefStr, enum_map::Enum, PartialEq,
)]
#[serde(rename_all = "camelCase")]
#[strum(serialize_all = "camelCase")]
pub enum SIPrefix {
Kilo,
Hecto,
Deca,
Deci,
Centi,
Milli,
}
impl SIPrefix {
pub fn ratio(&self) -> f64 {
match self {
SIPrefix::Kilo => 1e3,
SIPrefix::Hecto => 1e2,
SIPrefix::Deca => 1e1,
SIPrefix::Deci => 1e-1,
SIPrefix::Centi => 1e-2,
SIPrefix::Milli => 1e-3,
}
}
}
#[derive(Debug, Clone, Deserialize, Default, PartialEq)]
#[serde(default, deny_unknown_fields)]
pub struct Fractions {
pub all: Option<FractionsConfigWrapper>,
pub metric: Option<FractionsConfigWrapper>,
pub imperial: Option<FractionsConfigWrapper>,
pub quantity: HashMap<PhysicalQuantity, FractionsConfigWrapper>,
pub unit: HashMap<String, FractionsConfigWrapper>,
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum FractionsConfigWrapper {
Toggle(bool),
Custom(FractionsConfigHelper),
}
impl FractionsConfigWrapper {
pub fn get(self) -> FractionsConfigHelper {
match self {
FractionsConfigWrapper::Toggle(enabled) => FractionsConfigHelper {
enabled: Some(enabled),
..Default::default()
},
FractionsConfigWrapper::Custom(cfg) => cfg,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, Default, PartialEq)]
#[serde(default, deny_unknown_fields)]
pub struct FractionsConfigHelper {
pub enabled: Option<bool>,
pub accuracy: Option<f32>,
pub max_denominator: Option<u8>,
pub max_whole: Option<u32>,
}
impl FractionsConfigHelper {
pub(crate) fn merge(self, other: FractionsConfigHelper) -> Self {
Self {
enabled: self.enabled.or(other.enabled),
accuracy: self.accuracy.or(other.accuracy),
max_denominator: self.max_denominator.or(other.max_denominator),
max_whole: self.max_whole.or(other.max_whole),
}
}
pub(crate) fn define(self) -> FractionsConfig {
let d = FractionsConfig::default();
FractionsConfig {
enabled: self.enabled.unwrap_or(d.enabled),
accuracy: self.accuracy.unwrap_or(d.accuracy).clamp(0.0, 1.0),
max_denominator: self
.max_denominator
.unwrap_or(d.max_denominator)
.clamp(1, 16),
max_whole: self.max_whole.unwrap_or(d.max_whole),
}
}
}
#[derive(Debug, Default, Deserialize, Clone, PartialEq)]
#[serde(default, deny_unknown_fields)]
pub struct Extend {
pub precedence: Precedence,
pub units: HashMap<String, ExtendUnitEntry>,
}
#[derive(Debug, Default, Deserialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum Precedence {
#[default]
Before,
After,
Override,
}
#[derive(Debug, Deserialize, Clone, Default, PartialEq)]
#[serde(default)]
pub struct ExtendUnitEntry {
pub ratio: Option<f64>,
pub difference: Option<f64>,
#[serde(alias = "name")]
pub names: Option<Vec<Arc<str>>>,
#[serde(alias = "symbol")]
pub symbols: Option<Vec<Arc<str>>>,
#[serde(alias = "alias")]
pub aliases: Option<Vec<Arc<str>>>,
}
#[derive(Debug, Deserialize, Clone, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct QuantityGroup {
pub quantity: PhysicalQuantity,
#[serde(default)]
pub best: Option<BestUnits>,
#[serde(default)]
pub units: Option<Units>,
}
#[derive(Debug, Deserialize, Clone, PartialEq)]
#[serde(untagged, deny_unknown_fields)]
pub enum BestUnits {
Unified(Vec<String>),
BySystem {
metric: Vec<String>,
imperial: Vec<String>,
},
}
#[derive(Debug, Deserialize, Clone, PartialEq)]
#[serde(untagged, deny_unknown_fields)]
pub enum Units {
Unified(Vec<UnitEntry>),
BySystem {
#[serde(default)]
metric: Vec<UnitEntry>,
#[serde(default)]
imperial: Vec<UnitEntry>,
#[serde(default)]
unspecified: Vec<UnitEntry>,
},
}
#[derive(Debug, Deserialize, Clone, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct UnitEntry {
#[serde(alias = "name")]
pub names: Vec<Arc<str>>,
#[serde(alias = "symbol")]
pub symbols: Vec<Arc<str>>,
#[serde(default, alias = "alias")]
pub aliases: Vec<Arc<str>>,
pub ratio: f64,
#[serde(default)]
pub difference: f64,
#[serde(default)]
pub expand_si: bool,
}
#[cfg(feature = "bundled_units")]
include!(concat!(env!("OUT_DIR"), "/bundled_units.rs"));
#[cfg(feature = "bundled_units")]
impl UnitsFile {
pub fn bundled() -> Self {
__bundled_units::get_bundled()
}
}
#[cfg(all(test, feature = "bundled_units"))]
mod tests {
use super::*;
#[test]
fn generated_bundled() {
let text = std::fs::read_to_string("units.toml").unwrap();
let expected: UnitsFile = toml::from_str(&text).unwrap();
assert_eq!(expected, UnitsFile::bundled());
}
}