summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-06-25 21:47:23 -0600
committermo khan <mo@mokhan.ca>2025-06-25 21:47:23 -0600
commit4edbf0571e161f8503e7513e7675cd3afc2897de (patch)
tree7d25166a5e9d3047f246212e96c526e3b2bc8fc1
parent98268f8a3d4197685e18197fec277fb5dcb06d39 (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.md15
-rwxr-xr-xspendr10
-rw-r--r--src/cli.rs21
-rw-r--r--src/main.rs187
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
diff --git a/spendr b/spendr
index 5a58ac1..81856d5 100755
--- a/spendr
+++ b/spendr
@@ -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
diff --git a/src/cli.rs b/src/cli.rs
index 0c4a815..dd23725 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -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);
}
}
}