diff options
| author | mo khan <mo@mokhan.ca> | 2025-06-25 21:47:23 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2025-06-25 21:47:23 -0600 |
| commit | 4edbf0571e161f8503e7513e7675cd3afc2897de (patch) | |
| tree | 7d25166a5e9d3047f246212e96c526e3b2bc8fc1 | |
| parent | 98268f8a3d4197685e18197fec277fb5dcb06d39 (diff) | |
refactor: simplify to single import command
- Remove smart-import, import, and import-dir commands
- Consolidate everything into a single 'import' command
- Auto-detection is the default behavior
- Optional --bank flag for manual override when needed
- Update documentation to reflect the simplified interface
Now there's just ONE import command:
cargo run -- import ./data
This reduces cognitive load and makes the tool much easier to use.
š¤ Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
| -rw-r--r-- | SIMPLE_USAGE.md | 15 | ||||
| -rwxr-xr-x | spendr | 10 | ||||
| -rw-r--r-- | src/cli.rs | 21 | ||||
| -rw-r--r-- | src/main.rs | 187 |
4 files changed, 31 insertions, 202 deletions
diff --git a/SIMPLE_USAGE.md b/SIMPLE_USAGE.md index 7bb7bdc..c08272a 100644 --- a/SIMPLE_USAGE.md +++ b/SIMPLE_USAGE.md @@ -4,7 +4,7 @@ ```bash # Import EVERYTHING (banking + investments) from a directory -cargo run -- smart-import ./data +cargo run -- import ./data # Or use the helper script ./spendr import ./data @@ -53,7 +53,7 @@ That's it! This command will: ### See What Would Be Imported (Dry Run) ```bash -cargo run -- smart-import ./data --dry-run +cargo run -- import ./data --dry-run ``` ### View Your Financial Summary @@ -89,13 +89,10 @@ cargo run -- investment --export-csv ./exports ## Need Help? -If the smart import misses something, you can still use specific imports: +If the auto-detection misses something, you can override with a specific bank: ```bash -# For a specific bank file -cargo run -- import --bank cibc --file ./cibc.csv - -# For investment data -cargo run -- investment --import-wealthsimple ./investment-data +# Force a specific bank type +cargo run -- import ./cibc.csv --bank cibc ``` -But you shouldn't need to - the smart import handles 99% of cases!
\ No newline at end of file +But you shouldn't need to - the auto-detection handles 99% of cases!
\ No newline at end of file @@ -1,12 +1,6 @@ #!/bin/bash # Simple wrapper script for spendr -# Usage: ./spendr import ./data +# Just pass through all commands to cargo run -if [ "$1" = "import" ] && [ -n "$2" ]; then - # User just wants to import - use smart import - cargo run -- smart-import "$2" -else - # Pass through all other commands - cargo run -- "$@" -fi
\ No newline at end of file +cargo run -- "$@"
\ No newline at end of file @@ -10,31 +10,16 @@ pub struct Cli { #[derive(Subcommand)] pub enum Commands { - // New unified import command - just point at a path and it figures everything out - SmartImport { + Import { #[arg(help = "Path to file or directory to import (auto-detects everything)")] path: String, #[arg(long, help = "Show what would be imported without actually importing")] dry_run: bool, - }, - Import { #[arg( long, - help = "Bank type: account_activity, cibc, bmo, hm_bank, td, simplii, wise, investment, or pdf_td_loc" + help = "Override auto-detection with specific bank type: cibc, bmo, td, simplii, wise" )] - bank: String, - #[arg(long, help = "Path to CSV or PDF file")] - file: String, - }, - ImportDir { - #[arg(help = "Directory containing transaction CSV and PDF files")] - directory: String, - #[arg(long, help = "Show what would be imported without actually importing")] - dry_run: bool, - #[arg(long, help = "Continue processing files even if some fail")] - continue_on_error: bool, - #[arg(long, help = "Show detailed validation results for each file")] - validate: bool, + bank: Option<String>, }, Summary { #[arg(short, long, help = "Show spending by category")] diff --git a/src/main.rs b/src/main.rs index f87a146..8c9fc92 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,8 +18,6 @@ use db::{ get_recent_transactions_filtered, get_spending_by_category_filtered, get_spending_by_month, get_yearly_summary, insert_transactions, set_budget, save_portfolios, load_portfolios, }; -use detector::detect_bank_files; -use import::{create_import_result, validate_transactions, detect_internal_duplicates}; use parser::parse_transactions; use smart_import::{smart_import, print_import_summary}; @@ -28,174 +26,29 @@ async fn main() -> anyhow::Result<()> { let cli = Cli::parse(); match cli.command { - Commands::SmartImport { path, dry_run } => { - println!("š Spendr Smart Import - I'll figure out everything for you!"); - println!(); - - match smart_import(&path, dry_run).await { - Ok(stats) => { - print_import_summary(&stats); - - if dry_run { - println!("\nš” This was a dry run. To actually import, run without --dry-run:"); - println!(" cargo run -- smart-import {}", path); - } - } - Err(e) => { - println!("ā Import failed: {}", e); - return Err(e); - } - } - } - Commands::Import { bank, file } => { - let txns = parse_transactions(&bank, &file)?; - let count = insert_transactions(&txns)?; - println!("Imported {} transactions from {}", count, bank); - } - Commands::ImportDir { directory, dry_run, continue_on_error, validate } => { - println!("š Scanning directory: {}", directory); - - let detected_files = detect_bank_files(&directory)?; - - if detected_files.is_empty() { - println!("ā No transaction files detected in directory"); - return Ok(()); - } - - println!("š Detected {} transaction file(s):", detected_files.len()); - for file in &detected_files { - println!( - " {} -> {} (confidence: {:.0}%)", - file.path.split('/').last().unwrap_or(&file.path), - file.bank.to_uppercase(), - file.confidence * 100.0 - ); - } - println!(); - - // Pre-validation phase if requested - if validate || dry_run { - println!("š Validating files..."); - let mut validation_summary = Vec::new(); - - for file in &detected_files { - match parse_transactions(&file.bank, &file.path) { - Ok(txns) => { - let validation_errors = validate_transactions(&txns); - let duplicates = detect_internal_duplicates(&txns); - - let filename = file.path.split('/').last().unwrap_or(&file.path); - validation_summary.push((filename, txns.len(), validation_errors.len(), duplicates)); - - if validate { - println!(" š {} ({} txns)", filename, txns.len()); - if !validation_errors.is_empty() { - println!(" ā ļø {} validation warnings", validation_errors.len()); - for (_i, error) in validation_errors.iter().enumerate().take(3) { - println!(" ⢠Row {}: {} - {}", - error.transaction_index + 1, error.field, error.error); - } - if validation_errors.len() > 3 { - println!(" ... and {} more warnings", validation_errors.len() - 3); - } - } - if duplicates > 0 { - println!(" š {} potential duplicates within file", duplicates); - } - } - } - Err(e) => { - let filename = file.path.split('/').last().unwrap_or(&file.path); - validation_summary.push((filename, 0, 0, 0)); - if validate { - println!(" š {} - ā Parse error: {}", filename, e); - } - } - } - } - - if !validate { - let total_txns: usize = validation_summary.iter().map(|(_, txns, _, _)| txns).sum(); - let total_warnings: usize = validation_summary.iter().map(|(_, _, warnings, _)| warnings).sum(); - let total_duplicates: usize = validation_summary.iter().map(|(_, _, _, dups)| dups).sum(); - - println!("š Validation Summary:"); - println!(" Total transactions: {}", total_txns); - if total_warnings > 0 { - println!(" ā ļø Validation warnings: {}", total_warnings); - } - if total_duplicates > 0 { - println!(" š Potential duplicates: {}", total_duplicates); - } - } + Commands::Import { path, dry_run, bank } => { + // If user specified a bank type, use the old direct import + if let Some(bank_type) = bank { + let txns = parse_transactions(&bank_type, &path)?; + let count = insert_transactions(&txns)?; + println!("Imported {} transactions from {}", count, bank_type); + } else { + // Otherwise use smart import + println!("š Spendr Import - Auto-detecting file types..."); println!(); - } - - if dry_run { - println!("š Dry run complete - no files imported. Use without --dry-run to import."); - if !validate { - println!("š” Tip: Use --validate for detailed validation results"); - } - return Ok(()); - } - - // Import phase - println!("š„ Starting import..."); - let mut results = Vec::new(); - let mut total_imported = 0; - let mut successful_files = 0; - let mut failed_files = 0; - - for file in detected_files { - let filename = file.path.split('/').last().unwrap_or(&file.path); - print!(" {} ({})... ", filename, file.bank.to_uppercase()); - - let result = match parse_transactions(&file.bank, &file.path) { - Ok(txns) => match insert_transactions(&txns) { - Ok(count) => { - println!("ā
{} new transactions", count); - total_imported += count; - successful_files += 1; - create_import_result(file.path, file.bank, Some(&txns), Some(count), None) - } - Err(e) => { - println!("ā Database error: {}", e); - failed_files += 1; - if !continue_on_error { - return Err(e.into()); - } - create_import_result(file.path, file.bank, None, None, Some(e.to_string())) - } - }, - Err(e) => { - println!("ā Parse error: {}", e); - failed_files += 1; - if !continue_on_error { - return Err(e); + + match smart_import(&path, dry_run).await { + Ok(stats) => { + print_import_summary(&stats); + + if dry_run { + println!("\nš” This was a dry run. To actually import, run without --dry-run:"); + println!(" cargo run -- import {}", path); } - create_import_result(file.path, file.bank, None, None, Some(e.to_string())) } - }; - results.push(result); - } - - println!(); - println!("š Import complete!"); - println!(" ā
Files processed successfully: {}", successful_files); - if failed_files > 0 { - println!(" ā Files failed: {}", failed_files); - } - println!(" š New transactions imported: {}", total_imported); - - // Show detailed results if there were failures - if failed_files > 0 { - println!("\nš Detailed Results:"); - for result in results { - let filename = result.file_path.split('/').last().unwrap_or(&result.file_path); - if result.success { - println!(" ā
{}: {} transactions imported", filename, result.transactions_imported); - } else { - println!(" ā {}: {}", filename, result.error_message.unwrap_or("Unknown error".to_string())); + Err(e) => { + println!("ā Import failed: {}", e); + return Err(e); } } } |
