main.rs 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. use itertools::Itertools;
  2. mod check;
  3. mod data;
  4. #[derive(clap::Parser)]
  5. struct Invocation {
  6. /// path to treasure file
  7. #[clap(long, short, env = "TREASURE_FILE")]
  8. file: std::path::PathBuf,
  9. /// verbosity of output messages
  10. #[clap(long, short, action = clap::ArgAction::Count)]
  11. verbose: u8,
  12. #[clap(subcommand)]
  13. cmd: Command,
  14. }
  15. #[derive(clap::Subcommand)]
  16. enum Command {
  17. Summarize,
  18. Ledger { account: String },
  19. Reformat,
  20. }
  21. fn load_data(
  22. fsdata: &mut data::FilesystemData,
  23. invocation: &Invocation,
  24. ) -> anyhow::Result<data::Root> {
  25. match data::Root::load(fsdata, &invocation.file) {
  26. Ok(data) => Ok(data),
  27. Err(data::DataError::IOError(ioerror)) => Err(ioerror.into()),
  28. Err(data::DataError::Report(report)) => {
  29. report.eprint(fsdata)?;
  30. Err(anyhow::anyhow!("Error reported"))
  31. }
  32. Err(data::DataError::Validation(verr)) => Err(anyhow::anyhow!("Validation error: {verr}")),
  33. }
  34. }
  35. impl Command {
  36. fn run(&self, inv: &Invocation) -> anyhow::Result<()> {
  37. let mut fsdata = data::FilesystemData::default();
  38. match self {
  39. Self::Summarize => {
  40. let data = load_data(&mut fsdata, inv)?;
  41. let positive = console::Style::new().green();
  42. let negative = console::Style::new().red();
  43. let neutral = console::Style::new();
  44. let Some(maxlen) = data.account_names().map(|v| v.len()).max() else {
  45. return Ok(());
  46. };
  47. for shortname in data.account_names().sorted_by_key(|an| an.as_str()) {
  48. if shortname.as_ref() == "initial" {
  49. continue;
  50. }
  51. if let Some(balances) = data.balance(shortname) {
  52. let balances = balances
  53. .into_iter()
  54. .filter(|(_, b)| !b.is_zero())
  55. .sorted()
  56. .map(|(u, b)| {
  57. (
  58. u,
  59. if b.is_sign_positive() {
  60. positive.apply_to(b)
  61. } else if b.is_sign_negative() {
  62. negative.apply_to(b)
  63. } else {
  64. neutral.apply_to(b)
  65. },
  66. )
  67. });
  68. println!(
  69. "{shortname:maxlen$} {}",
  70. balances.map(|(u, b)| format!("{b:8} {u:5}")).join(" ")
  71. );
  72. }
  73. }
  74. }
  75. Self::Ledger { account } => {
  76. let data = load_data(&mut fsdata, inv)?;
  77. let aname = data::AccountName::new(account.as_str());
  78. if let Some(ld) = data.ledger_data_for(aname) {
  79. for le in ld {
  80. let Some((acc_bal, other_bal)) = le.split_balances(aname) else {
  81. continue;
  82. };
  83. log::info!(
  84. "{:?}: {} {:?}",
  85. le.datestamp,
  86. acc_bal.amount,
  87. other_bal.map(|b| b.account).collect::<Vec<_>>()
  88. );
  89. }
  90. } else {
  91. log::info!("account not found. data: {data:?}");
  92. }
  93. // let data = data::Root::load(&mut fsdata, &inv.file);
  94. // data.account(account)?;
  95. }
  96. Self::Reformat => {
  97. let data = load_data(&mut fsdata, inv)?;
  98. data::ledger::print_ledger(&data, data.all_ledger_data().iter());
  99. }
  100. }
  101. Ok(())
  102. }
  103. }
  104. fn main() -> anyhow::Result<()> {
  105. use clap::Parser;
  106. let inv = Invocation::parse();
  107. pretty_env_logger::formatted_builder()
  108. .filter_level(match inv.verbose {
  109. 0 => log::LevelFilter::Info,
  110. 1 => log::LevelFilter::Debug,
  111. _ => log::LevelFilter::Trace,
  112. })
  113. .init();
  114. inv.cmd.run(&inv)
  115. }