pactole-cli 0.2.0

Command-line interface for Pactole, a plain-text double-entry accounting ledger.
use std::path::PathBuf;
use std::process::ExitCode;

use chrono::NaiveDate;
use pactole_core::{RegisterFilter, TransactionStatus};
use pactole_storage_fs::FsSourceLoader;

use crate::commands::StatusArg;

#[allow(clippy::too_many_arguments)]
pub(crate) fn run(
    file: PathBuf,
    account: Option<String>,
    from: Option<NaiveDate>,
    to: Option<NaiveDate>,
    payee: Option<String>,
    narration: Option<String>,
    tag: Option<String>,
    status: Option<StatusArg>,
) -> ExitCode {
    let project = match pactole_storage_fs::analyze_file_with_loader(&file, &FsSourceLoader) {
        Ok(project) => project,
        Err(err) => return failure(err),
    };

    let diagnostics = project.diagnostics();
    crate::diagnostics::print(&diagnostics);
    let journal = match project.validated_journal() {
        Ok(journal) => journal,
        Err(_) => return ExitCode::FAILURE,
    };

    let account = match account {
        Some(account) => match pactole_core::AccountName::new(account) {
            Ok(account) => Some(account),
            Err(err) => return failure(err),
        },
        None => None,
    };

    let filter = RegisterFilter {
        account,
        from,
        to,
        payee,
        narration,
        tag,
        status: status.map(TransactionStatus::from),
    };

    for entry in pactole_core::register(&journal, &filter) {
        let status = match entry.status {
            TransactionStatus::Uncleared => "?",
            TransactionStatus::Pending => "!",
            TransactionStatus::Cleared => "*",
        };
        let description = [entry.payee.as_deref(), entry.narration.as_deref()]
            .into_iter()
            .flatten()
            .collect::<Vec<_>>()
            .join(" ");
        let amount = match &entry.amount {
            Some(amount) => format!("{} {}", amount.number, amount.commodity),
            None => String::new(),
        };
        let balance = entry
            .balance
            .iter()
            .map(|(commodity, sum)| format!("{sum} {commodity}"))
            .collect::<Vec<_>>()
            .join(", ");

        println!(
            "{}  {}  {:<40}  {:<25}  {:>15}  {:>15}",
            entry.date, status, description, entry.account, amount, balance
        );
    }

    ExitCode::SUCCESS
}

fn failure(error: impl std::fmt::Display) -> ExitCode {
    eprintln!("error: {error}");
    ExitCode::FAILURE
}