|
@@ -4,6 +4,9 @@ use std::collections::HashMap;
|
|
use std::rc::Rc;
|
|
use std::rc::Rc;
|
|
|
|
|
|
use ariadne::Cache;
|
|
use ariadne::Cache;
|
|
|
|
+use chumsky::span::Span as CSpan;
|
|
|
|
+
|
|
|
|
+pub use rust_decimal::Decimal;
|
|
|
|
|
|
pub mod ledger;
|
|
pub mod ledger;
|
|
mod spec;
|
|
mod spec;
|
|
@@ -72,7 +75,7 @@ impl ariadne::Cache<SourceFile> for FilesystemData {
|
|
#[derive(Debug)]
|
|
#[derive(Debug)]
|
|
pub enum DataError {
|
|
pub enum DataError {
|
|
IOError(std::io::Error),
|
|
IOError(std::io::Error),
|
|
- Report(Box<ariadne::Report<'static, (SourceFile, std::ops::Range<usize>)>>),
|
|
|
|
|
|
+ Report(Box<ariadne::Report<'static, Span>>),
|
|
Validation(String),
|
|
Validation(String),
|
|
}
|
|
}
|
|
|
|
|
|
@@ -88,10 +91,16 @@ impl From<std::io::Error> for DataError {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-impl From<ariadne::Report<'static, (SourceFile, std::ops::Range<usize>)>> for DataError {
|
|
|
|
|
|
+/*impl From<ariadne::Report<'static, (SourceFile, std::ops::Range<usize>)>> for DataError {
|
|
fn from(value: ariadne::Report<'static, (SourceFile, std::ops::Range<usize>)>) -> Self {
|
|
fn from(value: ariadne::Report<'static, (SourceFile, std::ops::Range<usize>)>) -> Self {
|
|
Self::Report(value.into())
|
|
Self::Report(value.into())
|
|
}
|
|
}
|
|
|
|
+}*/
|
|
|
|
+
|
|
|
|
+impl From<ariadne::Report<'static, Span>> for DataError {
|
|
|
|
+ fn from(value: ariadne::Report<'static, Span>) -> Self {
|
|
|
|
+ Self::Report(value.into())
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
impl std::error::Error for DataError {}
|
|
impl std::error::Error for DataError {}
|
|
@@ -99,7 +108,7 @@ impl std::error::Error for DataError {}
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
|
pub struct Span {
|
|
pub struct Span {
|
|
range: (usize, usize),
|
|
range: (usize, usize),
|
|
- context: SourceFile
|
|
|
|
|
|
+ context: SourceFile,
|
|
}
|
|
}
|
|
|
|
|
|
impl chumsky::span::Span for Span {
|
|
impl chumsky::span::Span for Span {
|
|
@@ -107,7 +116,10 @@ impl chumsky::span::Span for Span {
|
|
type Context = SourceFile;
|
|
type Context = SourceFile;
|
|
|
|
|
|
fn new(context: Self::Context, range: std::ops::Range<Self::Offset>) -> Self {
|
|
fn new(context: Self::Context, range: std::ops::Range<Self::Offset>) -> Self {
|
|
- Self { context, range: (range.start, range.end) }
|
|
|
|
|
|
+ Self {
|
|
|
|
+ context,
|
|
|
|
+ range: (range.start, range.end),
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
fn start(&self) -> Self::Offset {
|
|
fn start(&self) -> Self::Offset {
|
|
@@ -125,11 +137,24 @@ impl chumsky::span::Span for Span {
|
|
fn to_end(&self) -> Self {
|
|
fn to_end(&self) -> Self {
|
|
Self {
|
|
Self {
|
|
context: self.context,
|
|
context: self.context,
|
|
- range: (self.range.1, self.range.1)
|
|
|
|
|
|
+ range: (self.range.1, self.range.1),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+impl ariadne::Span for Span {
|
|
|
|
+ type SourceId = SourceFile;
|
|
|
|
+ fn source(&self) -> &Self::SourceId {
|
|
|
|
+ &self.context
|
|
|
|
+ }
|
|
|
|
+ fn start(&self) -> usize {
|
|
|
|
+ self.range.0
|
|
|
|
+ }
|
|
|
|
+ fn end(&self) -> usize {
|
|
|
|
+ self.range.1
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
#[derive(Debug, Clone, Copy)]
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct Spanned<T>(pub T, pub Span);
|
|
pub struct Spanned<T>(pub T, pub Span);
|
|
|
|
|
|
@@ -168,7 +193,7 @@ impl<T: PartialEq> PartialEq for Spanned<T> {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-impl<T: Eq> Eq for Spanned<T> { }
|
|
|
|
|
|
+impl<T: Eq> Eq for Spanned<T> {}
|
|
|
|
|
|
impl<T: PartialOrd> PartialOrd for Spanned<T> {
|
|
impl<T: PartialOrd> PartialOrd for Spanned<T> {
|
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
@@ -231,6 +256,8 @@ impl Root {
|
|
r.load_ledgers(fsdata)?;
|
|
r.load_ledgers(fsdata)?;
|
|
r.preprocess_ledger_data();
|
|
r.preprocess_ledger_data();
|
|
|
|
|
|
|
|
+ crate::check::run_checks(&mut r)?;
|
|
|
|
+
|
|
Ok(r)
|
|
Ok(r)
|
|
}
|
|
}
|
|
Err(te) => {
|
|
Err(te) => {
|
|
@@ -238,11 +265,13 @@ impl Root {
|
|
panic!("TOML parse error with no range: {te}");
|
|
panic!("TOML parse error with no range: {te}");
|
|
};
|
|
};
|
|
|
|
|
|
- let report =
|
|
|
|
- ariadne::Report::build(ariadne::ReportKind::Error, (sf, range.clone()))
|
|
|
|
- .with_label(ariadne::Label::new((sf, range)).with_message(te.message()))
|
|
|
|
- .with_message("Failed to parse root TOML")
|
|
|
|
- .finish();
|
|
|
|
|
|
+ let report = ariadne::Report::build(
|
|
|
|
+ ariadne::ReportKind::Error,
|
|
|
|
+ Span::new(sf, range.clone()),
|
|
|
|
+ )
|
|
|
|
+ .with_label(ariadne::Label::new(Span::new(sf, range)).with_message(te.message()))
|
|
|
|
+ .with_message("Failed to parse root TOML")
|
|
|
|
+ .finish();
|
|
|
|
|
|
Err(report.into())
|
|
Err(report.into())
|
|
}
|
|
}
|
|
@@ -309,7 +338,27 @@ impl Root {
|
|
self.account_ledger_data.get(&aname).map(Vec::as_slice)
|
|
self.account_ledger_data.get(&aname).map(Vec::as_slice)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ pub fn account_names(&self) -> impl Iterator<Item = AccountName> {
|
|
|
|
+ self.spec_root.accounts.keys().cloned()
|
|
|
|
+ }
|
|
|
|
+
|
|
pub fn account_spec(&self, aname: AccountName) -> Option<&spec::AccountSpec> {
|
|
pub fn account_spec(&self, aname: AccountName) -> Option<&spec::AccountSpec> {
|
|
self.spec_root.accounts.get(&aname)
|
|
self.spec_root.accounts.get(&aname)
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ pub fn balance(&self, aname: AccountName) -> Option<HashMap<UnitName, Decimal>> {
|
|
|
|
+ let mut running = HashMap::<UnitName, Decimal>::new();
|
|
|
|
+
|
|
|
|
+ for le in self.ledger_data_for(aname)? {
|
|
|
|
+ for b in &le.balances {
|
|
|
|
+ if *b.account != aname {
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ let v = running.entry(*b.unit).or_default();
|
|
|
|
+ *v = v.checked_add(*b.amount)?;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Some(running)
|
|
|
|
+ }
|
|
}
|
|
}
|