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 construction
Self::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 Value
where
S: 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 construction
Self::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 separators
path.reserve(additional);
let mut path = base.to_string();
let additional = account.len() + 4 + 2 + 5 + 3; // account name + year + month + extension + extra separators
path.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(())
}