Accept CSV file path as command line argument

Output is now written to the same directory as the input file.
This commit is contained in:
2026-03-23 10:41:54 +01:00
parent 623cf12a1c
commit 1929b3ed49

View File

@@ -1,11 +1,12 @@
use askama::Template; use askama::Template;
use chrono::Utc; use chrono::Utc;
use std::env;
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
mod invoice_generator; mod invoice_generator;
use invoice_generator::{group_by_customer, read_csv_file, Batch, Customer}; use invoice_generator::{group_by_customer, read_csv_file, Customer};
fn fmt(v: f64) -> String { fn fmt(v: f64) -> String {
format!("{:.2}", v) format!("{:.2}", v)
@@ -101,40 +102,35 @@ struct CustomerTemplate {
} }
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let data_dir = Path::new("input"); let args: Vec<String> = env::args().collect();
let output_dir = Path::new("output");
if args.len() != 2 {
eprintln!("Usage: {} <csv-file>", args[0]);
std::process::exit(1);
}
let input_path = Path::new(&args[1]);
if !input_path.exists() {
eprintln!("Error: File not found: {:?}", input_path);
std::process::exit(1);
}
let output_dir = input_path.parent().unwrap_or(Path::new("."));
if !output_dir.exists() { if !output_dir.exists() {
fs::create_dir_all(output_dir)?; fs::create_dir_all(output_dir)?;
} }
let mut batches: Vec<Batch> = Vec::new(); let batch = read_csv_file(input_path)?;
println!(
"Loaded {} transactions from {}",
batch.transactions.len(),
batch.filename
);
for entry in fs::read_dir(data_dir)? { let batch_filename = batch.filename.clone();
let entry = entry?; let customers = group_by_customer(&[batch]);
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("csv") {
match read_csv_file(&path) {
Ok(batch) => {
println!(
"Loaded {} transactions from {}",
batch.transactions.len(),
batch.filename
);
batches.push(batch);
}
Err(e) => {
eprintln!("Error reading {:?}: {}", path, e);
}
}
}
}
batches.sort_by(|a, b| a.filename.cmp(&b.filename));
let batch_filenames: Vec<String> = batches.iter().map(|b| b.filename.clone()).collect();
let customers = group_by_customer(&batches);
let index_customers: Vec<(String, usize)> = customers let index_customers: Vec<(String, usize)> = customers
.iter() .iter()
@@ -142,8 +138,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.collect(); .collect();
let html = IndexTemplate { let html = IndexTemplate {
customers: index_customers, customers: index_customers.clone(),
batches: batch_filenames.clone(), batches: vec![batch_filename.clone()],
} }
.render() .render()
.unwrap(); .unwrap();
@@ -156,7 +152,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let prepared = PreparedCustomer::from_customer(customer); let prepared = PreparedCustomer::from_customer(customer);
let customer_html = CustomerTemplate { let customer_html = CustomerTemplate {
customer: prepared, customer: prepared,
batches: batch_filenames.clone(), batches: vec![batch_filename.clone()],
generated_date: generated_date.clone(), generated_date: generated_date.clone(),
} }
.render() .render()
@@ -167,8 +163,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
println!( println!(
"\nGenerated {} customer invoices in output/", "\nGenerated {} customer invoices in {:?}",
customer_count customer_count, output_dir
); );
Ok(()) Ok(())