Skip to main content

okane_core/report/
context.rs

1use bumpalo::Bump;
2
3use crate::report::AccountAggregate;
4
5use super::account::{Account, AccountStore, AccountTree};
6use super::commodity::{CommodityStore, CommodityTag};
7
8/// Context object extensively used across Ledger file evaluation.
9pub struct ReportContext<'ctx> {
10    pub(super) arena: &'ctx Bump,
11    pub(super) accounts: AccountStore<'ctx>,
12    pub(super) account_tree: AccountTree<'ctx>,
13    pub(super) commodities: CommodityStore<'ctx>,
14}
15
16impl<'ctx> ReportContext<'ctx> {
17    /// Create a new instance of `ReportContext`.
18    pub fn new(arena: &'ctx Bump) -> Self {
19        let accounts = AccountStore::new(arena);
20        let account_tree = AccountTree::new(arena);
21        let commodities = CommodityStore::new(arena);
22        Self {
23            arena,
24            accounts,
25            account_tree,
26            commodities,
27        }
28    }
29
30    /// Returns all accounts, sorted as string order.
31    pub(super) fn all_accounts_unsorted(&self) -> impl Iterator<Item = Account<'ctx>> + '_ {
32        self.accounts.iter()
33    }
34    /// Returns all accounts, sorted as string order.
35    pub(super) fn all_accounts(&self) -> Vec<Account<'ctx>> {
36        let mut r: Vec<Account<'ctx>> = self.all_accounts_unsorted().collect();
37        r.sort_unstable_by_key(|x| x.as_str());
38        r
39    }
40
41    /// Returns the given account, or `None` if not found.
42    #[inline]
43    pub fn account(&self, value: &str) -> Option<Account<'ctx>> {
44        self.accounts.resolve(value)
45    }
46
47    /// Returns the account aggregate, or `None` if not found.
48    #[inline]
49    pub fn account_aggregate(&self, value: &str) -> Option<AccountAggregate<'ctx>> {
50        match self.account(value) {
51            Some(v) => Some(AccountAggregate::Account(v)),
52            None => self
53                .account_tree
54                .resolve_ancestor(value)
55                .map(AccountAggregate::Ancestor),
56        }
57    }
58
59    /// Returns the given commmodity, or `None` if not found.
60    #[inline]
61    pub fn commodity(&self, value: &str) -> Option<CommodityTag<'ctx>> {
62        self.commodities.resolve(value)
63    }
64
65    /// Returns [`CommodityStore`].
66    #[inline]
67    pub fn commodity_store(&self) -> &CommodityStore<'ctx> {
68        &self.commodities
69    }
70    /// Returns mut [`CommodityStore`].
71    #[inline]
72    pub fn commodity_store_mut(&mut self) -> &mut CommodityStore<'ctx> {
73        &mut self.commodities
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use pretty_assertions::assert_eq;
80
81    use super::*;
82
83    #[test]
84    fn context_all_accounts() {
85        let arena = Bump::new();
86        let mut ctx = ReportContext::new(&arena);
87        let want = vec![
88            ctx.accounts.ensure("Account 1"),
89            ctx.accounts.ensure("Account 2"),
90            ctx.accounts.ensure("Account 2:Sub account"),
91            ctx.accounts.ensure("Account 3"),
92            // I don't think ordering in Japanese doesn't make sense a lot without 'yomi' information.
93            // OTOH, I don't have plan to use Japanese label, thus no urgent priorities.
94            ctx.accounts.ensure("資産:りんご"),
95            ctx.accounts.ensure("資産:バナナ"),
96            ctx.accounts.ensure("資産:吉祥寺"),
97        ];
98
99        let got = ctx.all_accounts();
100
101        assert_eq!(want, got);
102    }
103
104    #[test]
105    fn context_sccount() {
106        let arena = Bump::new();
107        let mut ctx = ReportContext::new(&arena);
108        let a1 = ctx.accounts.ensure("Account 1");
109        let a3 = ctx.accounts.ensure("Account 3");
110
111        assert_eq!(Some(a1), ctx.account("Account 1"));
112        assert_eq!(None, ctx.account("Account 2"));
113        assert_eq!(Some(a3), ctx.account("Account 3"));
114    }
115}