Skip to main content

okane_core/report/
process.rs

1use std::borrow::Borrow;
2use std::path::PathBuf;
3
4use crate::{load, syntax};
5
6use super::balance::Balance;
7use super::book_keeping::{self, BookKeepError};
8use super::context::ReportContext;
9use super::error::{self, ReportError};
10use super::price_db::PriceRepositoryBuilder;
11use super::query::Ledger;
12use super::transaction::Transaction;
13
14/// Options to control process behavior.
15#[derive(Debug, Default)]
16// TODO: non_exhaustive
17pub struct ProcessOptions {
18    /// Path to the price DB file.
19    pub price_db_path: Option<PathBuf>,
20}
21
22/// Takes the loader, and gives back the all read transactions.
23/// Also returns the computed balance, as a side-artifact.
24/// Usually this needs to be reordered, so just returning a `Vec`.
25pub fn process<'ctx, L, F>(
26    ctx: &mut ReportContext<'ctx>,
27    loader: L,
28    options: &ProcessOptions,
29) -> Result<Ledger<'ctx>, ReportError>
30where
31    L: Borrow<load::Loader<F>>,
32    F: load::FileSystem,
33{
34    let mut accum = ProcessAccumulator::new();
35    loader.borrow().load(|path, pctx, entry| {
36        accum.process(ctx, entry).map_err(|berr| {
37            ReportError::BookKeep(
38                berr,
39                error::ErrorContext::new(
40                    loader.borrow().error_style().clone(),
41                    path.to_owned(),
42                    pctx,
43                ),
44            )
45        })
46    })?;
47    if let Some(price_db_path) = options.price_db_path.as_deref() {
48        accum
49            .price_repos
50            .load_price_db(ctx, loader.borrow().filesystem(), price_db_path)?;
51    }
52    Ok(Ledger {
53        arena: ctx.arena,
54        transactions: accum.txns,
55        date_sorted_txns: None,
56        raw_balance: accum.balance,
57        price_repos: accum.price_repos.build(),
58    })
59}
60
61struct ProcessAccumulator<'ctx> {
62    balance: Balance<'ctx>,
63    txns: Vec<Transaction<'ctx>>,
64    price_repos: PriceRepositoryBuilder<'ctx>,
65}
66
67impl<'ctx> ProcessAccumulator<'ctx> {
68    fn new() -> Self {
69        Self {
70            balance: Balance::default(),
71            txns: Vec::new(),
72            price_repos: PriceRepositoryBuilder::default(),
73        }
74    }
75
76    fn process(
77        &mut self,
78        ctx: &mut ReportContext<'ctx>,
79        entry: &syntax::tracked::LedgerEntry,
80    ) -> Result<(), BookKeepError> {
81        match entry {
82            syntax::LedgerEntry::Txn(txn) => {
83                self.txns.push(book_keeping::add_transaction(
84                    ctx,
85                    &mut self.price_repos,
86                    &mut self.balance,
87                    txn,
88                )?);
89                Ok(())
90            }
91            syntax::LedgerEntry::Account(account) => process_account(ctx, account),
92            syntax::LedgerEntry::Commodity(commodity) => process_commodity(ctx, commodity),
93            _ => Ok(()),
94        }
95    }
96}
97
98pub fn process_account<'ctx>(
99    ctx: &mut ReportContext<'ctx>,
100    account: &syntax::AccountDeclaration<'_>,
101) -> Result<(), BookKeepError> {
102    let canonical = ctx.accounts.ensure(&account.name);
103    for ad in &account.details {
104        if let syntax::AccountDetail::Alias(alias) = ad {
105            ctx.accounts
106                .insert_alias(alias, canonical)
107                .map_err(|_| BookKeepError::InvalidAccountAlias(alias.to_string()))?;
108        }
109    }
110    Ok(())
111}
112
113fn process_commodity<'ctx>(
114    ctx: &mut ReportContext<'ctx>,
115    commodity: &syntax::CommodityDeclaration<'_>,
116) -> Result<(), BookKeepError> {
117    let canonical = ctx.commodities.ensure(&commodity.name);
118    for cd in &commodity.details {
119        match cd {
120            syntax::CommodityDetail::Alias(alias) => {
121                ctx.commodities
122                    .insert_alias(alias, canonical)
123                    .map_err(|_| BookKeepError::InvalidCommodityAlias(alias.to_string()))?;
124            }
125            syntax::CommodityDetail::Format(format_amount) => {
126                ctx.commodities.set_format(canonical, format_amount.value);
127            }
128            _ => {}
129        }
130    }
131    Ok(())
132}