YDK6X6PPD42DMLFGF6OO2O3G7GA4Z2PCIDJIREHX6XNX2NYEBJSQC
use crate::Account;
use crate::Amount;
use crate::CostBasis;
use std::fmt::Display;
use std::fmt::Write as _;
use std::hash::Hash;
use time::Date;
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Posting {
pub flag: Option<TransactionFlag>,
pub account: Account,
pub amount: Option<Amount>,
pub cost: Option<CostBasis>,
pub price: Option<Amount>,
}
impl Display for Posting {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Posting {
flag,
account,
amount,
cost,
price,
} = self;
if let Some(flag) = flag {
write!(f, "{flag} ")?;
}
write!(f, "{account}")?;
if let Some(amount) = amount {
write!(f, " {amount}")?;
if let Some(cost) = cost {
write!(f, " {cost}")?;
}
if let Some(price) = price {
write!(f, " @ {price}")?;
}
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Transaction {
pub date: Date,
pub flag: TransactionFlag,
pub payee: Option<String>,
pub narration: Option<String>,
pub postings: Vec<Posting>,
}
impl Display for Transaction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self {
date,
flag,
payee,
narration,
postings,
} = self;
write!(f, "{date} {flag}")?;
match (payee, narration) {
(None, None) => {}
(None, Some(narration)) => write!(f, r#" "{narration}""#)?,
(Some(payee), None) => write!(f, r#" "{payee}" """#)?,
(Some(payee), Some(narration)) => write!(f, r#" "{payee}" "{narration}""#)?,
}
for posting in postings {
write!(f, "\n {posting}")?;
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum TransactionFlag {
Complete,
Incomplete,
}
impl Display for TransactionFlag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_char(match self {
TransactionFlag::Complete => '*',
TransactionFlag::Incomplete => '!',
})
}
}
mod account {
use crate::account::Acc;
use crate::account::Account;
use static_assertions::assert_impl_all;
use std::fmt::Debug;
use std::hash::Hash;
use std::str::FromStr;
use test_case::test_case;
assert_impl_all!(
Account: Clone,
Debug,
FromStr,
Hash,
Ord,
TryFrom<&'static str>
);
#[test_case("Assets"; "only top-level")]
#[test_case("Equity:OpeningBalances"; "subaccount")]
#[test_case("Expenses:Banking-Fees"; "subaccount with dash")]
#[test_case("Income:Sales:2022"; "subaccount starting with number")]
#[test_case("Liabilities:Credit-Cards:VISA"; "multiple levels")]
fn parse_when_valid(name: &str) {
let acc = <&Acc>::try_from(name).unwrap();
let account = Account::try_from(name).unwrap();
assert_eq!(account, name);
assert_eq!(acc, name);
}
#[test_case("Cash"; "invalid account type")]
#[test_case("Assets:Accounts_Receivable"; "invalid characters")]
#[test_case("Assets:-Test"; "subaccount starting with dash")]
#[test_case("Income::Sales"; "empty segment")]
fn do_not_parse_when_invalid(name: &str) {
<&Acc>::try_from(name).unwrap_err();
}
}
mod amount {
use {
crate::Amount,
static_assertions::assert_impl_all,
std::{fmt::Debug, hash::Hash, str::FromStr},
test_case::test_case,
};
assert_impl_all!(
Amount: Copy,
Debug,
FromStr,
Hash,
PartialOrd,
TryFrom<&'static str>
);
#[test_case("0 EUR"; "zero")]
#[test_case("15 USD"; "integer amount")]
#[test_case("0.30 CAD"; "fractional amount")]
#[test_case("0.0000067 ETH"; "arbitrary precision")]
#[test_case("-16.93 USD"; "negative amount")]
fn parse_when_valid(amount: &str) {
let account = Amount::try_from(amount).unwrap();
assert_eq!(account.to_string(), amount);
}
#[test_case(" EUR"; "missing units")]
#[test_case("15 "; "missing commodity")]
#[test_case("15,000.00 USD"; "thousands separator")]
#[test_case("15000,00 USD"; "invalid decimal separator")]
fn do_not_parse_when_invalid(amount: &str) {
Amount::try_from(amount).unwrap_err();
}
}
mod commodities {
use {
crate::Commodity,
static_assertions::assert_impl_all,
std::{fmt::Debug, hash::Hash, str::FromStr},
test_case::test_case,
};
assert_impl_all!(
Commodity: Copy,
Debug,
FromStr,
Hash,
Ord,
TryFrom<&'static str>
);
#[test_case("A"; "single letter")]
#[test_case("USD"; "dollar")]
#[test_case("EUR"; "euro")]
#[test_case("MSFT"; "stock")]
#[test_case("AIRMILE"; "creative")]
#[test_case("DE.-_3"; "with special characters")]
fn parse_when_valid(name: &str) {
let commodity = Commodity::try_from(name).unwrap();
assert_eq!(commodity.to_string(), name);
}
#[test_case("0"; "starting with number")]
#[test_case("D-"; "ending with dash")]
#[test_case("E_"; "ending with underscore")]
#[test_case("X."; "ending with dot")]
#[test_case("X 3"; "containing space")]
#[test_case("X\\0"; "containing backslash")]
fn do_not_parse_when_invalid(name: &str) {
Commodity::try_from(name).unwrap_err();
}
// TODO proptest?
}
use std::fmt::Display;
use time::Date;
mod account;
mod amount;
mod balance;
mod commodity;
mod cost;
mod transaction;
pub use crate::account::Acc;
pub use crate::account::Account;
pub use crate::amount::Amount;
pub use crate::balance::Balance;
pub use crate::commodity::Commodity;
pub use crate::cost::CostBasis;
pub use crate::transaction::Posting;
pub use crate::transaction::Transaction;
pub use crate::transaction::TransactionFlag;
#[derive(Clone, Debug)]
pub enum Directive {
Balance(balance::Balance),
Transaction(transaction::Transaction),
}
impl Directive {
pub fn date(&self) -> Date {
match self {
Self::Balance(balance) => balance.date,
Self::Transaction(transaction) => transaction.date,
}
}
pub fn main_account(&self) -> Option<&Acc> {
match self {
Directive::Balance(balance) => Some(&balance.account),
Directive::Transaction(transaction) => transaction
.postings
.get(0)
.map(|posting| posting.account.as_ref()),
}
}
}
impl Display for Directive {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Balance(balance) => balance.fmt(f),
Self::Transaction(transaction) => transaction.fmt(f),
}
}
}
impl From<balance::Balance> for Directive {
fn from(balance: balance::Balance) -> Self {
Self::Balance(balance)
}
}
impl From<transaction::Transaction> for Directive {
fn from(transaction: transaction::Transaction) -> Self {
Self::Transaction(transaction)
}
}
#[cfg(test)]
mod that;
use crate::amount::Amount;
use std::fmt::Display;
use std::hash::Hash;
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum CostBasis {
Empty,
PerUnit(Amount),
Total(Amount),
}
impl Display for CostBasis {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Empty => write!(f, "{{}}"),
Self::PerUnit(amount) => write!(f, "{{{amount}}}"),
Self::Total(amount) => write!(f, "{{{{{amount}}}}}"),
}
}
}
use arrayvec::ArrayString;
use snafu::ensure;
use snafu::Backtrace;
use snafu::Snafu;
use std::fmt::Display;
use std::str::FromStr;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Commodity {
pub(crate) name: ArrayString<24>,
}
impl Display for Commodity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.pad(&self.name)
}
}
impl FromStr for Commodity {
type Err = <Self as TryFrom<&'static str>>::Error;
fn from_str(name: &str) -> Result<Self, Self::Err> {
Self::try_from(name)
}
}
impl TryFrom<&str> for Commodity {
type Error = CommodityError;
fn try_from(name: &str) -> Result<Self, Self::Error> {
ensure!(
lazy_regex::regex_is_match!("^[A-Z](?:[-A-Z0-9._]{0,22}[A-Z0-9])?$", name),
CommoditySnafu { name }
);
let name = name.try_into().expect("length should fit");
Ok(Self { name })
}
}
#[derive(Debug, Snafu)]
#[snafu(display("invalid commodity: {name:?}"))]
pub struct CommodityError {
pub(crate) name: String,
pub(crate) backtrace: Backtrace,
}
use crate::Account;
use crate::Amount;
use std::fmt::Display;
use std::hash::Hash;
use time::Date;
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Balance {
pub date: Date,
pub account: Account,
pub amount: Amount,
}
impl Display for Balance {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Balance {
date,
account,
amount,
} = self;
write!(f, "{date} balance {account} {amount}")
}
}
use crate::Commodity;
use rust_decimal::Decimal;
use snafu::Backtrace;
use snafu::OptionExt as _;
use snafu::Snafu;
use std::fmt::Display;
use std::ops::Neg;
use std::str::FromStr;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Amount {
pub amount: Decimal,
pub commodity: Commodity,
}
impl Display for Amount {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { amount, commodity } = self;
write!(f, "{amount} {commodity}")
}
}
impl FromStr for Amount {
type Err = <Self as TryFrom<&'static str>>::Error;
fn from_str(amount: &str) -> Result<Self, Self::Err> {
Self::try_from(amount)
}
}
impl Neg for Amount {
type Output = Self;
fn neg(self) -> Self::Output {
Self {
amount: -self.amount,
..self
}
}
}
impl PartialOrd for Amount {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
(self.commodity == other.commodity).then(|| self.amount.cmp(&other.amount))
}
}
impl TryFrom<&str> for Amount {
type Error = AmountError;
fn try_from(amount: &str) -> Result<Self, Self::Error> {
let context = AmountSnafu { value: amount };
let (amount, commodity) = amount.split_once(' ').context(context)?;
let amount = amount.parse().map_err(|_| context.build())?;
let commodity = commodity.parse().map_err(|_| context.build())?;
Ok(Self { amount, commodity })
}
}
#[derive(Debug, Snafu)]
pub struct AmountError {
value: String,
backtrace: Backtrace,
}
use delegate::delegate;
use snafu::ensure;
use snafu::Backtrace;
use snafu::Snafu;
use std::borrow::Borrow;
use std::borrow::Cow;
use std::cmp::Ordering;
use std::fmt::Display;
use std::hash::Hash;
use std::iter::FusedIterator;
use std::ops::Deref;
use std::str::FromStr;
use std::str::Split;
#[derive(Debug)]
#[repr(transparent)]
pub struct Acc {
name: str,
}
impl Acc {
pub fn ancestors(&self) -> impl Iterator<Item = &Self> + '_ {
Ancestors { next: Some(self) }
}
pub fn segments(&self) -> Segments<'_> {
Segments {
inner: self.name.split(':'),
}
}
pub fn parent(&self) -> Option<&Self> {
self.name
.split_once(':')
.map(|(parent, _)| unsafe { Self::from_unchecked(parent) })
}
const unsafe fn from_unchecked(name: &str) -> &Self {
&*(name as *const _ as *const _)
}
}
impl AsRef<str> for Acc {
#[inline]
fn as_ref(&self) -> &str {
self.borrow()
}
}
impl Borrow<str> for Acc {
#[inline]
fn borrow(&self) -> &str {
&self.name
}
}
impl Display for Acc {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.pad(&self.name)
}
}
impl Eq for Acc {}
impl Hash for Acc {
#[inline]
fn hash<H>(&self, state: &mut H)
where
H: std::hash::Hasher,
{
self.name.hash(state);
}
}
impl Ord for Acc {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.segments().cmp(other.segments())
}
}
impl PartialEq for Acc {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.cmp(other).is_eq()
}
}
impl PartialEq<&str> for Acc {
#[inline]
fn eq(&self, other: &&str) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialEq<Acc> for &str {
#[inline]
fn eq(&self, other: &Acc) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialEq<Acc> for Cow<'_, Acc> {
#[inline]
fn eq(&self, other: &Acc) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialEq<Acc> for Cow<'_, str> {
#[inline]
fn eq(&self, other: &Acc) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialEq<Acc> for String {
#[inline]
fn eq(&self, other: &Acc) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialEq<Acc> for str {
#[inline]
fn eq(&self, other: &Acc) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialEq<Cow<'_, Self>> for Acc {
#[inline]
fn eq(&self, other: &Cow<'_, Self>) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialEq<Cow<'_, str>> for Acc {
#[inline]
fn eq(&self, other: &Cow<'_, str>) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialEq<String> for Acc {
#[inline]
fn eq(&self, other: &String) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialEq<str> for Acc {
#[inline]
fn eq(&self, other: &str) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialOrd for Acc {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialOrd<&str> for Acc {
#[inline]
fn partial_cmp(&self, other: &&str) -> Option<Ordering> {
self.partial_cmp(*other)
}
}
impl PartialOrd<Acc> for &str {
#[inline]
fn partial_cmp(&self, other: &Acc) -> Option<Ordering> {
other.partial_cmp(self).map(Ordering::reverse)
}
}
impl PartialOrd<Acc> for Cow<'_, Acc> {
#[inline]
fn partial_cmp(&self, other: &Acc) -> Option<Ordering> {
other.partial_cmp(self).map(Ordering::reverse)
}
}
impl PartialOrd<Acc> for Cow<'_, str> {
#[inline]
fn partial_cmp(&self, other: &Acc) -> Option<Ordering> {
other.partial_cmp(self).map(Ordering::reverse)
}
}
impl PartialOrd<Acc> for String {
#[inline]
fn partial_cmp(&self, other: &Acc) -> Option<Ordering> {
other.partial_cmp(self).map(Ordering::reverse)
}
}
impl PartialOrd<Acc> for str {
#[inline]
fn partial_cmp(&self, other: &Acc) -> Option<Ordering> {
other.partial_cmp(self).map(Ordering::reverse)
}
}
impl PartialOrd<Cow<'_, Acc>> for Acc {
fn partial_cmp(&self, other: &Cow<'_, Acc>) -> Option<Ordering> {
self.partial_cmp(&**other)
}
}
impl PartialOrd<Cow<'_, str>> for Acc {
fn partial_cmp(&self, other: &Cow<'_, str>) -> Option<Ordering> {
self.partial_cmp(&**other)
}
}
impl PartialOrd<String> for Acc {
#[inline]
fn partial_cmp(&self, other: &String) -> Option<Ordering> {
self.partial_cmp(&**other)
}
}
impl PartialOrd<str> for Acc {
#[inline]
fn partial_cmp(&self, other: &str) -> Option<Ordering> {
other.try_into().ok().map(|other| self.cmp(other))
}
}
impl ToOwned for Acc {
type Owned = Account;
fn to_owned(&self) -> Self::Owned {
Account {
name: self.name.to_owned(),
}
}
}
impl<'a> TryFrom<&'a str> for &'a Acc {
type Error = AccountError;
fn try_from(name: &'a str) -> Result<Self, Self::Error> {
ensure!(is_valid_account_name(name), AccountSnafu { name });
Ok(unsafe { Acc::from_unchecked(name) })
}
}
#[derive(Clone, Debug)]
#[repr(transparent)]
pub struct Account {
name: String,
}
impl Account {
pub fn join(self, segment: impl AsRef<str>) -> Result<Self, AccountError> {
fn join_inner(mut this: Account, segment: &str) -> Result<Account, AccountError> {
ensure!(
is_valid_account_segment(segment),
AccountSnafu { name: segment }
);
this.name.push(':');
this.name.push_str(segment);
Ok(this)
}
join_inner(self, segment.as_ref())
}
}
impl AsRef<Acc> for Account {
#[inline]
fn as_ref(&self) -> &Acc {
self
}
}
impl AsRef<String> for Account {
#[inline]
fn as_ref(&self) -> &String {
self.borrow()
}
}
impl AsRef<str> for Account {
#[inline]
fn as_ref(&self) -> &str {
self.borrow()
}
}
impl Borrow<Acc> for Account {
#[inline]
fn borrow(&self) -> &Acc {
self
}
}
impl Borrow<String> for Account {
#[inline]
fn borrow(&self) -> &String {
&self.name
}
}
impl Borrow<str> for Account {
#[inline]
fn borrow(&self) -> &str {
&self.name
}
}
impl Deref for Account {
type Target = Acc;
#[inline]
fn deref(&self) -> &Self::Target {
unsafe { Acc::from_unchecked(&self.name) }
}
}
impl Display for Account {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
(**self).fmt(f)
}
}
impl Eq for Account {}
impl From<&Acc> for Account {
#[inline]
fn from(acc: &Acc) -> Self {
acc.to_owned()
}
}
impl FromStr for Account {
type Err = <Self as TryFrom<&'static str>>::Error;
#[inline]
fn from_str(name: &str) -> Result<Self, Self::Err> {
Self::try_from(name)
}
}
impl Hash for Account {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
(**self).hash(state)
}
}
impl Ord for Account {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
(**self).cmp(&**other)
}
}
impl PartialEq for Account {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.cmp(other).is_eq()
}
}
impl PartialEq<&Acc> for Account {
#[inline]
fn eq(&self, other: &&Acc) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialEq<&str> for Account {
#[inline]
fn eq(&self, other: &&str) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialEq<Acc> for Account {
#[inline]
fn eq(&self, other: &Acc) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialEq<Account> for &Acc {
#[inline]
fn eq(&self, other: &Account) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialEq<Account> for &str {
#[inline]
fn eq(&self, other: &Account) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialEq<Account> for Acc {
#[inline]
fn eq(&self, other: &Account) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialEq<Account> for Cow<'_, Acc> {
#[inline]
fn eq(&self, other: &Account) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialEq<Account> for Cow<'_, str> {
#[inline]
fn eq(&self, other: &Account) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialEq<Account> for String {
#[inline]
fn eq(&self, other: &Account) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialEq<Account> for str {
#[inline]
fn eq(&self, other: &Account) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialEq<Cow<'_, Acc>> for Account {
#[inline]
fn eq(&self, other: &Cow<'_, Acc>) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialEq<Cow<'_, str>> for Account {
#[inline]
fn eq(&self, other: &Cow<'_, str>) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialEq<String> for Account {
#[inline]
fn eq(&self, other: &String) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialEq<str> for Account {
#[inline]
fn eq(&self, other: &str) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl PartialOrd for Account {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialOrd<&Acc> for Account {
#[inline]
fn partial_cmp(&self, other: &&Acc) -> Option<Ordering> {
self.partial_cmp(*other)
}
}
impl PartialOrd<&str> for Account {
#[inline]
fn partial_cmp(&self, other: &&str) -> Option<Ordering> {
self.partial_cmp(*other)
}
}
impl PartialOrd<Acc> for Account {
#[inline]
fn partial_cmp(&self, other: &Acc) -> Option<Ordering> {
(**self).partial_cmp(other)
}
}
impl PartialOrd<Account> for &Acc {
#[inline]
fn partial_cmp(&self, other: &Account) -> Option<Ordering> {
other.partial_cmp(self).map(Ordering::reverse)
}
}
impl PartialOrd<Account> for &str {
#[inline]
fn partial_cmp(&self, other: &Account) -> Option<Ordering> {
other.partial_cmp(self).map(Ordering::reverse)
}
}
impl PartialOrd<Account> for Acc {
#[inline]
fn partial_cmp(&self, other: &Account) -> Option<Ordering> {
other.partial_cmp(self).map(Ordering::reverse)
}
}
impl PartialOrd<Account> for Cow<'_, Acc> {
#[inline]
fn partial_cmp(&self, other: &Account) -> Option<Ordering> {
other.partial_cmp(self).map(Ordering::reverse)
}
}
impl PartialOrd<Account> for Cow<'_, str> {
#[inline]
fn partial_cmp(&self, other: &Account) -> Option<Ordering> {
other.partial_cmp(self).map(Ordering::reverse)
}
}
impl PartialOrd<Account> for String {
#[inline]
fn partial_cmp(&self, other: &Account) -> Option<Ordering> {
other.partial_cmp(self).map(Ordering::reverse)
}
}
impl PartialOrd<Account> for str {
#[inline]
fn partial_cmp(&self, other: &Account) -> Option<Ordering> {
other.partial_cmp(self).map(Ordering::reverse)
}
}
impl PartialOrd<Cow<'_, Acc>> for Account {
fn partial_cmp(&self, other: &Cow<'_, Acc>) -> Option<Ordering> {
self.partial_cmp(&**other)
}
}
impl PartialOrd<Cow<'_, str>> for Account {
fn partial_cmp(&self, other: &Cow<'_, str>) -> Option<Ordering> {
self.partial_cmp(&**other)
}
}
impl PartialOrd<String> for Account {
#[inline]
fn partial_cmp(&self, other: &String) -> Option<Ordering> {
self.partial_cmp(&**other)
}
}
impl PartialOrd<str> for Account {
#[inline]
fn partial_cmp(&self, other: &str) -> Option<Ordering> {
(**self).partial_cmp(other)
}
}
impl TryFrom<&str> for Account {
type Error = AccountError;
#[inline]
fn try_from(name: &str) -> Result<Self, Self::Error> {
<&Acc>::try_from(name).map(Self::from)
}
}
impl TryFrom<String> for Account {
type Error = <Self as TryFrom<&'static str>>::Error;
#[inline]
fn try_from(name: String) -> Result<Self, Self::Error> {
ensure!(is_valid_account_name(&name), AccountSnafu { name });
Ok(Self { name })
}
}
#[derive(Debug, Snafu)]
#[snafu(display("invalid account: {name:?}"))]
pub struct AccountError {
name: String,
backtrace: Backtrace,
}
struct Ancestors<'a> {
next: Option<&'a Acc>,
}
impl FusedIterator for Ancestors<'_> {}
impl<'a> Iterator for Ancestors<'a> {
type Item = &'a Acc;
fn next(&mut self) -> Option<Self::Item> {
let next = self.next?;
self.next = next.parent();
Some(next)
}
}
#[derive(Clone, Debug)]
pub struct Segments<'a> {
inner: Split<'a, char>,
}
impl DoubleEndedIterator for Segments<'_> {
delegate! {
to self.inner {
fn next_back(&mut self) -> Option<Self::Item>;
}
}
}
impl FusedIterator for Segments<'_> {}
impl<'a> Iterator for Segments<'a> {
type Item = &'a str;
delegate! {
to self.inner {
fn next(&mut self) -> Option<Self::Item>;
}
}
}
fn is_valid_account_name(name: &str) -> bool {
lazy_regex::regex_is_match!(
r#"^(?:Assets|Equity|Expenses|Income|Liabilities)(?::[\p{Lu}\p{Nd}][-\p{L}\p{Nd}]*)*$"#,
name
)
}
fn is_valid_account_segment(name: &str) -> bool {
lazy_regex::regex_is_match!(r#"^(?:[\p{Lu}\p{Nd}][-\p{L}\p{Nd}]*)$"#, name)
}
[package]
name = "beancount-types"
version = "0.0.0-dev.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
arrayvec = "0.7.2"
delegate = "0.8.0"
lazy-regex = "2.2.2"
once_cell = "1.9.0"
regex = "1.5.4"
rust_decimal = "1.21.0"
snafu = "0.7.0"
time = "0.3.7"
[dev-dependencies]
insta = "1.12.0"
proptest = "1.0.0"
static_assertions = "1.1.0"
test-case = "2.2.1"
[features]
name = "beancount-types"
version = "0.0.0-dev.0"
dependencies = [
"arrayvec",
"delegate",
"insta",
"lazy-regex",
"once_cell",
"proptest",
"regex",
"rust_decimal",
"snafu",
"static_assertions",
"test-case",
"time",
]
[[package]]
name = "bit-set"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "console"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847"
dependencies = [
"encode_unicode",
"libc",
"once_cell",
"terminal_size",
"winapi",
]
[[package]]
name = "delegate"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "082a24a9967533dc5d743c602157637116fc1b52806d694a5a45e6f32567fcdd"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "fastrand"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
dependencies = [
"instant",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "insta"
version = "1.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc61e98be01e89296f3343a878e9f8ca75a494cb5aaf29df65ef55734aeb85f5"
dependencies = [
"console",
"linked-hash-map",
"once_cell",
"similar",
"yaml-rust",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "lazy-regex"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6b12f2eb6ed7d39405c5eb25a034b4c106a9ad87a6d9be3298de6c5f32fd57d"
dependencies = [
"lazy-regex-proc_macros",
"once_cell",
"regex",
]
[[package]]
name = "lazy-regex-proc_macros"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2496e5264069bc726ccf37eb76b9cd89406ae110d836c3f76729f99c8a23293"
dependencies = [
"proc-macro2",
"quote",
"regex",
"syn",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.132"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
]
[[package]]
name = "num_threads"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
dependencies = [
"libc",
]
[[package]]
name = "once_cell"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
[[package]]
name = "ppv-lite86"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
name = "proptest"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0d9cc07f18492d879586c92b485def06bc850da3118075cd45d50e9c95b0e5"
dependencies = [
"bit-set",
"bitflags",
"byteorder",
"lazy_static",
"num-traits",
"quick-error 2.0.1",
"rand",
"rand_chacha",
"rand_xorshift",
"regex-syntax",
"rusty-fork",
"tempfile",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_xorshift"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
dependencies = [
"rand_core",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi",
[[package]]
name = "similar"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62ac7f900db32bf3fd12e0117dd3dc4da74bc52ebaac97f39668446d89694803"
[[package]]
name = "snafu"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5177903bf45656592d9eb5c0e22f408fc023aae51dbe2088889b71633ba451f2"
dependencies = [
"doc-comment",
"snafu-derive",
]
[[package]]
name = "snafu-derive"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "410b26ed97440d90ced3e2488c868d56a86e2064f5d7d6f417909b286afe25e5"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "syn"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
dependencies = [
"cfg-if",
"fastrand",
"libc",
"redox_syscall",
"remove_dir_all",
"winapi",
]
[[package]]
name = "terminal_size"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "test-case"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07aea929e9488998b64adc414c29fe5620398f01c2e3f58164122b17e567a6d5"
dependencies = [
"test-case-macros",
]
[[package]]
name = "test-case-macros"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c95968eedc6fc4f5c21920e0f4264f78ec5e4c56bb394f319becc1a5830b3e54"
dependencies = [
"cfg-if",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b"
dependencies = [
"libc",
"num_threads",
]
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wait-timeout"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
dependencies = [
"libc",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]