1use std::collections::{HashMap, hash_map};
4
5use bumpalo::Bump;
6use bumpalo_intern::direct::{
7 DirectInternStore, FromInterned, InternedStr, OccupiedError, StoredValue,
8};
9
10#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
13pub struct Account<'arena>(InternedStr<'arena>);
14
15impl<'arena> FromInterned<'arena> for Account<'arena> {
16 fn from_interned(v: InternedStr<'arena>) -> Self {
17 Self(v)
18 }
19
20 fn as_interned(&self) -> InternedStr<'arena> {
21 self.0
22 }
23}
24
25impl<'arena> Account<'arena> {
26 #[inline]
28 pub fn as_str(&self) -> &'arena str {
29 self.0.as_str()
30 }
31}
32
33pub struct AccountStore<'arena> {
35 intern: DirectInternStore<'arena, Account<'arena>>,
37}
38
39impl<'arena> AccountStore<'arena> {
40 pub fn new(arena: &'arena Bump) -> Self {
42 Self {
43 intern: DirectInternStore::new(arena),
44 }
45 }
46
47 pub fn ensure(&mut self, value: &str) -> Account<'arena> {
51 self.intern.ensure(value)
52 }
53
54 pub fn resolve(&self, value: &str) -> Option<Account<'arena>> {
56 self.intern.resolve(value)
57 }
58
59 pub fn register_alias(
62 &mut self,
63 value: &str,
64 canonical: Account<'arena>,
65 ) -> Result<(), OccupiedError<'arena, Account<'arena>>> {
66 self.intern.register_alias(value, canonical)
67 }
68
69 pub fn iter(&self) -> impl Iterator<Item = Account<'arena>> {
72 self.intern.iter().filter_map(|v| match v {
73 StoredValue::Alias { .. } => None,
74 StoredValue::Canonical(a) => Some(a),
75 })
76 }
77}
78
79#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
85pub struct AccountAncestor<'arena>(InternedStr<'arena>);
86
87impl<'arena> FromInterned<'arena> for AccountAncestor<'arena> {
88 fn from_interned(v: InternedStr<'arena>) -> Self {
89 Self(v)
90 }
91
92 fn as_interned(&self) -> InternedStr<'arena> {
93 self.0
94 }
95}
96
97#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
100pub enum AccountAggregate<'arena> {
101 Account(Account<'arena>),
103 Ancestor(AccountAncestor<'arena>),
105}
106
107impl<'arena> From<Account<'arena>> for AccountAggregate<'arena> {
108 fn from(value: Account<'arena>) -> Self {
109 Self::Account(value)
110 }
111}
112
113impl<'arena> From<AccountAncestor<'arena>> for AccountAggregate<'arena> {
114 fn from(value: AccountAncestor<'arena>) -> Self {
115 Self::Ancestor(value)
116 }
117}
118
119impl<'arena> AccountAggregate<'arena> {
120 #[inline]
122 pub fn as_str(&self) -> &'arena str {
123 match self {
124 AccountAggregate::Account(Account(v)) => v.as_str(),
125 AccountAggregate::Ancestor(AccountAncestor(v)) => v.as_str(),
126 }
127 }
128
129 #[inline]
131 pub fn as_account(&self) -> Option<Account<'arena>> {
132 match self {
133 AccountAggregate::Account(a) => Some(*a),
134 AccountAggregate::Ancestor(_) => None,
135 }
136 }
137
138 #[inline]
141 pub fn last_segment(&self) -> &'arena str {
142 let full = self.as_str();
143 full.rsplit_once(':').map_or(full, |(_, leaf)| leaf)
144 }
145}
146
147pub struct AccountTree<'arena> {
149 prefixes: DirectInternStore<'arena, AccountAncestor<'arena>>,
150 root_children: Vec<AccountAggregate<'arena>>,
151 nodes: HashMap<AccountAggregate<'arena>, Node<'arena>>,
152}
153
154impl<'arena> AccountTree<'arena> {
155 pub fn new(arena: &'arena Bump) -> Self {
161 Self {
162 prefixes: DirectInternStore::new(arena),
163 root_children: Vec::new(),
164 nodes: HashMap::new(),
165 }
166 }
167
168 pub fn construct(&mut self, accounts: &AccountStore<'arena>) {
170 self.root_children.clear();
171 self.nodes.clear();
172 for account in accounts.iter() {
173 if self.nodes.contains_key(&account.into()) {
174 continue;
175 }
176 let mut current = AccountAggregate::Account(account);
177 loop {
178 let Some(parent) = self.ensure_parent(accounts, current) else {
179 self.nodes.entry(current).or_insert_with(Node::placeholder);
181 self.root_children.push(current);
182 break;
183 };
184 let child_node = self.nodes.entry(current).or_insert_with(Node::placeholder);
185 child_node.parent = AccountTreeKey::Descendant(parent);
186 let (children, already_exists) = match self.nodes.entry(parent) {
187 hash_map::Entry::Occupied(existing) => {
188 (&mut existing.into_mut().children, true)
189 }
190 hash_map::Entry::Vacant(new) => {
191 (&mut new.insert(Node::placeholder()).children, false)
192 }
193 };
194 children.push(current);
195 if already_exists {
196 break;
197 };
198 current = parent;
199 }
200 }
201 self.root_children.sort_by_key(AccountAggregate::as_str);
202 for node in self.nodes.values_mut() {
203 node.children.sort_by_key(AccountAggregate::as_str);
204 }
205 }
206
207 fn ensure_parent(
210 &mut self,
211 accounts: &AccountStore<'arena>,
212 child: AccountAggregate<'arena>,
213 ) -> Option<AccountAggregate<'arena>> {
214 let (parent, _) = child.as_str().rsplit_once(':')?;
216 Some(match accounts.resolve(parent) {
217 Some(v) => AccountAggregate::Account(v),
218 None => AccountAggregate::Ancestor(self.prefixes.ensure(parent)),
219 })
220 }
221
222 pub fn resolve_ancestor(&self, label: &str) -> Option<AccountAncestor<'arena>> {
227 self.prefixes.resolve(label)
228 }
229
230 pub fn parent<T: Into<AccountAggregate<'arena>>>(
233 &self,
234 account: T,
235 ) -> Option<AccountTreeKey<'arena>> {
236 self.nodes.get(&account.into()).map(|node| node.parent)
237 }
238
239 pub fn children<T: Into<AccountTreeKey<'arena>>>(
243 &self,
244 key: T,
245 ) -> Option<&[AccountAggregate<'arena>]> {
246 match key.into() {
247 AccountTreeKey::Root => Some(&self.root_children),
248 AccountTreeKey::Descendant(account) => {
249 self.nodes.get(&account).map(|n| n.children.as_slice())
250 }
251 }
252 }
253}
254
255#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
257pub enum AccountTreeKey<'arena> {
258 Root,
260 Descendant(AccountAggregate<'arena>),
262}
263
264impl<'arena> AccountTreeKey<'arena> {
265 #[inline]
268 pub fn as_aggregate(self) -> Option<AccountAggregate<'arena>> {
269 match self {
270 Self::Root => None,
271 Self::Descendant(a) => Some(a),
272 }
273 }
274}
275
276impl<'arena> From<Account<'arena>> for AccountTreeKey<'arena> {
277 fn from(value: Account<'arena>) -> Self {
278 Self::Descendant(value.into())
279 }
280}
281
282impl<'arena> From<AccountAncestor<'arena>> for AccountTreeKey<'arena> {
283 fn from(value: AccountAncestor<'arena>) -> Self {
284 Self::Descendant(value.into())
285 }
286}
287
288impl<'arena> From<AccountAggregate<'arena>> for AccountTreeKey<'arena> {
289 fn from(value: AccountAggregate<'arena>) -> Self {
290 Self::Descendant(value)
291 }
292}
293
294#[derive(Debug, Clone)]
296struct Node<'arena> {
297 parent: AccountTreeKey<'arena>,
298 children: Vec<AccountAggregate<'arena>>,
299}
300
301impl<'arena> Node<'arena> {
302 fn placeholder() -> Self {
304 Self {
305 parent: AccountTreeKey::Root,
306 children: Vec::new(),
307 }
308 }
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314
315 use pretty_assertions::assert_eq;
316
317 #[test]
318 fn account_aggregate_last_segment() {
319 let arena = Bump::new();
320 let mut accounts = AccountStore::new(&arena);
321 let mut tree = AccountTree::new(&arena);
322 let grocery = accounts.ensure("Expenses:Grocery");
323 tree.construct(&accounts);
324 let expenses = tree.resolve_ancestor("Expenses").unwrap();
325
326 assert_eq!("Grocery", AccountAggregate::from(grocery).last_segment());
327 assert_eq!("Expenses", AccountAggregate::from(expenses).last_segment());
328 }
329
330 mod account_tree {
331 use super::assert_eq;
332 use super::*;
333
334 #[test]
335 fn init_reuses_previous_value() {
336 let arena = Bump::new();
337 let mut accounts = AccountStore::new(&arena);
338 let mut tree = AccountTree::new(&arena);
339
340 let foo = accounts.ensure("Assets:Foo");
341 tree.construct(&accounts);
342 let assets = tree.parent(AccountAggregate::Account(foo)).unwrap();
343
344 tree.construct(&accounts);
345 let assets2 = tree.parent(AccountAggregate::Account(foo)).unwrap();
346
347 assert_eq!(assets, assets2);
348 }
349
350 #[test]
351 fn empty_tree() {
352 let arena = Bump::new();
353 let accounts = AccountStore::new(&arena);
354 let mut tree = AccountTree::new(&arena);
355
356 tree.construct(&accounts);
357
358 assert!(tree.children(AccountTreeKey::Root).unwrap().is_empty());
359 }
360
361 #[test]
362 fn tree_structure() {
363 let arena = Bump::new();
364 let mut accounts = AccountStore::new(&arena);
365
366 let foo = accounts.ensure("Assets:Banks:Foo");
367 let bar = accounts.ensure("Assets:Banks:Bar");
368 let food = accounts.ensure("Expenses:Food");
369 let food_special = accounts.ensure("Expenses:Food:Special");
370
371 let mut tree = AccountTree::new(&arena);
372 tree.construct(&accounts);
373
374 let assets = tree.resolve_ancestor("Assets").unwrap();
375 let banks = tree.resolve_ancestor("Assets:Banks").unwrap();
376 let expenses = tree.resolve_ancestor("Expenses").unwrap();
377 let parent_of: [(AccountTreeKey, AccountAggregate); _] = [
378 (AccountTreeKey::Root, assets.into()),
379 (AccountTreeKey::Root, expenses.into()),
380 (assets.into(), banks.into()),
381 (banks.into(), foo.into()),
382 (banks.into(), bar.into()),
383 (expenses.into(), food.into()),
384 (food.into(), food_special.into()),
385 ];
386 for (expected, child) in parent_of {
387 assert_eq!(expected, tree.parent(child).unwrap());
388 }
389 let children_of: [(&[AccountAggregate], AccountTreeKey); _] = [
390 (
391 &[
392 AccountAggregate::Ancestor(assets),
393 AccountAggregate::Ancestor(expenses),
394 ],
395 AccountTreeKey::Root,
396 ),
397 (&[AccountAggregate::Ancestor(banks)], assets.into()),
398 (
399 &[
401 AccountAggregate::Account(bar),
402 AccountAggregate::Account(foo),
403 ],
404 banks.into(),
405 ),
406 (&[AccountAggregate::Account(food)], expenses.into()),
407 (&[AccountAggregate::Account(food_special)], food.into()),
408 ];
409 for (expected, parent) in children_of {
410 assert_eq!(expected, tree.children(parent).unwrap());
411 }
412 }
413 }
414}