Browse Source

Parse and re-print transaction annotations.

Kestrel 2 days ago
parent
commit
b96e84946e
4 changed files with 40 additions and 19 deletions
  1. 1 0
      src/data/ledger.rs
  2. 29 11
      src/data/ledger/parse.rs
  3. 9 8
      src/data/ledger/print.rs
  4. 1 0
      testdata/ledger

+ 1 - 0
src/data/ledger.rs

@@ -19,6 +19,7 @@ pub struct Balance {
 pub struct LedgerEntry {
     pub datestamp: (u16, u8, u8),
     pub title: Option<String>,
+    pub annotations: Vec<String>,
     pub balances: Vec<Spanned<Balance>>,
 }
 

+ 29 - 11
src/data/ledger/parse.rs

@@ -38,7 +38,10 @@ fn ledger_parser<'a>() -> impl Parser<
         .try_map(|s: &str, span| {
             Ok(Spanned::new(
                 Decimal::from_str_exact(s.trim()).map_err(|e| {
-                    Rich::custom(span, format!("Failed to parse '{s}' as a decimal number: {e}"))
+                    Rich::custom(
+                        span,
+                        format!("Failed to parse '{s}' as a decimal number: {e}"),
+                    )
                 })?,
                 span,
             ))
@@ -93,6 +96,11 @@ fn ledger_parser<'a>() -> impl Parser<
         }, span))
     });
 
+    let annotation = mark('[')
+        .ignore_then(none_of("]\n\t").repeated().to_slice())
+        .then_ignore(mark(']'))
+        .map(|v: &str| String::from(v.trim()));
+
     let entry = group((
         chumsky::text::whitespace(),
         datestamp,
@@ -102,19 +110,29 @@ fn ledger_parser<'a>() -> impl Parser<
             .repeated()
             .collect::<String>(),
         chumsky::text::newline(),
+        choice((
+            annotation
+                .repeated()
+                .collect()
+                .then_ignore(chumsky::text::newline()),
+            empty().map(|_| vec![]),
+        )),
         balance.repeated().at_least(1).collect(),
         chumsky::text::whitespace(),
     ))
-    .map_with(|(_, datestamp, _, _, title, _, balances, _), e| {
-        Spanned::new(
-            LedgerEntry {
-                datestamp,
-                title: (!title.is_empty()).then_some(title),
-                balances,
-            },
-            e.span(),
-        )
-    });
+    .map_with(
+        |(_, datestamp, _, _, title, _, annotations, balances, _), e| {
+            Spanned::new(
+                LedgerEntry {
+                    datestamp,
+                    title: (!title.is_empty()).then_some(title),
+                    annotations,
+                    balances,
+                },
+                e.span(),
+            )
+        },
+    );
 
     entry.repeated().collect()
 }

+ 9 - 8
src/data/ledger/print.rs

@@ -15,6 +15,13 @@ fn print_ledger_entry(root: &Root, entry: &LedgerEntry, padding: usize) {
         entry.title.as_deref().unwrap_or("")
     );
 
+    if !entry.annotations.is_empty() {
+        println!(
+            "  {}",
+            entry.annotations.iter().map(|a| format!("[{a}]")).join(" ")
+        );
+    }
+
     let force_show = entry.balances.iter().unique_by(|b| *b.account).count() > 1;
 
     for bal in &entry.balances {
@@ -26,17 +33,11 @@ fn print_ledger_entry(root: &Root, entry: &LedgerEntry, padding: usize) {
         if Some(bal.unit.as_ref()) == root.account_spec(*bal.account).unwrap().unit.as_ref()
             && !force_show
         {
-            println!(
-                " - {:padding$}: {spacer}{}",
-                bal.account,
-                bal.amount,
-            );
+            println!(" - {:padding$}: {spacer}{}", bal.account, bal.amount,);
         } else {
             println!(
                 " - {:padding$}: {spacer}{} {}",
-                bal.account,
-                bal.amount,
-                bal.unit,
+                bal.account, bal.amount, bal.unit,
             );
         }
     }

+ 1 - 0
testdata/ledger

@@ -20,6 +20,7 @@
  - savings: 0.0003 CAD
 
 2001-02-10: clean up
+    [tag] [anothertag]
  - initial: 0.0001 CAD
  - chequing: 0.0002 CAD
  - savings: -0.0003 CAD