NSWL54NMMYELYDQNRFAO7S6GBFDW6MDJ5TM2EYMK2VX336DGACHQC
5S4MZHL5SO3D3M2CNQESORNJ47QCX7X7SPTO3K63VDXMITYMRZBQC
ONRIF4V72HMVLO4BWDHI7ZOWYWTLTVYNH5TXUN5HB7BQUMV22NVAC
SJ6AFVZL5HEXG5ZUV5STIGGIGLU56WGAQCSZBFFHQOVHAILBOS2QC
GUNI4ZUIWBCDRR33NFFAGMHTWDX6BCGLUPAH6MH7SJO373MPNUOQC
QRIJE4AQWN7A7O2CO7FXRV4FXZ5RHONQKRGHYXAD7WSECJYK2MFAC
M7VINXOFAKFTEKQZR3RFO3WJQ3RXE2TWD4YAVKTPLMJWC4LISTIQC
ND7GASJ45SLCZWNTZVAEQOWRF4HQ4GQ7TQLQZ4CUTW2IW3JN776QC
I2P2FTLEKLICJKHQ3FHOLRQRQYGZCJTCTU2MWXU2TMIRIKG6YFCQC
let directives = [Directive::Transaction(Transaction {
date: time::macros::date!(2022 - 03 - 07),
flag: Complete,
payee: None,
narration: None,
postings: vec![
Posting {
flag: None,
account: Account::try_from("Assets:Checking").unwrap(),
amount: Some(Amount {
amount: dec!(-010000.00),
commodity: Commodity::try_from("EUR").unwrap(),
}),
cost: None,
price: None,
},
Posting {
flag: None,
account: Account::try_from("Assets:Wallet").unwrap(),
amount: Some(Amount {
amount: dec!(9000.00),
commodity: Commodity::try_from("EUR").unwrap(),
}),
cost: None,
price: None,
},
Posting {
flag: None,
account: Account::try_from("Expenses:Banking:Fees").unwrap(),
amount: None,
cost: None,
price: None,
},
],
})];
let config = Config::derive_from_directives(&directives);
printer
.print_transaction(&Transaction {
date: time::macros::date!(2022 - 03 - 07),
flag: Complete,
payee: None,
narration: None,
postings: vec![
Posting {
flag: None,
account: Account::try_from("Assets:Checking").unwrap(),
amount: Some(Amount {
amount: dec!(-10000.00),
commodity: Commodity::try_from("EUR").unwrap(),
}),
cost: None,
price: None,
},
Posting {
flag: None,
account: Account::try_from("Assets:Wallet").unwrap(),
amount: Some(Amount {
amount: dec!(9000.00),
commodity: Commodity::try_from("EUR").unwrap(),
}),
cost: None,
price: None,
},
Posting {
flag: None,
account: Account::try_from("Expenses:Banking:Fees").unwrap(),
amount: None,
cost: None,
price: None,
},
],
})
.unwrap();
printer.print_directives(&directives).unwrap();
}
impl AmountConfig {
fn from_metrics(metrics: AmountMetrics) -> Self {
const SIGN_WIDTH: usize = 1;
let AmountMetrics {
magnitude_range,
start_column,
} = metrics;
let magnitude: usize = (*magnitude_range.end()).try_into().unwrap_or_default();
let integral_digits = ((4 * magnitude) / 3) + 1;
let left_width = integral_digits + SIGN_WIDTH;
let decimal_separator_column = start_column + left_width;
let decimals = (-magnitude_range.start()).try_into().unwrap_or(0);
let right_width = decimals + 1;
let commodity_column = decimal_separator_column + right_width + 1;
Self {
decimal_separator_column,
commodity_column,
}
}
}
impl Config {
pub fn derive_from_directives<'d>(
directives: impl IntoParallelIterator<Item = &'d Directive>,
) -> Self {
let DirectiveMetrics {
amount,
posting_flag,
..
} = DirectiveMetrics::derive_from_directives(directives);
let flag_column = (posting_flag as usize) * 2;
let account_column = flag_column + 2;
let amount = AmountConfig::from_metrics(amount);
Self {
account_column,
amount,
flag_column,
}
}
let sign_width = matches!(decimal.sign(), Sign::Negative) as usize;
let magnitude: usize = decimal
.nonzero_magnitude_start()
.try_into()
.unwrap_or_default();
let integral_width = ((4 * magnitude) / 3) + 1;
}
}
#[derive(Clone, Debug)]
struct AmountMetrics {
magnitude_range: RangeInclusive<i16>,
start_column: usize,
}
impl AmountMetrics {
const ZERO: Self = Self::zero();
fn derive(start_column: usize, amount: &Amount) -> Self {
Self::derive_opt(start_column, Some(amount))
}
fn derive_opt(start_column: usize, amount: Option<&Amount>) -> Self {
let magnitude_range = amount.map_or(0..=0, |amount| {
fixed_decimal_from(&amount.amount).magnitude_range()
});
Self {
start_column,
magnitude_range,
}
}
const fn zero() -> Self {
Self {
start_column: 0,
magnitude_range: 0..=0,
}
}
}
impl AmountMetrics {
fn merge(self, other: Self) -> Self {
let start_column = self.start_column.max(other.start_column);
let magnitude_range = merge_ranges(self.magnitude_range, other.magnitude_range);
Self {
start_column,
magnitude_range,
}
}
}
#[derive(Clone, Debug)]
struct DirectiveMetrics {
amount: AmountMetrics,
posting_flag: bool,
}
impl DirectiveMetrics {
const ACCOUNT_AMOUNT_SEPARATION: usize = 2;
const ZERO: Self = Self::zero();
}
impl DirectiveMetrics {
fn derive_from_directives<'d>(
directives: impl IntoParallelIterator<Item = &'d Directive>,
) -> Self {
directives
.into_par_iter()
.flat_map(Self::derive_from_directive)
.reduce(Self::zero, Self::merge)
}
fn derive_from_directive(directive: &Directive) -> impl ParallelIterator<Item = Self> + '_ {
match directive {
Directive::Balance(balance) => {
Either::Left(rayon::iter::once(Self::derive_from_balance(balance)))
}
Directive::Transaction(transaction) => {
Either::Right(Self::derive_from_transaction(transaction))
}
}
}
fn derive_from_balance(balance: &Balance) -> Self {
const PREFIX_WIDTH: usize = "YYYY-MM-DD balance ".len();
let Balance {
account, amount, ..
} = balance;
let amount_start_column =
PREFIX_WIDTH + account_width(account) + Self::ACCOUNT_AMOUNT_SEPARATION;
let amount = AmountMetrics::derive(amount_start_column, amount);
Self {
amount,
..Self::ZERO
}
}
fn derive_from_transaction(
transaction: &Transaction,
) -> impl ParallelIterator<Item = Self> + '_ {
transaction
.postings
.par_iter()
.map(Self::derive_from_posting)
}
fn derive_from_posting(posting: &Posting) -> Self {
const INDENT: usize = 2;
let Posting {
flag,
account,
amount,
..
} = posting;
let posting_flag = flag.is_some();
let amount_start_column = INDENT
+ (2 * posting_flag as usize)
+ account_width(account)
+ Self::ACCOUNT_AMOUNT_SEPARATION;
let amount = AmountMetrics::derive_opt(amount_start_column, amount.as_ref());
Self {
amount,
posting_flag,
}
}
const fn zero() -> Self {
Self {
amount: AmountMetrics::ZERO,
posting_flag: false,
}
}
}
impl DirectiveMetrics {
fn merge(self, other: Self) -> Self {
let amount = self.amount.merge(other.amount);
let posting_flag = self.posting_flag || other.posting_flag;
Self {
amount,
posting_flag,
}
fn merge_ranges(lhs: RangeInclusive<i16>, rhs: RangeInclusive<i16>) -> RangeInclusive<i16> {
let [lhs, rhs] = [lhs, rhs].map(RangeInclusive::into_inner);
let (start, end) = (lhs.0.min(rhs.0), lhs.1.max(rhs.1));
start..=end
}