main.rs 4.4 KB

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