Browse Source

Parse and keep ledger comments for reformatting.

Kestrel 15 hours ago
parent
commit
53b8ad3bad
6 changed files with 84 additions and 47 deletions
  1. 5 4
      src/check.rs
  2. 7 6
      src/data.rs
  3. 24 2
      src/data/ledger.rs
  4. 10 8
      src/data/ledger/parse.rs
  5. 21 14
      src/data/ledger/print.rs
  6. 17 13
      testdata/ledger

+ 5 - 4
src/check.rs

@@ -6,15 +6,16 @@ fn check_equal_sum(root: &Root) -> Result<(), DataError> {
     log::debug!("Checking for equal sums in monounit ledger entries...");
 
     for le in root.all_ledger_data() {
-        let Some(mono) = le.mono_unit() else { continue };
+        let Some(tx) = le.as_transaction() else { continue };
+        let Some(mono) = tx.mono_unit() else { continue };
 
-        let net = le
+        let net = tx
             .balances
             .iter()
             .try_fold(Decimal::ZERO, |acc, b| acc.checked_add(*b.amount));
         if net != Some(Decimal::ZERO) {
-            let report = ariadne::Report::build(ariadne::ReportKind::Error, le.span()).with_labels(
-                le.balances.iter().map(|v| {
+            let report = ariadne::Report::build(ariadne::ReportKind::Error, tx.span()).with_labels(
+                tx.balances.iter().map(|v| {
                     let span = v.amount.span().union(v.unit.span());
                     ariadne::Label::new(span).with_message("balance here")
                 }),

+ 7 - 6
src/data.rs

@@ -216,9 +216,9 @@ pub struct Root {
     path: std::path::PathBuf,
     spec_root: spec::SpecRoot,
 
-    ledger_data: Vec<Spanned<ledger::LedgerEntry>>,
+    ledger_data: Vec<ledger::LedgerEntry>,
 
-    account_ledger_data: HashMap<AccountName, Vec<Spanned<ledger::LedgerEntry>>>,
+    account_ledger_data: HashMap<AccountName, Vec<Spanned<ledger::Transaction>>>,
 }
 
 impl Root {
@@ -319,20 +319,21 @@ impl Root {
 
     fn preprocess_ledger_data(&mut self) {
         for entry in &self.ledger_data {
-            for bal in &entry.balances {
+            let ledger::LedgerEntry::Transaction(tx) = &entry else { continue };
+            for bal in &tx.balances {
                 self.account_ledger_data
                     .entry(*bal.account)
                     .or_default()
-                    .push(entry.clone());
+                    .push(tx.clone());
             }
         }
     }
 
-    pub fn all_ledger_data(&self) -> &[Spanned<ledger::LedgerEntry>] {
+    pub fn all_ledger_data(&self) -> &[ledger::LedgerEntry] {
         self.ledger_data.as_slice()
     }
 
-    pub fn ledger_data_for(&self, aname: AccountName) -> Option<&[Spanned<ledger::LedgerEntry>]> {
+    pub fn ledger_data_for(&self, aname: AccountName) -> Option<&[Spanned<ledger::Transaction>]> {
         self.account_ledger_data.get(&aname).map(Vec::as_slice)
     }
 

+ 24 - 2
src/data/ledger.rs

@@ -16,14 +16,14 @@ pub struct Balance {
 }
 
 #[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord)]
-pub struct LedgerEntry {
+pub struct Transaction {
     pub datestamp: (u16, u8, u8),
     pub title: Option<String>,
     pub annotations: Vec<String>,
     pub balances: Vec<Spanned<Balance>>,
 }
 
-impl LedgerEntry {
+impl Transaction {
     pub fn modifies(&self, account: AccountName) -> bool {
         self.balances.iter().any(|b| b.account.as_ref() == &account)
     }
@@ -60,3 +60,25 @@ impl LedgerEntry {
         it.next().is_none().then_some(*uniq.unit)
     }
 }
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub enum LedgerEntry {
+    Transaction(Spanned<Transaction>),
+    Comment(Spanned<String>),
+}
+
+impl LedgerEntry {
+    pub fn as_transaction(&self) -> Option<&Spanned<Transaction>> {
+        match self {
+            Self::Transaction(tx) => Some(tx),
+            _ => None
+        }
+    }
+
+    pub fn span(&self) -> super::Span {
+        match self {
+            Self::Transaction(ts) => ts.span(),
+            Self::Comment(c) => c.span(),
+        }
+    }
+}

+ 10 - 8
src/data/ledger/parse.rs

@@ -2,7 +2,7 @@ use crate::data::{
     AccountName, DataError, Decimal, SourceFile, Span, Spanned, UnitName, spec::SpecRoot,
 };
 
-use super::{Balance, LedgerEntry};
+use super::{Balance, Transaction, LedgerEntry};
 
 use chumsky::{prelude::*, text::inline_whitespace};
 
@@ -11,7 +11,7 @@ type InputWithContext<'a> = chumsky::input::WithContext<Span, &'a str>;
 fn ledger_parser<'a>() -> impl Parser<
     'a,
     InputWithContext<'a>,
-    Vec<Spanned<LedgerEntry>>,
+    Vec<LedgerEntry>,
     chumsky::extra::Full<
         chumsky::error::Rich<'a, char, Span>,
         chumsky::extra::SimpleState<&'a SpecRoot>,
@@ -101,7 +101,7 @@ fn ledger_parser<'a>() -> impl Parser<
         .then_ignore(mark(']'))
         .map(|v: &str| String::from(v.trim()));
 
-    let entry = group((
+    let transaction = group((
         chumsky::text::whitespace(),
         datestamp,
         mark(':'),
@@ -122,26 +122,28 @@ fn ledger_parser<'a>() -> impl Parser<
     ))
     .map_with(
         |(_, datestamp, _, _, title, _, annotations, balances, _), e| {
-            Spanned::new(
-                LedgerEntry {
+            LedgerEntry::Transaction(Spanned::new(
+                Transaction {
                     datestamp,
                     title: (!title.is_empty()).then_some(title),
                     annotations,
                     balances,
                 },
                 e.span(),
-            )
+            ))
         },
     );
 
-    entry.repeated().collect()
+    let comment = mark('#').ignore_then(none_of("\n").repeated()).padded().to_slice().map_with(|s: &str, e| LedgerEntry::Comment(Spanned::new(s.into(), e.span())));
+
+    (transaction.or(comment)).repeated().collect()
 }
 
 pub fn parse_ledger(
     source: SourceFile,
     spec: &SpecRoot,
     data: &str,
-) -> Result<Vec<Spanned<LedgerEntry>>, DataError> {
+) -> Result<Vec<LedgerEntry>, DataError> {
     let parser = ledger_parser();
 
     let (presult, errors) = parser

+ 21 - 14
src/data/ledger/print.rs

@@ -4,27 +4,27 @@ use itertools::Itertools;
 
 use crate::data::{Root, SourceFile, Span, Spanned};
 
-use super::LedgerEntry;
+use super::{LedgerEntry, Transaction};
 
-fn print_ledger_entry(root: &Root, entry: &LedgerEntry, padding: usize) {
+fn print_transaction(root: &Root, tx: &Transaction, padding: usize) {
     println!(
         "{}-{:02}-{:02}: {}",
-        entry.datestamp.0,
-        entry.datestamp.1,
-        entry.datestamp.2,
-        entry.title.as_deref().unwrap_or("")
+        tx.datestamp.0,
+        tx.datestamp.1,
+        tx.datestamp.2,
+        tx.title.as_deref().unwrap_or("")
     );
 
-    if !entry.annotations.is_empty() {
+    if !tx.annotations.is_empty() {
         println!(
-            "  {}",
-            entry.annotations.iter().map(|a| format!("[{a}]")).join(" ")
+            "    {}",
+            tx.annotations.iter().map(|a| format!("[{a}]")).join(" ")
         );
     }
 
-    let force_show = entry.balances.iter().unique_by(|b| *b.account).count() > 1;
+    let force_show = tx.balances.iter().unique_by(|b| *b.account).count() > 1;
 
-    for bal in &entry.balances {
+    for bal in &tx.balances {
         let spacer = if bal.amount.is_sign_positive() {
             " "
         } else {
@@ -45,14 +45,18 @@ fn print_ledger_entry(root: &Root, entry: &LedgerEntry, padding: usize) {
     println!();
 }
 
-pub fn print_ledger<'l>(root: &Root, entries: impl Iterator<Item = &'l Spanned<LedgerEntry>>) {
+fn print_comment(c: &Spanned<String>) {
+    println!("{c}");
+}
+
+pub fn print_ledger<'l>(root: &Root, entries: impl Iterator<Item = &'l LedgerEntry>) {
     let mut ordering = BTreeMap::<SourceFile, BTreeMap<Span, &LedgerEntry>>::new();
 
     entries.for_each(|e| {
         ordering
             .entry(e.span().context)
             .or_default()
-            .insert(e.span(), e.as_ref());
+            .insert(e.span(), &e);
     });
 
     let Some(padding) = root.spec_root.accounts.keys().map(|k| k.len()).max() else {
@@ -66,7 +70,10 @@ pub fn print_ledger<'l>(root: &Root, entries: impl Iterator<Item = &'l Spanned<L
             std::path::Path::new(filename.as_ref()).display()
         );
         for (_span, le) in entries {
-            print_ledger_entry(root, le, padding);
+            match le {
+                LedgerEntry::Transaction(tx) => print_transaction(root, tx, padding),
+                LedgerEntry::Comment(c) => print_comment(c),
+            }
         }
     }
 }

+ 17 - 13
testdata/ledger

@@ -1,26 +1,30 @@
+==== file testdata/./ledger ====
 2001-01-05: initial balance
- - initial   : -400.00 CAD
- - chequing  : 400.00 CAD
+ - initial    : -400.00 CAD
+ - chequing   :  400.00 CAD
 
 2001-01-07: transfer to savings
- - chequing  : -300.00
- - savings   :  300.00
+ - chequing   : -300.00 CAD
+ - savings    :  300.00 CAD
 
 2001-02-08: test for unusual account name
- - initial   : -4.00 USD
- - a.b/c     : 4.00 USD
+ - initial    : -4.00 USD
+ - a.b/c      :  4.00 USD
 
 2001-02-09: currency conversion
  - savings    : -100.00 CAD
- - savings_usd: 80.00 USD
+ - savings_usd:  80.00 USD
 
 2001-02-10: check for floating-point error
- - initial: -0.0001 CAD
- - chequing: -0.0002 CAD
- - savings: 0.0003 CAD
+ - initial    : -0.0001 CAD
+ - chequing   : -0.0002 CAD
+ - savings    :  0.0003 CAD
 
 2001-02-10: clean up
     [tag] [anothertag]
- - initial: 0.0001 CAD
- - chequing: 0.0002 CAD
- - savings: -0.0003 CAD
+ - initial    :  0.0001 CAD
+ - chequing   :  0.0002 CAD
+ - savings    : -0.0003 CAD
+
+# vim: set filetype=hoard :
+