1use crate::parse::Parser;
2pub(crate) use chrono::NaiveDate;
3use getset::{CopyGetters, Getters};
4use rust_decimal::Decimal;
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7use std::collections::{HashMap, HashSet};
8use std::convert::From;
9use std::fmt;
10use std::ops::{Div, Mul};
11use std::sync::Arc;
12
13#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
16pub struct Location {
17 pub line: usize,
18 pub col: usize,
19}
20
21impl Location {
22 pub fn advance(&self, width: usize) -> Self {
23 Location {
24 col: self.col + width,
25 line: self.line,
26 }
27 }
28}
29
30impl From<(usize, usize)> for Location {
31 fn from(tuple: (usize, usize)) -> Self {
32 Location {
33 line: tuple.0,
34 col: tuple.1,
35 }
36 }
37}
38
39pub type SrcFile = Arc<String>;
42
43#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
46#[derive(Debug, Clone, PartialEq, Eq, Hash)]
47pub struct Source {
48 pub file: SrcFile,
49 pub start: Location,
50 pub end: Location,
51}
52
53impl fmt::Display for Source {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 write!(f, "{}:{}:{}", self.file, self.start.line, self.start.col)
56 }
57}
58
59#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
63pub enum ErrorType {
64 Io,
66 Syntax,
68 NotBalanced,
70 Incomplete,
73 Account,
75 NoMatch,
78 Ambiguous,
81 Duplicate,
83}
84
85#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
88#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
89pub enum ErrorLevel {
90 Info,
91 Warning,
92 Error,
93}
94#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
96#[derive(Debug, Clone, PartialEq, Eq, Hash)]
97pub struct Error {
98 pub msg: String,
99 pub src: Source,
100 pub r#type: ErrorType,
101 pub level: ErrorLevel,
102}
103
104impl fmt::Display for Error {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 write!(
107 f,
108 "{:?}: {}\n {}:{}:{}",
109 self.level, self.msg, self.src.file, self.src.start.line, self.src.start.col
110 )
111 }
112}
113
114pub type Currency = String;
115
116#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
118#[derive(Debug, Clone, PartialEq, Eq, Hash)]
119pub struct Amount {
120 pub number: Decimal,
121 pub currency: Currency,
122}
123
124impl fmt::Display for Amount {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 write!(f, "{} {}", self.number, self.currency)
127 }
128}
129
130impl<'a> Div<Decimal> for &'a Amount {
131 type Output = Amount;
132
133 fn div(self, rhs: Decimal) -> Self::Output {
134 Amount {
135 number: self.number / rhs,
136 currency: self.currency.clone(),
137 }
138 }
139}
140
141impl Div<Decimal> for Amount {
142 type Output = Amount;
143
144 fn div(self, rhs: Decimal) -> Self::Output {
145 Amount {
146 number: self.number / rhs,
147 currency: self.currency,
148 }
149 }
150}
151
152impl<'a> Mul<Decimal> for &'a Amount {
153 type Output = Amount;
154
155 fn mul(self, rhs: Decimal) -> Self::Output {
156 Amount {
157 number: self.number * rhs,
158 currency: self.currency.clone(),
159 }
160 }
161}
162
163pub type Price = Amount;
165
166#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
169#[derive(Debug, Clone, PartialEq, Eq, Hash)]
170pub struct UnitCost {
171 pub amount: Amount,
173 pub date: NaiveDate,
175}
176
177impl fmt::Display for UnitCost {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 write!(f, "{{ {}, {} }}", self.amount, self.date)
180 }
181}
182
183#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
185#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
186pub enum TxnFlag {
187 Pending,
189 Posted,
191 Pad,
193 Balance,
195}
196
197impl fmt::Display for TxnFlag {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 match self {
200 TxnFlag::Pending => write!(f, "!"),
201 TxnFlag::Posted | TxnFlag::Pad => write!(f, "*"),
202 TxnFlag::Balance => write!(f, "balance"),
203 }
204 }
205}
206
207pub type Account = Arc<String>;
210
211#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
213#[derive(Debug, Clone, PartialEq, Eq)]
214pub struct Posting {
215 pub account: Account,
216 pub amount: Amount,
217 pub cost: Option<UnitCost>,
218 pub price: Option<Price>,
219 pub meta: Meta,
220 pub src: Source,
221}
222
223impl fmt::Display for Posting {
224 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225 let num_str = self.amount.to_string();
226 let index = num_str.find(|c| c == ' ' || c == '.').unwrap();
227 let width = f.width().unwrap_or(46) - 1;
228 let account_width = std::cmp::max(self.account.len() + 1, width - index);
229 write!(
230 f,
231 "{:width$}{}",
232 self.account,
233 num_str,
234 width = account_width
235 )?;
236 if let Some(cost) = &self.cost {
237 write!(f, " {}", cost)?;
238 }
239 if let Some(ref price) = self.price {
240 write!(f, " {}", price)?;
241 }
242 Ok(())
243 }
244}
245
246pub type Payee = String;
247pub type Narration = String;
248pub type Link = String;
249pub type Tag = String;
250
251#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
254#[derive(Debug, Clone, PartialEq, Eq, Getters, CopyGetters)]
255pub struct Transaction {
256 #[getset(get_copy = "pub")]
258 pub(crate) date: NaiveDate,
259
260 #[getset(get_copy = "pub")]
262 pub(crate) flag: TxnFlag,
263
264 #[getset(get = "pub")]
266 pub(crate) payee: Payee,
267
268 #[getset(get = "pub")]
270 pub(crate) narration: Narration,
271
272 #[getset(get = "pub")]
274 pub(crate) links: Vec<Link>,
275
276 #[getset(get = "pub")]
278 pub(crate) tags: Vec<Tag>,
279
280 #[getset(get = "pub")]
282 pub(crate) meta: Meta,
283
284 #[getset(get = "pub")]
286 pub(crate) postings: Vec<Posting>,
287
288 #[getset(get = "pub")]
290 pub(crate) src: Source,
291}
292
293#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
295#[derive(Debug, Clone, PartialEq, Eq, Hash)]
296pub struct AccountNote {
297 pub date: NaiveDate,
298 pub val: String,
299 pub src: Source,
300}
301
302pub type AccountDoc = AccountNote;
304
305pub type Meta = HashMap<String, (String, Source)>;
307
308#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
310#[derive(Debug, Clone, PartialEq, Eq, Getters)]
311pub struct AccountInfo {
312 #[getset(get = "pub")]
314 pub(crate) open: (NaiveDate, Source),
315
316 #[getset(get = "pub")]
318 pub(crate) close: Option<(NaiveDate, Source)>,
319
320 #[getset(get = "pub")]
323 pub(crate) currencies: HashSet<Currency>,
324
325 #[getset(get = "pub")]
327 pub(crate) notes: Vec<AccountNote>,
328
329 #[getset(get = "pub")]
331 pub(crate) docs: Vec<AccountDoc>,
332
333 #[getset(get = "pub")]
335 pub(crate) meta: Meta,
336}
337
338#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
340#[derive(Debug, Clone, PartialEq, Eq, Hash)]
341pub struct EventInfo {
342 pub date: NaiveDate,
343 pub desc: String,
344 pub src: Source,
345}
346
347impl From<(NaiveDate, String, Source)> for EventInfo {
348 fn from(tuple: (NaiveDate, String, Source)) -> Self {
349 EventInfo {
350 date: tuple.0,
351 desc: tuple.1,
352 src: tuple.2,
353 }
354 }
355}
356
357pub type BalanceSheet = HashMap<Account, HashMap<Currency, HashMap<Option<UnitCost>, Decimal>>>;
359
360#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
363#[derive(Debug, Clone, PartialEq, Eq, Getters)]
364pub struct Ledger {
365 #[getset(get = "pub")]
367 pub(crate) accounts: HashMap<Account, AccountInfo>,
368 #[getset(get = "pub")]
370 pub(crate) commodities: HashMap<Currency, (Meta, Source)>,
371 #[getset(get = "pub")]
374 pub(crate) txns: Vec<Transaction>,
375 #[getset(get = "pub")]
377 pub(crate) options: HashMap<String, (String, Source)>,
378 #[getset(get = "pub")]
380 pub(crate) events: HashMap<String, Vec<EventInfo>>,
381 #[getset(get = "pub")]
383 pub(crate) files: Vec<SrcFile>,
384 #[getset(get = "pub")]
386 pub(crate) balance_sheet: BalanceSheet,
387}
388
389impl Ledger {
390 pub fn from_file(path: &str) -> (Self, Vec<Error>) {
391 let (draft, mut errors) = Parser::parse(path);
392 let (ledger, more_errors) = draft.into_ledger();
393 errors.extend(more_errors);
394 (ledger, errors)
395 }
396}
397
398impl fmt::Display for Transaction {
399 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
400 match self.flag {
401 TxnFlag::Balance => write!(f, "{} {}", self.date, self.flag)?,
402 _ => write!(
403 f,
404 "{} {} \"{}\" \"{}\"",
405 self.date, self.flag, self.payee, self.narration
406 )?,
407 };
408 for tag in &self.tags {
409 write!(f, " {}", tag)?;
410 }
411 for link in &self.links {
412 write!(f, " {}", link)?;
413 }
414 for (key, val) in self.meta.iter() {
415 write!(f, "\n {}: {}", key, val.0)?;
416 }
417 let width = f.width().unwrap_or(50);
418 match self.flag {
419 TxnFlag::Balance => {
420 if self.postings.len() == 1 {
421 write!(f, " {:width$}", self.postings[0], width = width - 19)?;
422 } else {
423 for posting in self.postings.iter() {
424 write!(f, "\n {:width$}", posting, width = width - 4)?;
425 }
426 }
427 }
428 _ => {
429 for posting in self.postings.iter() {
430 write!(f, "\n {:width$}", posting, width = width - 4)?;
431 }
432 }
433 }
434 Ok(())
435 }
436}