allow deriving pretty printing config from a set of directives
Dependencies
- [2]
5S4MZHL5pretty print decimals using icu - [3]
SJ6AFVZLremove const configuration in favor of runtime config - [4]
QRIJE4AQadd a simple pretty printer for beancount directives - [5]
M7VINXOFuse fixed_decimal for decimal formatting - [6]
ONRIF4V7add basic snapshot test for pretty printer - [7]
GUNI4ZUIuse larger numbers in test - [*]
ND7GASJ4track current column position when writing - [*]
I2P2FTLEadd basic parser for german decimals
Change contents
- edit in common/beancount-pretty-printer/tests/snapshotting.rs at line 6
use beancount_types::Directive; - edit in common/beancount-pretty-printer/tests/snapshotting.rs at line 14
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); - replacement in common/beancount-pretty-printer/tests/snapshotting.rs at line 52
let mut printer = PrettyPrinter::unbuffered(Config::default(), &mut buffer);let mut printer = PrettyPrinter::unbuffered(config, &mut buffer); - replacement in common/beancount-pretty-printer/tests/snapshotting.rs at line 54[3.508]→[3.508:903](∅→∅),[3.903]→[3.0:49](∅→∅),[3.49]→[3.950:1304](∅→∅),[3.950]→[3.950:1304](∅→∅),[3.1304]→[3.50:97](∅→∅),[3.97]→[3.1350:1530](∅→∅),[3.1350]→[3.1350:1530](∅→∅),[3.1530]→[3.98:356](∅→∅),[3.356]→[3.1530:1575](∅→∅),[3.1530]→[3.1530:1575](∅→∅)
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(); - replacement in common/beancount-pretty-printer/tests/snapshots/snapshotting__pretty_printed_transaction.snap at line 3
assertion_line: 55assertion_line: 57 - replacement in common/beancount-pretty-printer/tests/snapshots/snapshotting__pretty_printed_transaction.snap at line 7
Assets:Checking -10,000.00 EURAssets:Wallet 9,000.00 EURAssets:Checking -10,000.00 EURAssets:Wallet 9,000.00 EUR - edit in common/beancount-pretty-printer/tests/snapshots/snapshotting__pretty_printed_transaction.snap at line 10[3.641]
- edit in common/beancount-pretty-printer/src/lib.rs at line 3
use beancount_types::Acc; - edit in common/beancount-pretty-printer/src/lib.rs at line 16
use rayon::iter::Either;use rayon::prelude::IntoParallelIterator;use rayon::prelude::IntoParallelRefIterator as _;use rayon::prelude::ParallelIterator; - edit in common/beancount-pretty-printer/src/lib.rs at line 24
use std::ops::RangeInclusive;use unicode_segmentation::UnicodeSegmentation as _; - edit in common/beancount-pretty-printer/src/lib.rs at line 33
}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,}} - edit in common/beancount-pretty-printer/src/lib.rs at line 69
// TODO format cost basis and prices as well - edit in common/beancount-pretty-printer/src/lib.rs at line 79
}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,}} - edit in common/beancount-pretty-printer/src/lib.rs at line 246[3.2340]→[3.257:333](∅→∅),[3.333]→[2.667:859](∅→∅),[2.859]→[3.425:426](∅→∅),[3.425]→[3.425:426](∅→∅)
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; - replacement in common/beancount-pretty-printer/src/lib.rs at line 247
.ensure_column(decimal_separator_column - (sign_width + integral_width))?;.ensure_column(decimal_separator_column - left_width(&decimal))?; - edit in common/beancount-pretty-printer/src/lib.rs at line 276
}}#[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,} - edit in common/beancount-pretty-printer/src/lib.rs at line 479
fn account_width(account: &Acc) -> usize {let account: &str = account.as_ref();account.graphemes(true).count()} - edit in common/beancount-pretty-printer/src/lib.rs at line 487
}fn left_width(decimal: &FixedDecimal) -> usize {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;sign_width + integral_width - edit in common/beancount-pretty-printer/src/lib.rs at line 499[3.743]
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} - edit in common/beancount-pretty-printer/Cargo.toml at line 20
rayon.workspace = true - edit in common/beancount-pretty-printer/Cargo.toml at line 22
unicode-segmentation.workspace = true - edit in Cargo.toml at line 19
rayon = "1.5.3" - edit in Cargo.toml at line 22
unicode-segmentation = "1.10.0" - edit in Cargo.lock at line 144
"rayon", - edit in Cargo.lock at line 148
"unicode-segmentation", - edit in Cargo.lock at line 3147
[[package]]name = "unicode-segmentation"version = "1.10.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"