main.rs 3.7 KB

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