瀏覽代碼

Change default_unit to unit in account spec.

Kestrel 2 天之前
父節點
當前提交
9ccc65b0c5
共有 9 個文件被更改,包括 53 次插入14 次删除
  1. 1 0
      Cargo.toml
  2. 5 0
      src/check.rs
  3. 2 2
      src/data.rs
  4. 18 2
      src/data/ledger/parse.rs
  5. 15 5
      src/data/ledger/print.rs
  6. 1 1
      src/data/spec.rs
  7. 1 0
      src/main.rs
  8. 5 1
      testdata/ledger
  9. 5 3
      testdata/root.toml

+ 1 - 0
Cargo.toml

@@ -9,6 +9,7 @@ log = { version = "0.4.0" }
 anyhow = { version = "1" }
 stringstore = { version = "0.1.5", features = ["serde"], path = "../stringstore/" }
 rust_decimal = { version = "1.37" }
+itertools = { version = "0.14" }
 
 # i/o dependencies
 serde = { version = "1.0", features = ["derive"] }

+ 5 - 0
src/check.rs

@@ -0,0 +1,5 @@
+use crate::data::{DataError, Root};
+
+pub fn run_checks(root: &Root) -> Result<(), DataError> {
+    Ok(())
+}

+ 2 - 2
src/data.rs

@@ -210,10 +210,10 @@ impl Root {
                     spec_root.accounts.entry(initial_name)
                 {
                     ve.insert(spec::AccountSpec {
-                        title: None,
+                        title: Some(String::from("initial balances")),
                         description: None,
                         annotations: None,
-                        default_unit: None,
+                        unit: None,
                     });
                 } else {
                     return Err(DataError::Validation(String::from(

+ 18 - 2
src/data/ledger/parse.rs

@@ -98,11 +98,27 @@ fn ledger_parser<'a>() -> impl Parser<
             return Err(chumsky::error::Rich::custom(acc.span(), "no such account"));
         };
 
-        let unit = match unit {
+        let unit = match (unit, acc_spec.unit) {
+            (None, None) => {
+                return Err(chumsky::error::Rich::custom(span, "account does not have a unit specified, so all transactions must specify units"))
+            }
+            (Some(unit), None) => unit,
+            (None, Some(unit)) => Spanned(unit, span),
+            (Some(unit1), Some(unit2)) => {
+                if *unit1 != unit2 {
+                    return Err(chumsky::error::Rich::custom(span, "unit mismatch between account and transaction"))
+                } else {
+                    unit1
+                }
+            }
+
+        };
+
+        /*let unit = match unit {
             Some(sunit) => sunit,
             None => acc_spec.default_unit.map(|u| Spanned(u, span)).ok_or_else(||
                 chumsky::error::Rich::custom(span, format!("No unit specified and no default unit specified for account '{}'", acc.as_ref())))?
-        };
+        };*/
 
         if !spec.units.contains_key(&unit) {
             return Err(chumsky::error::Rich::custom(unit.span(), format!("no such unit '{unit}' found")))

+ 15 - 5
src/data/ledger/print.rs

@@ -1,17 +1,22 @@
 use std::collections::BTreeMap;
 
+use itertools::Itertools;
+
 use crate::data::{SourceFile, Spanned, Span, Root};
 
 use super::{Balance, LedgerEntry};
 
-fn print_ledger_entry(root: &Root, entry: &LedgerEntry) {
+fn print_ledger_entry(root: &Root, entry: &LedgerEntry, padding: usize) {
     println!("{}-{:02}-{:02}: {}", entry.datestamp.0, entry.datestamp.1, entry.datestamp.2, entry.title.as_ref().map(String::as_str).unwrap_or(""));
+
+    let force_show = entry.balances.iter().unique_by(|b| *b.account).count() > 1;
+
     for bal in &entry.balances {
         let spacer = if bal.amount.is_sign_positive() { " " } else { "" };
-        if Some(bal.unit.as_ref()) == root.account_spec(*bal.account).unwrap().default_unit.as_ref() {
-            println!(" - {:10}: {spacer}{}", bal.account, bal.amount);
+        if Some(bal.unit.as_ref()) == root.account_spec(*bal.account).unwrap().unit.as_ref() && !force_show {
+            println!(" - {:padding$}: {spacer}{}", bal.account, bal.amount, padding = padding);
         } else {
-            println!(" - {:10}: {spacer}{} {}", bal.account, bal.amount, bal.unit);
+            println!(" - {:padding$}: {spacer}{} {}", bal.account, bal.amount, bal.unit, padding = padding);
         }
     }
     // empty line afterwards
@@ -26,10 +31,15 @@ pub fn print_ledger<'l>(root: &Root, entries: impl Iterator<Item = &'l Spanned<L
         ordering.entry(e.span().context).or_default().insert(e.span(), e.as_ref());
     });
 
+    let Some(padding) = root.spec_root.accounts.keys().map(|k| k.len()).max() else {
+        // no accounts
+        return
+    };
+
     for (filename, entries) in ordering {
         println!("==== file {} ====", std::path::Path::new(filename.as_ref()).display());
         for (_span, le) in entries {
-            print_ledger_entry(root, le);
+            print_ledger_entry(root, le, padding);
         }
     }
 }

+ 1 - 1
src/data/spec.rs

@@ -10,7 +10,7 @@ pub struct AccountSpec {
 
     pub annotations: Option<HashMap<String, String>>,
 
-    pub default_unit: Option<UnitName>,
+    pub unit: Option<UnitName>,
 }
 
 #[derive(Debug, serde::Deserialize)]

+ 1 - 0
src/main.rs

@@ -1,4 +1,5 @@
 mod data;
+mod check;
 
 #[derive(clap::Parser)]
 struct Invocation {

+ 5 - 1
testdata/ledger

@@ -6,7 +6,11 @@
  - chequing  : -300.00 CAD
  - savings   : 300.00 CAD
 
-2001-02-07: test for unusual account name
+2001-02-08: test for unusual account name
  - initial   : -4.00 USD
  - a.b/c     : 4.00 USD
 
+2001-02-09: currency conversion
+ - savings    : -100.00 CAD
+ - savings_usd: 80.00 USD
+

+ 5 - 3
testdata/root.toml

@@ -8,13 +8,15 @@ USD = { name = "United States Dollar", format = "US$" }
 title = "Chequing"
 description = ""
 annotations = { abc = "def" }
-default_unit = "CAD"
+unit = "CAD"
 
 [accounts.savings]
-default_unit = "CAD"
+unit = "CAD"
+
+[accounts.savings_usd]
+unit = "USD"
 
 [accounts.loan]
-default_unit = "CAD"
 
 [accounts."a.b/c"]