1 use std::cmp::Ordering;
2 use std::{borrow::Cow, collections::HashMap, fmt, io};
3
4 use super::base_unit::BaseUnit;
5 use crate::num::complex::Complex;
6 use crate::result::FResult;
7 use crate::serialize::{Deserialize, Serialize};
8 use crate::Interrupt;
9
10 /// A named unit, like kilogram, megabyte or percent.
11 #[derive(Clone)]
12 pub(crate) struct NamedUnit {
13 prefix: Cow<'static, str>,
14 pub(super) singular_name: Cow<'static, str>,
15 plural_name: Cow<'static, str>,
16 alias: bool,
17 pub(super) base_units: HashMap<BaseUnit, Complex>,
18 pub(super) scale: Complex,
19 }
20
compare_hashmaps<I: Interrupt>( a: &HashMap<BaseUnit, Complex>, b: &HashMap<BaseUnit, Complex>, int: &I, ) -> FResult<bool>21 pub(crate) fn compare_hashmaps<I: Interrupt>(
22 a: &HashMap<BaseUnit, Complex>,
23 b: &HashMap<BaseUnit, Complex>,
24 int: &I,
25 ) -> FResult<bool> {
26 if a.len() != b.len() {
27 return Ok(false);
28 }
29 for (k, v) in a {
30 match b.get(k) {
31 None => return Ok(false),
32 Some(o) => {
33 if v.compare(o, int)? != Some(Ordering::Equal) {
34 return Ok(false);
35 }
36 }
37 }
38 }
39 Ok(true)
40 }
41
42 impl NamedUnit {
new( prefix: Cow<'static, str>, singular_name: Cow<'static, str>, plural_name: Cow<'static, str>, alias: bool, base_units: HashMap<BaseUnit, Complex>, scale: impl Into<Complex>, ) -> Self43 pub(crate) fn new(
44 prefix: Cow<'static, str>,
45 singular_name: Cow<'static, str>,
46 plural_name: Cow<'static, str>,
47 alias: bool,
48 base_units: HashMap<BaseUnit, Complex>,
49 scale: impl Into<Complex>,
50 ) -> Self {
51 Self {
52 prefix,
53 singular_name,
54 plural_name,
55 alias,
56 base_units,
57 scale: scale.into(),
58 }
59 }
60
serialize(&self, write: &mut impl io::Write) -> FResult<()>61 pub(crate) fn serialize(&self, write: &mut impl io::Write) -> FResult<()> {
62 self.prefix.as_ref().serialize(write)?;
63 self.singular_name.as_ref().serialize(write)?;
64 self.plural_name.as_ref().serialize(write)?;
65 self.alias.serialize(write)?;
66
67 self.base_units.len().serialize(write)?;
68 for (a, b) in &self.base_units {
69 a.serialize(write)?;
70 b.serialize(write)?;
71 }
72
73 self.scale.serialize(write)?;
74 Ok(())
75 }
76
deserialize(read: &mut impl io::Read) -> FResult<Self>77 pub(crate) fn deserialize(read: &mut impl io::Read) -> FResult<Self> {
78 let prefix = String::deserialize(read)?;
79 let singular_name = String::deserialize(read)?;
80 let plural_name = String::deserialize(read)?;
81 let alias = bool::deserialize(read)?;
82
83 let len = usize::deserialize(read)?;
84 let mut hashmap = HashMap::with_capacity(len);
85 for _ in 0..len {
86 let k = BaseUnit::deserialize(read)?;
87 let v = Complex::deserialize(read)?;
88 hashmap.insert(k, v);
89 }
90 Ok(Self {
91 prefix: Cow::Owned(prefix),
92 singular_name: Cow::Owned(singular_name),
93 plural_name: Cow::Owned(plural_name),
94 alias,
95 base_units: hashmap,
96 scale: Complex::deserialize(read)?,
97 })
98 }
99
compare<I: Interrupt>(&self, other: &Self, int: &I) -> FResult<bool>100 pub(crate) fn compare<I: Interrupt>(&self, other: &Self, int: &I) -> FResult<bool> {
101 Ok(self.prefix == other.prefix
102 && self.singular_name == other.singular_name
103 && self.plural_name == other.plural_name
104 && self.alias == other.alias
105 && self.scale.compare(&other.scale, int)? == Some(Ordering::Equal)
106 && compare_hashmaps(&self.base_units, &other.base_units, int)?)
107 }
108
new_from_base(base_unit: BaseUnit) -> Self109 pub(crate) fn new_from_base(base_unit: BaseUnit) -> Self {
110 Self {
111 prefix: "".into(),
112 singular_name: base_unit.name().to_string().into(),
113 plural_name: base_unit.name().to_string().into(),
114 alias: false,
115 base_units: {
116 let mut base_units = HashMap::new();
117 base_units.insert(base_unit, 1.into());
118 base_units
119 },
120 scale: 1.into(),
121 }
122 }
123
prefix_and_name(&self, plural: bool) -> (&str, &str)124 pub(crate) fn prefix_and_name(&self, plural: bool) -> (&str, &str) {
125 (
126 self.prefix.as_ref(),
127 if plural {
128 self.plural_name.as_ref()
129 } else {
130 self.singular_name.as_ref()
131 },
132 )
133 }
134
has_no_base_units(&self) -> bool135 pub(crate) fn has_no_base_units(&self) -> bool {
136 self.base_units.is_empty()
137 }
138
is_alias(&self) -> bool139 pub(crate) fn is_alias(&self) -> bool {
140 self.alias && self.has_no_base_units()
141 }
142
143 /// Returns whether or not this unit should be printed with a
144 /// space (between the number and the unit). This should be true for most
145 /// units like kg or m, but not for % or °
print_with_space(&self) -> bool146 pub(crate) fn print_with_space(&self) -> bool {
147 // Alphabetic names like kg or m should have a space,
148 // while non-alphabetic names like % or ' shouldn't.
149 // Empty names shouldn't really exist, but they might as well have a space.
150
151 // degree symbol
152 if self.singular_name == "\u{b0}" {
153 return false;
154 }
155
156 // if it starts with a quote and is more than one character long, print it with a space
157 if (self.singular_name.starts_with('\'') || self.singular_name.starts_with('\"'))
158 && self.singular_name.len() > 1
159 {
160 return true;
161 }
162
163 self.singular_name
164 .chars()
165 .next()
166 .map_or(true, |first_char| {
167 char::is_alphabetic(first_char) || first_char == '\u{b0}'
168 })
169 }
170 }
171
172 impl fmt::Debug for NamedUnit {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 if self.prefix.is_empty() {
175 write!(f, "{}", self.singular_name)?;
176 } else {
177 write!(f, "{}-{}", self.prefix, self.singular_name)?;
178 }
179 write!(f, " (")?;
180 if self.plural_name != self.singular_name {
181 if self.prefix.is_empty() {
182 write!(f, "{}, ", self.plural_name)?;
183 } else {
184 write!(f, "{}-{}, ", self.prefix, self.plural_name)?;
185 }
186 }
187 write!(f, "= {:?}", self.scale)?;
188 let mut it = self.base_units.iter().collect::<Vec<_>>();
189 it.sort_by_key(|(k, _v)| k.name());
190 for (base_unit, exponent) in &it {
191 write!(f, " {base_unit:?}")?;
192 if !exponent.is_definitely_one() {
193 write!(f, "^{exponent:?}")?;
194 }
195 }
196 write!(f, ")")?;
197 Ok(())
198 }
199 }
200