okane_core/report/
commodity.rs1use std::borrow::Cow;
4use std::fmt::Display;
5
6use bumpalo::Bump;
7use bumpalo_intern::dense::{DenseInternStore, InternTag, Interned, Keyed, OccupiedError};
8use pretty_decimal::PrettyDecimal;
9
10#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
12pub struct Commodity<'arena>(&'arena str);
13
14impl<'a> Keyed<'a> for Commodity<'a> {
15 fn intern_key(&self) -> &'a str {
16 self.0
17 }
18}
19impl<'a> Interned<'a> for Commodity<'a> {
20 type View<'b> = Commodity<'b>;
21
22 fn intern_from<'b>(arena: &'a Bump, view: Self::View<'b>) -> (&'a str, Self) {
23 let key = arena.alloc_str(view.0);
24 (key, Commodity(key))
25 }
26}
27
28impl Display for Commodity<'_> {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 self.as_str().fmt(f)
31 }
32}
33
34impl<'a> Commodity<'a> {
35 pub fn as_str(&self) -> &'a str {
37 self.0
38 }
39}
40
41#[derive(Debug, PartialEq, Eq, Hash, Clone)]
44pub struct OwnedCommodity(String);
45
46impl OwnedCommodity {
47 pub fn from_string(v: String) -> Self {
49 Self(v)
50 }
51
52 pub fn as_str(&self) -> &str {
54 self.0.as_str()
55 }
56
57 pub fn into_string(self) -> String {
59 self.0
60 }
61}
62
63impl From<Commodity<'_>> for OwnedCommodity {
64 fn from(value: Commodity<'_>) -> Self {
65 Self(value.as_str().to_string())
66 }
67}
68
69impl Display for OwnedCommodity {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 self.0.fmt(f)
72 }
73}
74
75#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
76pub struct CommodityTag<'a>(InternTag<Commodity<'a>>);
77
78impl<'ctx> CommodityTag<'ctx> {
79 pub fn as_index(&self) -> usize {
82 self.0.as_index()
83 }
84
85 pub(super) fn to_str_lossy(self, commodity_store: &CommodityStore<'ctx>) -> Cow<'ctx, str> {
87 match commodity_store.get(self) {
88 Some(x) => Cow::Borrowed(x.as_str()),
89 None => Cow::Owned(format!("unknown#{}", self.as_index())),
90 }
91 }
92
93 pub(super) fn to_owned_lossy(self, commodity_store: &CommodityStore<'ctx>) -> OwnedCommodity {
97 OwnedCommodity::from_string(self.to_str_lossy(commodity_store).into_owned())
98 }
99}
100
101pub(super) struct CommodityStore<'arena> {
103 intern: DenseInternStore<'arena, Commodity<'arena>>,
104 formatting: CommodityMap<PrettyDecimal>,
105}
106
107impl<'arena> std::fmt::Debug for CommodityStore<'arena> {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 f.debug_struct("CommodityStore")
110 .field("intern", &format!("[{} commodities]", self.intern.len()))
111 .finish()
112 }
113}
114
115impl<'arena> CommodityStore<'arena> {
116 pub fn new(arena: &'arena Bump) -> Self {
118 Self {
119 intern: DenseInternStore::new(arena),
120 formatting: CommodityMap::new(),
121 }
122 }
123
124 pub fn ensure(&mut self, value: &'_ str) -> CommodityTag<'arena> {
128 CommodityTag(self.intern.ensure(Commodity(value)))
129 }
130
131 pub fn get(&self, tag: CommodityTag<'arena>) -> Option<Commodity<'arena>> {
133 self.intern.get(tag.0)
134 }
135
136 pub fn resolve(&self, value: &str) -> Option<CommodityTag<'arena>> {
138 self.intern.resolve(value).map(CommodityTag)
139 }
140
141 #[cfg(test)]
142 pub fn insert(
143 &mut self,
144 value: &str,
145 ) -> Result<CommodityTag<'arena>, OccupiedError<Commodity<'arena>>> {
146 self.intern.try_insert(Commodity(value)).map(CommodityTag)
147 }
148
149 pub(super) fn insert_alias(
153 &mut self,
154 value: &str,
155 canonical: CommodityTag<'arena>,
156 ) -> Result<(), OccupiedError<Commodity<'arena>>> {
157 self.intern.insert_alias(Commodity(value), canonical.0)
158 }
159
160 #[inline]
162 pub(super) fn get_decimal_point(&self, commodity: CommodityTag<'arena>) -> Option<u32> {
163 self.formatting.get(commodity).map(|x| x.scale())
164 }
165
166 #[inline]
168 pub(super) fn set_format(&mut self, commodity: CommodityTag<'arena>, format: PrettyDecimal) {
169 self.formatting.set(commodity, format);
170 }
171
172 #[inline]
174 pub fn len(&self) -> usize {
175 self.intern.len()
176 }
177}
178
179#[derive(Debug, PartialEq, Eq, Clone)]
181pub struct CommodityMap<T> {
182 inner: Vec<Option<T>>,
183}
184
185impl<T> CommodityMap<T> {
186 pub fn new() -> Self {
188 Self::with_capacity(0)
189 }
190
191 pub fn with_capacity(capacity: usize) -> Self {
193 Self {
194 inner: Vec::with_capacity(capacity),
195 }
196 }
197
198 pub fn get(&self, k: CommodityTag<'_>) -> Option<&T> {
200 match self.inner.get(k.as_index()) {
201 Some(Some(r)) => Some(r),
202 Some(None) | None => None,
203 }
204 }
205}
206
207impl<T: Clone> CommodityMap<T> {
208 pub fn get_mut(&mut self, k: CommodityTag<'_>) -> &mut Option<T> {
210 self.ensure_size(k);
211 &mut self.inner[k.as_index()]
212 }
213
214 pub fn set(&mut self, k: CommodityTag<'_>, v: T) {
216 self.ensure_size(k);
217 self.inner[k.as_index()] = Some(v);
218 }
219
220 #[inline]
222 fn ensure_size(&mut self, k: CommodityTag<'_>) {
223 if self.inner.len() <= k.as_index() {
224 self.inner.resize(k.as_index() + 1, None);
225 }
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 use pretty_assertions::assert_eq;
234 use rust_decimal_macros::dec;
235
236 #[test]
237 fn to_owned_lossy() {
238 let arena = Bump::new();
239 let mut commodities = CommodityStore::new(&arena);
240 let chf = commodities.insert("CHF").unwrap();
241
242 assert_eq!(
243 OwnedCommodity::from_string("CHF".to_string()),
244 chf.to_owned_lossy(&commodities)
245 );
246
247 let unknown = CommodityTag(InternTag::new(1));
248
249 assert_eq!(
250 OwnedCommodity::from_string("unknown#1".to_string()),
251 unknown.to_owned_lossy(&commodities)
252 );
253 }
254
255 #[test]
256 fn get_decimal_point_returns_none_if_unspecified() {
257 let arena = Bump::new();
258 let mut commodities = CommodityStore::new(&arena);
259 let jpy = commodities.insert("JPY").unwrap();
260
261 assert_eq!(None, commodities.get_decimal_point(jpy));
262 }
263
264 #[test]
265 fn get_decimal_point_returns_some_if_set() {
266 let arena = Bump::new();
267 let mut commodities = CommodityStore::new(&arena);
268 let jpy = commodities.insert("JPY").unwrap();
269 commodities.set_format(jpy, PrettyDecimal::comma3dot(dec!(1.234)));
270
271 assert_eq!(Some(3), commodities.get_decimal_point(jpy));
272 }
273}