main.rs 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. use chrono::{Date, Utc};
  2. use pickledb::{PickleDb, PickleDbDumpPolicy, SerializationMethod};
  3. use qif_generator::{
  4. account::{Account, AccountType},
  5. split::Split,
  6. transaction::Transaction,
  7. };
  8. use radix_trie::Trie;
  9. use shellexpand::tilde;
  10. use std::fs;
  11. use std::path::{Path, PathBuf};
  12. use std::time::Duration;
  13. use structopt::StructOpt;
  14. mod categories;
  15. use categories::get_category;
  16. use categories::CatStats;
  17. mod import;
  18. mod receipt;
  19. mod telegram;
  20. mod ui;
  21. fn read_file(f: &str) -> receipt::Purchase {
  22. let json = fs::read_to_string(f).expect("Can't read file");
  23. receipt::parse_purchase(&json)
  24. }
  25. #[cfg(test)]
  26. mod tests {
  27. use super::*;
  28. #[test]
  29. fn test_read_receipt() {
  30. let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
  31. p.push("tests/resources/test.json");
  32. let full_path = p.to_string_lossy();
  33. let result = read_file(&full_path).items;
  34. assert_eq!(result[0].name, "СИДР 0.5 MAGNERS APP");
  35. assert_eq!(result[0].sum, 17713);
  36. }
  37. }
  38. fn gen_splits(items: &[receipt::Item], cs: &mut CatStats) -> Vec<Split> {
  39. let mut result: Vec<Split> = Vec::new();
  40. for i in items.iter() {
  41. let t = Split::new()
  42. .memo(i.name.as_str())
  43. .amount(i.sum)
  44. .category(&get_category(i.name.as_str(), cs))
  45. .build();
  46. result.push(t);
  47. }
  48. result
  49. }
  50. fn gen_trans<'a>(
  51. acc: &'a Account,
  52. date: Date<Utc>,
  53. sum: i64,
  54. splits: &'a [Split],
  55. ) -> Result<Transaction<'a>, String> {
  56. let t = Transaction::new(acc)
  57. .date(date)
  58. .memo("New")
  59. .splits(splits)
  60. .build();
  61. match t {
  62. Ok(t) => {
  63. if t.sum() == sum {
  64. Ok(t)
  65. } else {
  66. Err(format!(
  67. "Total sum is wrong. Expected: {} Got: {}",
  68. sum,
  69. t.sum()
  70. ))
  71. }
  72. }
  73. Err(e) => Err(e),
  74. }
  75. }
  76. /// Search for a pattern in a file and display the lines that contain it.
  77. #[derive(StructOpt)]
  78. struct Cli {
  79. /// The path to the file to read
  80. filename: String,
  81. #[structopt(parse(from_os_str), long, help = "Accounts csv file")]
  82. accounts: Option<PathBuf>,
  83. #[structopt(short, long, default_value = "~/.config/receqif/rc.db")]
  84. database: String,
  85. }
  86. #[cfg(not(tarpaulin_include))]
  87. fn main() {
  88. let args = Cli::from_args();
  89. let confpath: &str = &tilde(&args.database);
  90. let confpath = PathBuf::from(confpath);
  91. let ten_sec = Duration::from_secs(10);
  92. let db = PickleDb::load(
  93. &confpath,
  94. PickleDbDumpPolicy::PeriodicDump(ten_sec),
  95. SerializationMethod::Json,
  96. );
  97. let mut db = match db {
  98. Ok(db) => db,
  99. Err(_) => PickleDb::new(
  100. &confpath,
  101. PickleDbDumpPolicy::PeriodicDump(ten_sec),
  102. SerializationMethod::Json,
  103. ),
  104. };
  105. let mut catmap: CatStats = match db.get("catmap") {
  106. Some(v) => v,
  107. None => Trie::new(),
  108. };
  109. if let Some(filename) = args.accounts {
  110. let accounts = import::read_accounts(Path::new(&filename)).unwrap();
  111. db.set("accounts", &accounts).unwrap();
  112. }
  113. telegram::bot();
  114. let purchase = read_file(&args.filename);
  115. let splits = gen_splits(&purchase.items, &mut catmap);
  116. let acc = Account::new()
  117. .name("Wallet")
  118. .account_type(AccountType::Cash)
  119. .build();
  120. let t = gen_trans(&acc, purchase.date(), purchase.total_sum(), &splits).unwrap();
  121. print!("{}", acc.to_string());
  122. println!("{}", t.to_string());
  123. db.set("catmap", &catmap).unwrap();
  124. db.dump().unwrap();
  125. #[cfg(feature = "tv")]
  126. {
  127. ui::run_tv();
  128. }
  129. }