use crate::parser::located::Located;
use crate::parser::transaction::Transaction;
pub fn sort(transactions: &mut [Located<Transaction>], fields: &[String]) {
if fields.is_empty() {
return;
}
let criteria: Vec<_> = fields.iter().map(|f| parse_criterion(f)).collect();
transactions.sort_by(|a, b| {
for (field, reverse) in &criteria {
let ord = compare(&a.value, &b.value, field);
if ord != std::cmp::Ordering::Equal {
return if *reverse { ord.reverse() } else { ord };
}
}
std::cmp::Ordering::Equal
});
}
#[derive(Debug)]
enum Field {
Date,
Amount,
Account,
Description,
}
fn parse_criterion(s: &str) -> (Field, bool) {
let (name, reverse) = match s.strip_prefix('-') {
Some(rest) => (rest, true),
None => (s, false),
};
let field = match name {
"date" | "d" => Field::Date,
"amount" | "amt" => Field::Amount,
"account" | "acc" => Field::Account,
"description" | "desc" | "payee" => Field::Description,
_ => Field::Date,
};
(field, reverse)
}
fn compare(a: &Transaction, b: &Transaction, field: &Field) -> std::cmp::Ordering {
match field {
Field::Date => a.date.cmp(&b.date),
Field::Description => a.description.cmp(&b.description),
Field::Account => first_account(a).cmp(first_account(b)),
Field::Amount => first_amount(a)
.partial_cmp(&first_amount(b))
.unwrap_or(std::cmp::Ordering::Equal),
}
}
fn first_account(tx: &Transaction) -> &str {
tx.postings
.first()
.map(|lp| lp.value.account.as_str())
.unwrap_or("")
}
fn first_amount(tx: &Transaction) -> f64 {
tx.postings
.first()
.and_then(|lp| lp.value.amount.as_ref())
.map(|a| a.value.to_f64())
.unwrap_or(0.0)
}