use crate::parse::Parser;
pub(crate) use chrono::NaiveDate;
use getset::{CopyGetters, Getters};
use rust_decimal::Decimal;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::convert::From;
use std::fmt;
use std::ops::{Div, Mul};
use std::sync::Arc;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Location {
pub line: usize,
pub col: usize,
}
impl Location {
pub fn advance(&self, width: usize) -> Self {
Location {
col: self.col + width,
line: self.line,
}
}
}
impl From<(usize, usize)> for Location {
fn from(tuple: (usize, usize)) -> Self {
Location {
line: tuple.0,
col: tuple.1,
}
}
}
pub type SrcFile = Arc<String>;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Source {
pub file: SrcFile,
pub start: Location,
pub end: Location,
}
impl fmt::Display for Source {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}:{}", self.file, self.start.line, self.start.col)
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ErrorType {
Io,
Syntax,
NotBalanced,
Incomplete,
Account,
NoMatch,
Ambiguous,
Duplicate,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ErrorLevel {
Info,
Warning,
Error,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Error {
pub msg: String,
pub src: Source,
pub r#type: ErrorType,
pub level: ErrorLevel,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:?}: {}\n {}:{}:{}",
self.level, self.msg, self.src.file, self.src.start.line, self.src.start.col
)
}
}
pub type Currency = String;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Amount {
pub number: Decimal,
pub currency: Currency,
}
impl fmt::Display for Amount {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {}", self.number, self.currency)
}
}
impl<'a> Div<Decimal> for &'a Amount {
type Output = Amount;
fn div(self, rhs: Decimal) -> Self::Output {
Amount {
number: self.number / rhs,
currency: self.currency.clone(),
}
}
}
impl<'a> Mul<Decimal> for &'a Amount {
type Output = Amount;
fn mul(self, rhs: Decimal) -> Self::Output {
Amount {
number: self.number * rhs,
currency: self.currency.clone(),
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Price {
Unit(Amount),
Total(Amount),
}
impl fmt::Display for Price {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Price::Unit(amount) => write!(f, "@ {}", amount),
Price::Total(amount) => write!(f, "@@ {}", amount),
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct UnitCost {
pub amount: Amount,
pub date: NaiveDate,
}
impl fmt::Display for UnitCost {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{{ {}, {} }}", self.amount, self.date)
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum TxnFlag {
Pending,
Posted,
Pad,
Balance,
}
impl fmt::Display for TxnFlag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TxnFlag::Pending => write!(f, "!"),
TxnFlag::Posted | TxnFlag::Pad => write!(f, "*"),
TxnFlag::Balance => write!(f, "balance"),
}
}
}
pub type Account = Arc<String>;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Posting {
pub account: Account,
pub amount: Amount,
pub cost: Option<UnitCost>,
pub price: Option<Price>,
pub meta: Meta,
pub src: Source,
}
impl fmt::Display for Posting {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let num_str = self.amount.to_string();
let index = num_str.find(|c| c == ' ' || c == '.').unwrap();
let width = f.width().unwrap_or(46) - 1;
let account_width = std::cmp::max(self.account.len() + 1, width - index);
write!(
f,
"{:width$}{}",
self.account,
num_str,
width = account_width
)?;
if let Some(cost) = &self.cost {
write!(f, " {}", cost)?;
}
if let Some(ref price) = self.price {
write!(f, " {}", price)?;
}
Ok(())
}
}
pub type Payee = String;
pub type Narration = String;
pub type Link = String;
pub type Tag = String;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Getters, CopyGetters)]
pub struct Transaction {
#[getset(get_copy = "pub")]
pub(crate) date: NaiveDate,
#[getset(get_copy = "pub")]
pub(crate) flag: TxnFlag,
#[getset(get = "pub")]
pub(crate) payee: Payee,
#[getset(get = "pub")]
pub(crate) narration: Narration,
#[getset(get = "pub")]
pub(crate) links: Vec<Link>,
#[getset(get = "pub")]
pub(crate) tags: Vec<Tag>,
#[getset(get = "pub")]
pub(crate) meta: Meta,
#[getset(get = "pub")]
pub(crate) postings: Vec<Posting>,
#[getset(get = "pub")]
pub(crate) src: Source,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AccountNote {
pub date: NaiveDate,
pub val: String,
pub src: Source,
}
pub type AccountDoc = AccountNote;
pub type Meta = HashMap<String, (String, Source)>;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Getters)]
pub struct AccountInfo {
#[getset(get = "pub")]
pub(crate) open: (NaiveDate, Source),
#[getset(get = "pub")]
pub(crate) close: Option<(NaiveDate, Source)>,
#[getset(get = "pub")]
pub(crate) currencies: HashSet<Currency>,
#[getset(get = "pub")]
pub(crate) notes: Vec<AccountNote>,
#[getset(get = "pub")]
pub(crate) docs: Vec<AccountDoc>,
#[getset(get = "pub")]
pub(crate) meta: Meta,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct EventInfo {
pub date: NaiveDate,
pub desc: String,
pub src: Source,
}
impl From<(NaiveDate, String, Source)> for EventInfo {
fn from(tuple: (NaiveDate, String, Source)) -> Self {
EventInfo {
date: tuple.0,
desc: tuple.1,
src: tuple.2,
}
}
}
pub type BalanceSheet = HashMap<Account, HashMap<Currency, HashMap<Option<UnitCost>, Decimal>>>;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Getters)]
pub struct Ledger {
#[getset(get = "pub")]
pub(crate) accounts: HashMap<Account, AccountInfo>,
#[getset(get = "pub")]
pub(crate) commodities: HashMap<Currency, (Meta, Source)>,
#[getset(get = "pub")]
pub(crate) txns: Vec<Transaction>,
#[getset(get = "pub")]
pub(crate) options: HashMap<String, (String, Source)>,
#[getset(get = "pub")]
pub(crate) events: HashMap<String, Vec<EventInfo>>,
#[getset(get = "pub")]
pub(crate) balance_sheet: BalanceSheet,
}
impl Ledger {
pub fn from_file(path: &str) -> (Self, Vec<Error>) {
let (draft, mut errors) = Parser::parse(path);
let (ledger, more_errors) = draft.into_ledger();
errors.extend(more_errors);
(ledger, errors)
}
}
impl fmt::Display for Transaction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.flag {
TxnFlag::Balance => write!(f, "{} {}", self.date, self.flag)?,
_ => write!(
f,
"{} {} \"{}\" \"{}\"",
self.date, self.flag, self.payee, self.narration
)?,
};
for tag in &self.tags {
write!(f, " {}", tag)?;
}
for link in &self.links {
write!(f, " {}", link)?;
}
for (key, val) in self.meta.iter() {
write!(f, "\n {}: {}", key, val.0)?;
}
let width = f.width().unwrap_or(50);
match self.flag {
TxnFlag::Balance => {
if self.postings.len() == 1 {
write!(f, " {:width$}", self.postings[0], width = width - 19)?;
} else {
for posting in self.postings.iter() {
write!(f, "\n {:width$}", posting, width = width - 4)?;
}
}
}
_ => {
for posting in self.postings.iter() {
write!(f, "\n {:width$}", posting, width = width - 4)?;
}
}
}
Ok(())
}
}