data.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. use std::collections::HashMap;
  2. use ariadne::Cache;
  3. use chumsky::span::Span as _;
  4. pub use rust_decimal::Decimal;
  5. pub mod ledger;
  6. pub mod spec;
  7. pub struct LocationTag;
  8. impl stringstore::NamespaceTag for LocationTag {
  9. const PREFIX: &'static str = "loc";
  10. }
  11. pub type SourceFile = stringstore::StoredOsString<LocationTag>;
  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, Debug, PartialEq, Hash)]
  23. pub struct DataSource {
  24. file: SourceFile,
  25. range: std::ops::Range<usize>,
  26. }
  27. /// Helper for accessing data on the filesystem
  28. #[derive(Default)]
  29. pub struct FilesystemData {
  30. file_data: HashMap<SourceFile, ariadne::Source<std::rc::Rc<str>>>,
  31. }
  32. impl std::fmt::Debug for FilesystemData {
  33. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  34. f.debug_struct("FilesystemData").finish()
  35. }
  36. }
  37. impl ariadne::Cache<SourceFile> for FilesystemData {
  38. type Storage = std::rc::Rc<str>;
  39. fn fetch(
  40. &mut self,
  41. id: &SourceFile,
  42. ) -> Result<&ariadne::Source<Self::Storage>, impl std::fmt::Debug> {
  43. if !self.file_data.contains_key(id) {
  44. match std::fs::read_to_string(id.as_str()) {
  45. Ok(data) => {
  46. let data: std::rc::Rc<_> = std::rc::Rc::from(data.into_boxed_str());
  47. self.file_data.insert(*id, ariadne::Source::from(data));
  48. }
  49. Err(e) => return Err(e),
  50. }
  51. }
  52. Ok(self.file_data.get(id).unwrap())
  53. }
  54. fn display<'a>(&self, id: &'a SourceFile) -> Option<impl std::fmt::Display + 'a> {
  55. Some(id.as_str().to_string_lossy())
  56. }
  57. }
  58. #[derive(Debug)]
  59. pub enum DataError {
  60. IOError(std::io::Error),
  61. Report(Box<ariadne::Report<'static, Span>>),
  62. Validation(String),
  63. }
  64. impl std::fmt::Display for DataError {
  65. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  66. <Self as std::fmt::Debug>::fmt(self, f)
  67. }
  68. }
  69. impl From<std::io::Error> for DataError {
  70. fn from(value: std::io::Error) -> Self {
  71. Self::IOError(value)
  72. }
  73. }
  74. impl From<ariadne::Report<'static, Span>> for DataError {
  75. fn from(value: ariadne::Report<'static, Span>) -> Self {
  76. Self::Report(value.into())
  77. }
  78. }
  79. impl std::error::Error for DataError {}
  80. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
  81. pub struct Span {
  82. range: (usize, usize),
  83. context: Option<SourceFile>,
  84. }
  85. impl Default for Span {
  86. fn default() -> Self {
  87. Self { range: (0,0), context: None }
  88. }
  89. }
  90. impl chumsky::span::Span for Span {
  91. type Offset = usize;
  92. type Context = SourceFile;
  93. fn new(context: Self::Context, range: std::ops::Range<Self::Offset>) -> Self {
  94. Self {
  95. context: Some(context),
  96. range: (range.start, range.end),
  97. }
  98. }
  99. fn start(&self) -> Self::Offset {
  100. self.range.0
  101. }
  102. fn end(&self) -> Self::Offset {
  103. self.range.1
  104. }
  105. fn context(&self) -> Self::Context {
  106. self.context.unwrap()
  107. }
  108. fn to_end(&self) -> Self {
  109. Self {
  110. context: self.context,
  111. range: (self.range.1, self.range.1),
  112. }
  113. }
  114. }
  115. impl ariadne::Span for Span {
  116. type SourceId = SourceFile;
  117. fn source(&self) -> &Self::SourceId {
  118. self.context.as_ref().unwrap()
  119. }
  120. fn start(&self) -> usize {
  121. self.range.0
  122. }
  123. fn end(&self) -> usize {
  124. self.range.1
  125. }
  126. }
  127. #[derive(Debug, Clone, Copy)]
  128. pub struct Spanned<T>(pub T, pub Span);
  129. impl<T> Spanned<T> {
  130. pub fn new(t: T, span: Span) -> Self {
  131. Self(t, span)
  132. }
  133. pub fn span(&self) -> Span {
  134. self.1
  135. }
  136. }
  137. impl<T> std::ops::Deref for Spanned<T> {
  138. type Target = T;
  139. fn deref(&self) -> &Self::Target {
  140. &self.0
  141. }
  142. }
  143. impl<T> std::ops::DerefMut for Spanned<T> {
  144. fn deref_mut(&mut self) -> &mut Self::Target {
  145. &mut self.0
  146. }
  147. }
  148. impl<T> AsRef<T> for Spanned<T> {
  149. fn as_ref(&self) -> &T {
  150. &self.0
  151. }
  152. }
  153. impl<T: PartialEq> PartialEq for Spanned<T> {
  154. fn eq(&self, other: &Self) -> bool {
  155. self.0.eq(&other.0)
  156. }
  157. }
  158. impl<T: Eq> Eq for Spanned<T> {}
  159. impl<T> From<T> for Spanned<T> {
  160. fn from(value: T) -> Self {
  161. Self(value, Span::default())
  162. }
  163. }
  164. impl<T: PartialOrd> PartialOrd for Spanned<T> {
  165. fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
  166. self.0.partial_cmp(&other.0)
  167. }
  168. }
  169. impl<T: Ord> Ord for Spanned<T> {
  170. fn cmp(&self, other: &Self) -> std::cmp::Ordering {
  171. self.0.cmp(&other.0)
  172. }
  173. }
  174. impl<T: std::fmt::Display> std::fmt::Display for Spanned<T> {
  175. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  176. self.0.fmt(f)
  177. }
  178. }
  179. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
  180. pub struct Datestamp {
  181. pub year: u16,
  182. pub month: u8,
  183. pub day: u8,
  184. }
  185. impl std::fmt::Display for Datestamp {
  186. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  187. write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
  188. }
  189. }
  190. impl std::fmt::Debug for Datestamp {
  191. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  192. write!(f, "Datestamp ({self})")
  193. }
  194. }
  195. #[derive(Debug)]
  196. pub struct Root {
  197. path: std::path::PathBuf,
  198. spec_root: spec::SpecRoot,
  199. ledger_data: Vec<ledger::LedgerEntry>,
  200. account_ledger_data: HashMap<AccountName, Vec<Spanned<ledger::Transaction>>>,
  201. }
  202. impl Root {
  203. pub fn load(fsdata: &mut FilesystemData, path: &std::path::Path) -> Result<Self, DataError> {
  204. let sf = SourceFile::new(path.as_os_str());
  205. let root_data = fsdata.fetch(&sf).unwrap();
  206. match toml::from_str::<spec::SpecRoot>(root_data.text()) {
  207. Ok(mut spec_root) => {
  208. let initial_name = AccountName::from("initial");
  209. if let std::collections::hash_map::Entry::Vacant(ve) =
  210. spec_root.accounts.entry(initial_name)
  211. {
  212. ve.insert(spec::AccountSpec {
  213. title: Some(String::from("initial balances")),
  214. description: None,
  215. annotations: None,
  216. unit: None,
  217. import: None,
  218. });
  219. } else {
  220. return Err(DataError::Validation(String::from(
  221. "cannot define 'initial' account, as it is a built-in",
  222. )));
  223. }
  224. let mut r = Self {
  225. path: path.into(),
  226. spec_root,
  227. ledger_data: vec![],
  228. account_ledger_data: Default::default(),
  229. };
  230. r.load_ledgers(fsdata)?;
  231. r.preprocess_ledger_data();
  232. crate::check::run_checks(&mut r)?;
  233. Ok(r)
  234. }
  235. Err(te) => {
  236. let Some(range) = te.span() else {
  237. panic!("TOML parse error with no range: {te}");
  238. };
  239. let report = ariadne::Report::build(
  240. ariadne::ReportKind::Error,
  241. Span::new(sf, range.clone()),
  242. )
  243. .with_label(ariadne::Label::new(Span::new(sf, range)).with_message(te.message()))
  244. .with_message("Failed to parse root TOML")
  245. .finish();
  246. Err(report.into())
  247. }
  248. }
  249. }
  250. fn load_ledger(
  251. &mut self,
  252. fsdata: &mut FilesystemData,
  253. path: &mut std::path::PathBuf,
  254. ) -> Result<(), DataError> {
  255. log::debug!("Loading ledger data from {}", path.display());
  256. let md = std::fs::metadata(path.as_path()).map_err(DataError::IOError)?;
  257. if md.is_dir() {
  258. // recurse
  259. for de in std::fs::read_dir(path.as_path()).map_err(DataError::IOError)? {
  260. let de = de.map_err(DataError::IOError)?;
  261. path.push(de.file_name());
  262. self.load_ledger(fsdata, path)?;
  263. path.pop();
  264. }
  265. } else {
  266. let s = SourceFile::new(path.as_os_str());
  267. let data = fsdata.fetch(&s).unwrap();
  268. self.ledger_data
  269. .extend(ledger::parse_ledger(s, &self.spec_root, data.text())?);
  270. }
  271. Ok(())
  272. }
  273. fn load_ledgers(&mut self, fsdata: &mut FilesystemData) -> Result<(), DataError> {
  274. let mut ledger_path = self.path.to_owned();
  275. ledger_path.pop();
  276. ledger_path.push(&self.spec_root.ledger_path);
  277. self.load_ledger(fsdata, &mut ledger_path)?;
  278. self.ledger_data.sort();
  279. Ok(())
  280. }
  281. fn preprocess_ledger_data(&mut self) {
  282. for entry in &self.ledger_data {
  283. let ledger::LedgerEntry::Transaction(tx) = &entry else {
  284. continue;
  285. };
  286. for bal in &tx.changes {
  287. self.account_ledger_data
  288. .entry(*bal.account)
  289. .or_default()
  290. .push(tx.clone());
  291. }
  292. }
  293. for txns in self.account_ledger_data.values_mut() {
  294. txns.sort_by_key(|txn| txn.datestamp);
  295. }
  296. }
  297. pub fn all_ledger_data(&self) -> &[ledger::LedgerEntry] {
  298. self.ledger_data.as_slice()
  299. }
  300. pub fn ledger_data_for(&self, aname: AccountName) -> Option<&[Spanned<ledger::Transaction>]> {
  301. self.account_ledger_data.get(&aname).map(Vec::as_slice)
  302. }
  303. pub fn account_names(&self) -> impl Iterator<Item = AccountName> {
  304. self.spec_root.accounts.keys().cloned()
  305. }
  306. pub fn account_spec(&self, aname: AccountName) -> Option<&spec::AccountSpec> {
  307. self.spec_root.accounts.get(&aname)
  308. }
  309. pub fn unit_spec(&self, unit: UnitName) -> Option<&spec::UnitSpec> {
  310. self.spec_root.units.get(&unit)
  311. }
  312. pub fn spec_root(&self) -> &spec::SpecRoot {
  313. &self.spec_root
  314. }
  315. pub fn balance(&self, aname: AccountName) -> Option<HashMap<UnitName, Decimal>> {
  316. let mut running = HashMap::<UnitName, Decimal>::new();
  317. for le in self.ledger_data_for(aname)? {
  318. for b in &le.changes {
  319. if *b.account != aname {
  320. continue;
  321. }
  322. let v = running.entry(*b.unit).or_default();
  323. *v = v.checked_add(*b.amount)?;
  324. }
  325. }
  326. Some(running)
  327. }
  328. }