allow deriving pretty printing config from a set of directives

korrat
Oct 1, 2022, 10:06 AM
NSWL54NMMYELYDQNRFAO7S6GBFDW6MDJ5TM2EYMK2VX336DGACHQC

Dependencies

  • [2] 5S4MZHL5 pretty print decimals using icu
  • [3] SJ6AFVZL remove const configuration in favor of runtime config
  • [4] QRIJE4AQ add a simple pretty printer for beancount directives
  • [5] M7VINXOF use fixed_decimal for decimal formatting
  • [6] ONRIF4V7 add basic snapshot test for pretty printer
  • [7] GUNI4ZUI use larger numbers in test
  • [*] ND7GASJ4 track current column position when writing
  • [*] I2P2FTLE add basic parser for german decimals

Change contents

  • edit in common/beancount-pretty-printer/tests/snapshotting.rs at line 6
    [3.196]
    [3.196]
    use beancount_types::Directive;
  • edit in common/beancount-pretty-printer/tests/snapshotting.rs at line 14
    [3.381]
    [3.381]
    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
    [3.429][3.274:355]()
    let mut printer = PrettyPrinter::unbuffered(Config::default(), &mut buffer);
    [3.429]
    [3.507]
    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();
    [3.508]
    [3.1575]
    printer.print_directives(&directives).unwrap();
  • replacement in common/beancount-pretty-printer/tests/snapshots/snapshotting__pretty_printed_transaction.snap at line 3
    [3.1842][3.357:376]()
    assertion_line: 55
    [3.1842]
    [3.1842]
    assertion_line: 57
  • replacement in common/beancount-pretty-printer/tests/snapshots/snapshotting__pretty_printed_transaction.snap at line 7
    [3.1881][2.0:240]()
    Assets:Checking -10,000.00 EUR
    Assets:Wallet 9,000.00 EUR
    [3.1881]
    [3.617]
    Assets:Checking -10,000.00 EUR
    Assets: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
    [2.262]
    [3.87]
    use beancount_types::Acc;
  • edit in common/beancount-pretty-printer/src/lib.rs at line 16
    [2.353]
    [3.274]
    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
    [3.366]
    [2.354]
    use std::ops::RangeInclusive;
    use unicode_segmentation::UnicodeSegmentation as _;
  • edit in common/beancount-pretty-printer/src/lib.rs at line 33
    [3.747]
    [3.747]
    }
    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
    [3.920]
    [3.420]
    // TODO format cost basis and prices as well
  • edit in common/beancount-pretty-printer/src/lib.rs at line 79
    [3.1062]
    [3.1062]
    }
    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
    [3.2434][3.427:514]()
    .ensure_column(decimal_separator_column - (sign_width + integral_width))?;
    [3.2434]
    [2.860]
    .ensure_column(decimal_separator_column - left_width(&decimal))?;
  • edit in common/beancount-pretty-printer/src/lib.rs at line 276
    [3.5015]
    [3.5015]
    }
    }
    #[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
    [3.556]
    [3.556]
    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
    [3.741]
    [3.741]
    }
    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
    [2.1740]
    [9.1033]
    rayon.workspace = true
  • edit in common/beancount-pretty-printer/Cargo.toml at line 22
    [9.1063]
    [2.1741]
    unicode-segmentation.workspace = true
  • edit in Cargo.toml at line 19
    [2.2039]
    [3.2367]
    rayon = "1.5.3"
  • edit in Cargo.toml at line 22
    [3.2422]
    [2.2040]
    unicode-segmentation = "1.10.0"
  • edit in Cargo.lock at line 144
    [2.4197]
    [3.5565]
    "rayon",
  • edit in Cargo.lock at line 148
    [2.4214]
    [2.4214]
    "unicode-segmentation",
  • edit in Cargo.lock at line 3147
    [2.54091]
    [2.54091]
    [[package]]
    name = "unicode-segmentation"
    version = "1.10.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"