data.rs 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. use std::collections::HashMap;
  2. use itertools::Itertools;
  3. use ariadne::Cache;
  4. use chumsky::span::Span as _;
  5. pub use rust_decimal::Decimal;
  6. use crate::prelude::*;
  7. mod format;
  8. mod parse;
  9. pub mod spec;
  10. pub use format::format_ledger;
  11. pub use parse::parse_ledger;
  12. pub struct UnitTag;
  13. impl stringstore::NamespaceTag for UnitTag {
  14. const PREFIX: &'static str = "unit";
  15. }
  16. pub type UnitName = stringstore::StoredString<UnitTag>;
  17. pub struct AccountTag;
  18. impl stringstore::NamespaceTag for AccountTag {
  19. const PREFIX: &'static str = "acc";
  20. }
  21. pub type AccountName = stringstore::StoredString<AccountTag>;
  22. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
  23. pub struct Datestamp {
  24. pub year: u16,
  25. pub month: u8,
  26. pub day: u8,
  27. }
  28. impl std::fmt::Display for Datestamp {
  29. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  30. write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
  31. }
  32. }
  33. impl std::fmt::Debug for Datestamp {
  34. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  35. write!(f, "Datestamp ({self})")
  36. }
  37. }
  38. #[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq)]
  39. pub struct Change {
  40. pub source: Option<io::Span>,
  41. pub account: Spanned<AccountName>,
  42. pub amount: Spanned<Decimal>,
  43. pub balance: Option<Spanned<Decimal>>,
  44. pub unit: Spanned<UnitName>,
  45. }
  46. #[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord)]
  47. pub struct Transaction {
  48. pub source: Option<io::Span>,
  49. pub datestamp: Datestamp,
  50. pub title: Option<String>,
  51. pub annotations: Vec<String>,
  52. pub changes: Vec<Change>,
  53. }
  54. impl Transaction {
  55. pub fn modifies(&self, account: AccountName) -> bool {
  56. self.changes.iter().any(|b| b.account.as_ref() == &account)
  57. }
  58. pub fn change_for(&self, account: AccountName) -> Option<&Change> {
  59. self.changes.iter().find(|b| b.account.as_ref() == &account)
  60. }
  61. pub fn split_changes(
  62. &self,
  63. account: AccountName,
  64. ) -> Option<(&Change, impl Iterator<Item = &Change>)> {
  65. let index = self
  66. .changes
  67. .iter()
  68. .position(|b| b.account.as_ref() == &account)?;
  69. Some((
  70. &self.changes[index],
  71. self.changes[0..index]
  72. .iter()
  73. .chain(self.changes[index + 1..].iter()),
  74. ))
  75. }
  76. pub fn is_mono_unit(&self) -> bool {
  77. self.changes.iter().unique_by(|b| *b.unit).count() == 1
  78. }
  79. pub fn mono_unit(&self) -> Option<UnitName> {
  80. let mut it = self.changes.iter().unique_by(|b| *b.unit);
  81. let uniq = it.next()?;
  82. it.next().is_none().then_some(*uniq.unit)
  83. }
  84. pub fn get_annotation(&self, label: &str) -> Option<&str> {
  85. for anno in self.annotations.iter() {
  86. if let Some(body) = anno.strip_prefix(label) {
  87. return Some(body);
  88. }
  89. }
  90. None
  91. }
  92. }
  93. pub type TransactionRef = std::rc::Rc<std::cell::RefCell<Transaction>>;
  94. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
  95. enum RawLedgerEntry {
  96. Transaction(Transaction),
  97. Comment(Spanned<String>),
  98. }
  99. impl RawLedgerEntry {
  100. pub fn as_transaction(&self) -> Option<&Transaction> {
  101. match self {
  102. Self::Transaction(tx) => Some(tx),
  103. _ => None,
  104. }
  105. }
  106. pub fn as_transaction_mut(&mut self) -> Option<&mut Transaction> {
  107. match self {
  108. Self::Transaction(tx) => Some(tx),
  109. _ => None,
  110. }
  111. }
  112. pub fn span(&self) -> io::Span {
  113. match self {
  114. Self::Transaction(ts) => ts.source.unwrap_or_default(),
  115. Self::Comment(c) => c.span(),
  116. }
  117. }
  118. }
  119. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
  120. pub enum LedgerEntry {
  121. Transaction(TransactionRef),
  122. Comment(Spanned<String>),
  123. }
  124. impl LedgerEntry {
  125. pub fn as_transaction(&self) -> Option<&TransactionRef> {
  126. match self {
  127. Self::Transaction(tx) => Some(tx),
  128. _ => None,
  129. }
  130. }
  131. pub fn as_transaction_mut(&mut self) -> Option<&mut Transaction> {
  132. match self {
  133. Self::Transaction(tx) => Some(tx),
  134. _ => None,
  135. }
  136. }
  137. pub fn span(&self) -> io::Span {
  138. match self {
  139. Self::Transaction(ts) => ts.source.unwrap_or_default(),
  140. Self::Comment(c) => c.span(),
  141. }
  142. }
  143. }
  144. #[derive(Debug)]
  145. pub struct Hoard {
  146. path: std::path::PathBuf,
  147. spec_root: spec::SpecRoot,
  148. comments: Vec<Spanned<String>>,
  149. raw_ledger_data: Vec<RawLedgerEntry>,
  150. account_ledger_data: HashMap<AccountName, Vec<Transaction>>,
  151. }
  152. impl Hoard {
  153. pub fn load(
  154. fsdata: &mut io::FilesystemData,
  155. path: &std::path::Path,
  156. check_level: check::CheckLevel,
  157. ) -> Result<Self, DataError> {
  158. let sf = io::SourceFile::new(path.as_os_str());
  159. let root_data = fsdata.fetch(&sf).unwrap();
  160. match toml::from_str::<spec::SpecRoot>(root_data.text()) {
  161. Ok(spec_root) => {
  162. let mut r = Self {
  163. path: path.into(),
  164. spec_root,
  165. raw_ledger_data: vec![],
  166. comments: vec![],
  167. account_ledger_data: Default::default(),
  168. };
  169. r.load_ledgers(fsdata)?;
  170. r.preprocess_ledger_data();
  171. crate::check::run_checks(&mut r, check_level)?;
  172. Ok(r)
  173. }
  174. Err(te) => {
  175. let Some(range) = te.span() else {
  176. panic!("TOML parse error with no range: {te}");
  177. };
  178. let report = ariadne::Report::build(
  179. ariadne::ReportKind::Error,
  180. io::Span::new(sf, range.clone()),
  181. )
  182. .with_label(
  183. ariadne::Label::new(io::Span::new(sf, range)).with_message(te.message()),
  184. )
  185. .with_message("Failed to parse root TOML")
  186. .finish();
  187. Err(report.into())
  188. }
  189. }
  190. }
  191. fn load_ledger(
  192. &mut self,
  193. fsdata: &mut io::FilesystemData,
  194. path: &mut std::path::PathBuf,
  195. ) -> Result<(), DataError> {
  196. log::debug!("Loading ledger data from {}", path.display());
  197. let md = std::fs::metadata(path.as_path()).map_err(DataError::IOError)?;
  198. if md.is_dir() {
  199. // recurse
  200. for de in std::fs::read_dir(path.as_path()).map_err(DataError::IOError)? {
  201. let de = de.map_err(DataError::IOError)?;
  202. path.push(de.file_name());
  203. self.load_ledger(fsdata, path)?;
  204. path.pop();
  205. }
  206. } else {
  207. let path = std::fs::canonicalize(path)?;
  208. let Some(filename) = path.file_name() else {
  209. return Ok(());
  210. };
  211. // skip filenames beginning with a dot
  212. if filename.as_encoded_bytes()[0] == b'.' {
  213. log::info!("Skipping file {}", path.display());
  214. return Ok(());
  215. }
  216. let sf = io::SourceFile::new_from_string(path.into_os_string());
  217. if let Ok(data) = fsdata.fetch(&sf) {
  218. self.raw_ledger_data
  219. .extend(parse_ledger(sf, &self.spec_root, data.text())?);
  220. } else {
  221. log::error!(
  222. "Failed to load data from {}",
  223. std::path::Path::new(sf.as_str()).display()
  224. );
  225. }
  226. }
  227. Ok(())
  228. }
  229. fn load_ledgers(&mut self, fsdata: &mut io::FilesystemData) -> Result<(), DataError> {
  230. let mut ledger_path = std::fs::canonicalize(self.path.as_path())?;
  231. ledger_path.pop();
  232. ledger_path.push(&self.spec_root.ledger_path);
  233. let mut ledger_path = std::fs::canonicalize(ledger_path)?;
  234. self.load_ledger(fsdata, &mut ledger_path)?;
  235. Ok(())
  236. }
  237. fn preprocess_ledger_data(&mut self) {
  238. for entry in &self.raw_ledger_data {
  239. let RawLedgerEntry::Transaction(tx) = &entry else {
  240. continue;
  241. };
  242. for bal in &tx.changes {
  243. self.account_ledger_data
  244. .entry(*bal.account)
  245. .or_default()
  246. .push(tx.clone());
  247. }
  248. }
  249. }
  250. pub fn all_ledger_data(&self) -> &[LedgerEntry] {
  251. self.ledger_data.as_slice()
  252. }
  253. pub fn all_ledger_data_mut(&mut self) -> &mut [LedgerEntry] {
  254. self.ledger_data.as_mut_slice()
  255. }
  256. pub fn ledger_data_for(&self, aname: AccountName) -> Option<&[Transaction]> {
  257. self.account_ledger_data.get(&aname).map(Vec::as_slice)
  258. }
  259. pub fn ledger_data_for_mut(
  260. &mut self,
  261. aname: AccountName,
  262. ) -> Option<&mut [Transaction]> {
  263. self.account_ledger_data
  264. .get_mut(&aname)
  265. .map(Vec::as_mut_slice)
  266. }
  267. pub fn ledger_data_from(&self, source: io::SourceFile) -> impl Iterator<Item = &LedgerEntry> {
  268. self.all_ledger_data()
  269. .iter()
  270. .filter(move |le| le.span().context == Some(source))
  271. }
  272. pub fn account_names(&self) -> impl Iterator<Item = AccountName> {
  273. self.spec_root.accounts.keys().cloned()
  274. }
  275. pub fn account_spec(&self, aname: AccountName) -> Option<&spec::AccountSpec> {
  276. self.spec_root.accounts.get(&aname)
  277. }
  278. pub fn unit_spec(&self, unit: UnitName) -> Option<&spec::UnitSpec> {
  279. self.spec_root.units.get(&unit)
  280. }
  281. pub fn spec_root(&self) -> &spec::SpecRoot {
  282. &self.spec_root
  283. }
  284. pub fn balance(&self, aname: AccountName) -> Option<HashMap<UnitName, Decimal>> {
  285. let mut running = HashMap::<UnitName, Decimal>::new();
  286. for le in self.ledger_data_for(aname)? {
  287. for b in &le.changes {
  288. if *b.account != aname {
  289. continue;
  290. }
  291. let v = running.entry(*b.unit).or_default();
  292. *v = v.checked_add(*b.amount)?;
  293. }
  294. }
  295. Some(running)
  296. }
  297. }