R524JUUE57KNHXLPN52X6DWK6PPQPBG5DXYNSVLGSEI5UMYXWDMQC VM4ZH5WDVH6LR4ZEZFQB2MOHNLC6A7RFSXRZWM3MRU6ISH33KJBAC X6YJF46GPT3KOVC3MJPJRPWLW6PEECFXFJLMFHDFOADYJGX3EXUQC YBARPI2BY5EJYD3HKDWLXHZIL7CS3V3KT5O5UJVB2ICICP7O6BLQC 6MYJDQ3IMOSMAOTJ2FAFE6EQRT4O36YI4I77ZWZ57APZRYER5X6QC 2JBFREZGJ2PST2DE3ZVDQADXAOFXBYPMSFTG7C65GDKLOZGETTGAC YDK6X6PPD42DMLFGF6OO2O3G7GA4Z2PCIDJIREHX6XNX2NYEBJSQC T2S6UAVJ6SVH5X326RLFZWF2LODSOEYAOQUHKW5D3NYNS43CU3AQC SEEWF7KXEUNPVQBZUZ3ZXU4BIADFUEULIKAD3OMKSNRBDNJXRPLQC SMBQYFPGAKUWTAAJDS4VW3YI4OC3XPXIXJYJ5MBEGNH7FKGIVYJAC W3MWSSJ7LUAUUAQJH47BNNGLM2O6CEMJ2MA5PY72EAA6GIW67ANAC D6UTHZA4XNAR2PTG4YEZFNNH3OTSNOWGDSVBYDRE5R2YSV7MPN6AC MG46NYACGKHTJ5V6NLPDGKQSETQQNCOGP7TDS4KPJDJFNPKWEFJAC 2NYDNXH7PDF7ZYAGJXWYOUJKGDT4HVWZRU73SA4CSFLVIIK2CIDAC UESS5YZE6ZHPMVUL2P2OACW2Y2QLFLLLLC3F3JAENVH6A7REKLBAC QRIJE4AQWN7A7O2CO7FXRV4FXZ5RHONQKRGHYXAD7WSECJYK2MFAC SJ6AFVZL5HEXG5ZUV5STIGGIGLU56WGAQCSZBFFHQOVHAILBOS2QC #[inline]pub fn with_metadata(&mut self, block: impl FnOnce(&mut TransactionMetadata)) -> &mut Self {block(&mut self.metadata);self
impl Transaction {pub fn main_account(&self) -> Option<&Acc> {self.postings.first().map(|posting| posting.account.as_ref())
use core::fmt::Display;use time::Date;use crate::Amount;use crate::Commodity;use crate::MetadataKey;use crate::MetadataMap;use crate::MetadataValue;#[derive(Clone, Debug, Eq, Hash, PartialEq)]pub struct Price {pub date: Date,pub quote: Commodity,pub price: Amount,pub meta: MetadataMap,}impl Price {pub fn new(date: Date, quote: Commodity, price: Amount) -> Self {let meta = MetadataMap::default();Self {date,quote,price,meta,}}}impl Price {#[inline]pub fn add_meta(&mut self,key: impl Into<MetadataKey>,value: impl Into<MetadataValue>,) -> &mut Self {self.meta.insert(key.into(), value.into());self}#[inline]pub fn clear_meta(&mut self) -> &mut Self {self.meta.clear();self}#[inline]pub fn set_quote(&mut self, quote: impl Into<Commodity>) -> &mut Self {self.quote = quote.into();self}#[inline]pub fn set_price(&mut self, price: Amount) -> &mut Self {self.price = price;self}#[inline]pub fn set_date(&mut self, date: Date) -> &mut Self {self.date = date;self}#[inline]pub fn set_meta(&mut self, meta: impl Into<MetadataMap>) -> &mut Self {self.meta = meta.into();self}}impl Display for Price {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {let Self {date,quote,price,meta,} = self;write!(f, "{date} price {quote} {price}")?;for (key, value) in meta {write!(f, "\n {key}: {value}")?;}Ok(())}}
use core::borrow::Borrow;use core::fmt::Display;use core::ops::Deref;use core::str::FromStr;use alloc::collections::BTreeMap;use delegate::delegate;use miette::Diagnostic;use snafu::ensure;use snafu::Backtrace;use snafu::Snafu;#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]#[repr(transparent)]pub struct Key {name: String,}impl Key {pub const unsafe fn from_unchecked(name: String) -> Self {Self { name }}}impl Borrow<str> for Key {fn borrow(&self) -> &str {&self.name}}impl Display for Key {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {f.write_str(&self.name)}}impl FromStr for Key {type Err = KeyError;#[inline]fn from_str(name: &str) -> Result<Self, Self::Err> {ensure!(is_valid_key(name), KeySnafu { name });let name = name.to_owned();Ok(Self { name })}}impl TryFrom<String> for Key {type Error = KeyError;fn try_from(name: String) -> Result<Self, Self::Error> {ensure!(is_valid_key(&name), KeySnafu { name });Ok(Self { name })}}#[derive(Debug, Diagnostic, Snafu)]pub struct KeyError {backtrace: Backtrace,name: String,}#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]#[repr(transparent)]pub struct Link {name: String,}impl Borrow<Lnk> for Link {fn borrow(&self) -> &Lnk {self}}impl Deref for Link {type Target = Lnk;fn deref(&self) -> &Self::Target {#[allow(unsafe_code)]unsafe {// SAFETY: self.name is a valid link name, by constructionSelf::Target::from_unchecked(&self.name)}}}impl Display for Link {delegate! {to self.name {fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result;}}}impl From<&Lnk> for Link {#[inline]fn from(link: &Lnk) -> Self {link.to_owned()}}impl FromStr for Link {type Err = LinkError;#[inline]fn from_str(name: &str) -> Result<Self, Self::Err> {<&Lnk>::try_from(name).map(Self::from)}}impl TryFrom<String> for Link {type Error = LinkError;#[inline]fn try_from(name: String) -> Result<Self, Self::Error> {ensure!(is_valid_link_name(&name), LinkSnafu { name });Ok(Self { name })}}#[derive(Debug, Diagnostic, Snafu)]pub struct LinkError {name: String,backtrace: Backtrace,}#[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]#[repr(transparent)]pub struct Lnk {name: str,}impl Lnk {#[allow(unsafe_code)]#[inline]const unsafe fn from_unchecked(name: &str) -> &Self {let name: *const _ = name;let name = name as *const _;unsafe { &*name }}}impl ToOwned for Lnk {type Owned = Link;#[inline]fn to_owned(&self) -> Self::Owned {let name = self.name.to_owned();Self::Owned { name }}}impl<'l> TryFrom<&'l str> for &'l Lnk {type Error = LinkError;#[inline]fn try_from(name: &'l str) -> Result<Self, Self::Error> {ensure!(is_valid_link_name(name), LinkSnafu { name });Ok(#[allow(unsafe_code)]unsafe {// SAFETY: we have ensured that `name` is a valid link.Lnk::from_unchecked(name)},)}}#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]pub struct Transaction {pub key_value: BTreeMap<Key, Value>,pub links: Vec<Link>,}impl Transaction {#[inline]pub fn add_link(&mut self, link: impl Into<Link>) -> &mut Self {self.links.push(link.into());self}#[inline]pub fn add_key_value(&mut self, key: impl Into<Key>, value: impl Into<Value>) -> &mut Self {self.key_value.insert(key.into(), value.into());self}}impl Display for Transaction {fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {for link in &self.links {write!(f, "\n {link}")?;}for (key, value) in &self.key_value {write!(f, "\n {key}: {value}")?;}Ok(())}}#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]pub enum Value {String(String),}impl Value {pub fn as_str(&self) -> Option<&str> {let Self::String(inner) = self;Some(inner)}}impl Display for Value {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {match self {Value::String(inner) => write!(f, "{inner:?}"),}}}impl<S> From<S> for ValuewhereS: Into<String>,{fn from(value: S) -> Self {Self::String(value.into())}}fn is_valid_key(name: &str) -> bool {lazy_regex::regex_is_match!(r"^(:?[a-z][a-zA-Z0-9\-_]+)$", name)}fn is_valid_link_name(name: &str) -> bool {lazy_regex::regex_is_match!(r"^(:?\^[A-Za-z0-9\-_/.]+)$", name)}
pub mod kv;pub mod link;
use alloc::collections::BTreeSet;use delegate::delegate;use miette::Diagnostic;use snafu::Backtrace;use snafu::ensure;use snafu::Snafu;use core::str::FromStr;use core::fmt::Display;use core::ops::Deref;use core::borrow::Borrow;#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]#[repr(transparent)]pub struct Link {pub(crate) name: String,}impl Borrow<Lnk> for Link {fn borrow(&self) -> &Lnk {self}}impl Deref for Link {type Target = Lnk;fn deref(&self) -> &Self::Target {#[allow(unsafe_code)]unsafe {// SAFETY: self.name is a valid link name, by constructionSelf::Target::from_unchecked(&self.name)}}}impl Display for Link {delegate! {to self.name {fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result;}}}impl From<&Lnk> for Link {#[inline]fn from(link: &Lnk) -> Self {link.to_owned()}}impl FromStr for Link {type Err = LinkError;#[inline]fn from_str(name: &str) -> Result<Self, Self::Err> {<&Lnk>::try_from(name).map(Self::from)}}impl TryFrom<String> for Link {type Error = LinkError;#[inline]fn try_from(name: String) -> Result<Self, Self::Error> {ensure!(is_valid_link_name(&name), LinkSnafu { name });Ok(Self { name })}}#[derive(Debug, Diagnostic, Snafu)]pub struct LinkError {pub(crate) name: String,pub(crate) backtrace: Backtrace,}#[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]#[repr(transparent)]pub struct Lnk {pub(crate) name: str,}impl Lnk {#[allow(unsafe_code)]#[inline]pub(crate) const unsafe fn from_unchecked(name: &str) -> &Self {let name: *const _ = name;let name = name as *const _;unsafe { &*name }}}impl ToOwned for Lnk {type Owned = Link;#[inline]fn to_owned(&self) -> Self::Owned {let name = self.name.to_owned();Self::Owned { name }}}impl<'l> TryFrom<&'l str> for &'l Lnk {type Error = LinkError;#[inline]fn try_from(name: &'l str) -> Result<Self, Self::Error> {ensure!(is_valid_link_name(name), LinkSnafu { name });Ok(#[allow(unsafe_code)]unsafe {// SAFETY: we have ensured that `name` is a valid link.Lnk::from_unchecked(name)},)}}pub type Set = BTreeSet<Link>;fn is_valid_link_name(name: &str) -> bool {lazy_regex::regex_is_match!(r"^(:?\^[A-Za-z0-9\-_/.]+)$", name)}
use alloc::collections::BTreeMap;use miette::Diagnostic;use rust_decimal::Decimal;use snafu::Backtrace;use snafu::ensure;use snafu::Snafu;use core::ops::Deref;use core::str::FromStr;use core::fmt::Display;use core::borrow::Borrow;pub mod common_keys;#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]#[repr(transparent)]pub struct Key {name: String,}impl Key {#[must_use]pub const unsafe fn from_unchecked(name: String) -> Self {Self { name }}}impl Borrow<Ky> for Key {fn borrow(&self) -> &Ky {self}}impl Borrow<str> for Key {fn borrow(&self) -> &str {&self.name}}impl Deref for Key {type Target = Ky;fn deref(&self) -> &Self::Target {unsafe { Ky::from_unchecked(&self.name) }}}impl Display for Key {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {f.write_str(&self.name)}}impl From<&Ky> for Key {fn from(value: &Ky) -> Self {value.to_owned()}}impl FromStr for Key {type Err = KeyError;#[inline]fn from_str(name: &str) -> Result<Self, Self::Err> {Self::try_from(name)}}impl TryFrom<&str> for Key {type Error = KeyError;#[inline]fn try_from(name: &str) -> Result<Self, Self::Error> {ensure!(is_valid_key(name), KeySnafu { name });let name = name.to_owned();Ok(Self { name })}}impl TryFrom<String> for Key {type Error = KeyError;#[inline]fn try_from(name: String) -> Result<Self, Self::Error> {ensure!(is_valid_key(&name), KeySnafu { name });Ok(Self { name })}}#[derive(Debug, Diagnostic, Snafu)]pub struct KeyError {pub(crate) backtrace: Backtrace,pub(crate) name: String,}#[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]#[repr(transparent)]pub struct Ky {name: str,}impl Ky {#[must_use]pub const unsafe fn from_unchecked(name: &str) -> &Self {unsafe {let name: *const _ = name;let name: *const Self = name as _;&*name}}}impl Borrow<str> for Ky {fn borrow(&self) -> &str {&self.name}}impl Display for Ky {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {f.write_str(&self.name)}}impl ToOwned for Ky {type Owned = Key;fn to_owned(&self) -> Self::Owned {let name = self.name.to_owned();Key { name }}}impl<'s> TryFrom<&'s str> for &'s Ky {type Error = KeyError;#[inline]fn try_from(name: &'s str) -> Result<Self, Self::Error> {ensure!(is_valid_key(name), KeySnafu { name });Ok(unsafe { Ky::from_unchecked(name) })}}pub type Map = BTreeMap<Key, Value>;#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]pub enum Value {Number(Decimal),String(String),}impl Value {#[must_use]pub fn as_number(&self) -> Option<Decimal> {if let Self::Number(v) = self {Some(*v)} else {None}}#[must_use]pub fn as_str(&self) -> Option<&str> {if let Self::String(inner) = self {Some(inner)} else {None}}}impl Display for Value {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {match self {Value::Number(inner) => inner.fmt(f),Value::String(inner) => write!(f, "{inner:?}"),}}}impl From<&str> for Value {fn from(value: &str) -> Self {Self::from(value.to_owned())}}impl From<Decimal> for Value {fn from(value: Decimal) -> Self {Self::Number(value)}}impl From<String> for Value {fn from(value: String) -> Self {Self::String(value)}}impl From<u8> for Value {fn from(value: u8) -> Self {Self::Number(Decimal::from(value))}}impl From<u16> for Value {fn from(value: u16) -> Self {Self::Number(Decimal::from(value))}}impl From<u32> for Value {fn from(value: u32) -> Self {Self::Number(Decimal::from(value))}}impl From<u64> for Value {fn from(value: u64) -> Self {Self::Number(Decimal::from(value))}}fn is_valid_key(name: &str) -> bool {lazy_regex::regex_is_match!(r"^(:?[a-z][a-zA-Z0-9\-_]+)$", name)}
use crate::MetadataKy;pub const FILENAME: &MetadataKy = unsafe { MetadataKy::from_unchecked("filename") };pub const LINE_NUMBER: &MetadataKy = unsafe { MetadataKy::from_unchecked("lineno") };pub const TIMESTAMP: &MetadataKy = unsafe { MetadataKy::from_unchecked("timestamp") };pub const TRANSACTION_ID: &MetadataKy = unsafe { MetadataKy::from_unchecked("transaction-id") };
pub use crate::metadata::Key as MetadataKey;pub use crate::metadata::Link;pub use crate::metadata::Transaction as TransactionMetadata;pub use crate::metadata::Value as MetadataValue;
pub use crate::metadata::kv::common_keys;pub use crate::metadata::kv::Key as MetadataKey;pub use crate::metadata::kv::Ky as MetadataKy;pub use crate::metadata::kv::Map as MetadataMap;pub use crate::metadata::kv::Value as MetadataValue;pub use crate::metadata::link::Link;pub use crate::metadata::link::Set as LinkSet;
}impl Directive {pub fn add_meta(&mut self,key: impl Into<MetadataKey>,value: impl Into<MetadataValue>,) -> &mut Self {match self {Directive::Balance(inner) => {inner.add_meta(key, value);}Directive::Price(inner) => {inner.add_meta(key, value);}Directive::Transaction(inner) => {inner.add_meta(key, value);}}self}
pub fn clear_meta(&mut self) -> &mut Self {self.meta.clear();self}#[inline]pub fn set_account(&mut self, account: impl Into<Account>) -> &mut Self {self.account = account.into();self}#[inline]pub fn set_amount(&mut self, amount: Amount) -> &mut Self {self.amount = amount;self}#[inline]pub fn set_date(&mut self, date: Date) -> &mut Self {self.date = date;self}#[inline]pub fn set_meta(&mut self, meta: impl Into<MetadataMap>) -> &mut Self {self.meta = meta.into();self}}impl Balance {#[inline]#[must_use]
#[derive(Debug, Eq, Hash, PartialEq)]struct DirectiveIndex {account: Account,year: i32,month: Month,
fn file_for(base: &Utf8Path, directive: &Directive) -> Option<Utf8PathBuf> {match directive {Directive::Balance(Balance { date, account, .. }) => {Some(account_file(base, account, *date))}Directive::Price(Price { quote, .. }) => Some(price_file(base, quote)),Directive::Transaction(transaction) => {if let Some(account) = transaction.main_account() {Some(account_file(base, account, transaction.date))} else {warn!(?transaction,"ignoring transaction since it has no postings");None}}}
impl DirectiveIndex {fn file_in(&self, path: &Utf8Path) -> Utf8PathBuf {let Self {account,year,month,} = self;let month = *month as u8;
fn account_file(base: &Utf8Path, account: &Acc, date: Date) -> Utf8PathBuf {let year = date.year();let month = date.month();let month = month as u8;
let mut path = path.to_string();let additional = account.len() + 4 + 2 + 5 + 3; // account name + year + month + extension + extra separatorspath.reserve(additional);
let mut path = base.to_string();let additional = account.len() + 4 + 2 + 5 + 3; // account name + year + month + extension + extra separatorspath.reserve(additional);
fn try_from(value: &Directive) -> Result<Self, Self::Error> {let account = if let Some(account) = value.main_account() {account.to_owned()} else {return IndexingDirectiveSnafu {directive: value.clone(),}.fail();};let date = value.date();let year = date.year();let month = date.month();
fn price_file(base: &Utf8Path, quote: &Commodity) -> Utf8PathBuf {let mut path = base.join("prices");path.push(&**quote);path.set_extension("bean");
tracing.workspace = true
(Some(payee), Some(narration)) => write!(self.inner, r#" "{payee}" "{narration}""#)?,(Some(payee), None) => write!(self.inner, r#" "{payee}" """#)?,(None, Some(narration)) => write!(self.inner, r#" "{narration}""#)?,
(Some(payee), Some(narration)) => write!(self.inner, r" {payee:?} {narration:?}")?,(Some(payee), None) => write!(self.inner, r#" {payee:?} """#)?,(None, Some(narration)) => write!(self.inner, r" {narration:?}")?,
fn print_transaction_metadata(&mut self, metadata: &TransactionMetadata) -> Result<()> {let TransactionMetadata {key_value, links, ..} = metadata;for link in links {write!(self.inner, "\n {link}")?;}for (key, value) in key_value {write!(self.inner, "\n {key}: {value}")?;}Ok(())}