Refactor and add new commands and doc comments
This commit is contained in:
+98
-79
@@ -1,5 +1,4 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
//use directories::ProjectDirs;
|
||||
use std::{
|
||||
error::Error,
|
||||
fs::File,
|
||||
@@ -7,102 +6,83 @@ use std::{
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use ynabmunger::ynab;
|
||||
use ynabmunger::{Transaction, read_transactions};
|
||||
|
||||
fn output_csv(transactions: &[Transaction]) -> Result<(), Box<dyn Error>> {
|
||||
let mut writer = csv::Writer::from_writer(stdout());
|
||||
|
||||
writer
|
||||
.write_record(["Date", "Payee", "Memo", "Amount"])
|
||||
.unwrap();
|
||||
for transaction in transactions {
|
||||
let output = transaction.to_record();
|
||||
writer.write_record(output).unwrap();
|
||||
}
|
||||
writer.flush().unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Output {
|
||||
Csv,
|
||||
Ynab {
|
||||
token: String,
|
||||
plan: ynab::Lookup,
|
||||
account: ynab::Lookup,
|
||||
},
|
||||
}
|
||||
use ynabmunger::Transaction;
|
||||
use ynabmunger::csv::{output_csv, read_transactions};
|
||||
use ynabmunger::ynab::{Lookup, Ynab};
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Command {
|
||||
Csv {
|
||||
inputs: Vec<PathBuf>,
|
||||
/// List available plans in YNAB
|
||||
Plans {
|
||||
/// Your YNAB token, available from developer settings in YNAB
|
||||
#[arg(short, long, env = "YNAB_API_TOKEN", hide_env_values = true)]
|
||||
token: String,
|
||||
},
|
||||
Ynab {
|
||||
#[arg(short, long, env = "YNAB_API_TOKEN")]
|
||||
/// List available accounts in a given plan in YNAB
|
||||
Accounts {
|
||||
/// Your YNAB token, available from developer settings in YNAB
|
||||
#[arg(short, long, env = "YNAB_API_TOKEN", hide_env_values = true)]
|
||||
token: String,
|
||||
|
||||
#[arg(short, long, help = "Plan name", group = "planref")]
|
||||
/// The name of the YNAB plan to list
|
||||
#[arg(short, long, group = "planref")]
|
||||
plan: Option<String>,
|
||||
|
||||
#[arg(short = 'P', long, help = "Plan ID", group = "planref")]
|
||||
/// Alternatively, give the YNAB plan ID directly
|
||||
#[arg(short = 'P', long, group = "planref")]
|
||||
plan_id: Option<String>,
|
||||
},
|
||||
/// Convert from a bank export to a CSV you can import manually to YNAB
|
||||
Convert {
|
||||
#[arg(short, long, help = "Bank export format", default_value_t = String::from("bulder"))]
|
||||
format: String,
|
||||
|
||||
inputs: Vec<PathBuf>,
|
||||
},
|
||||
/// Read a bank export and import it directly to an account in ynab
|
||||
Import {
|
||||
/// Your YNAB token, available from developer settings in YNAB
|
||||
#[arg(short, long, env = "YNAB_API_TOKEN", hide_env_values = true)]
|
||||
token: String,
|
||||
|
||||
/// The name of the YNAB plan to import to
|
||||
#[arg(short, long, group = "planref")]
|
||||
plan: Option<String>,
|
||||
|
||||
/// Alternatively, give the YNAB plan ID directly
|
||||
#[arg(short = 'P', long, group = "planref")]
|
||||
plan_id: Option<String>,
|
||||
|
||||
#[arg(short, long, help = "Account name", group = "accountref")]
|
||||
/// The name of the YNAB account to import to
|
||||
#[arg(short, long, group = "accountref")]
|
||||
account: Option<String>,
|
||||
|
||||
#[arg(short = 'A', long, help = "Account ID", group = "accountref")]
|
||||
/// Alternatively, give the YNAB account ID directly
|
||||
#[arg(short = 'A', long, group = "accountref")]
|
||||
account_id: Option<String>,
|
||||
|
||||
/// The format of the bank export you are importing
|
||||
///
|
||||
/// This is different for every bank, and sometimes even the same
|
||||
/// bank can have different formats for different account types.
|
||||
#[arg(short, long, default_value_t = String::from("bulder"))]
|
||||
format: String,
|
||||
|
||||
inputs: Vec<PathBuf>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
struct Cli {
|
||||
#[arg(short, long, help = "Bank export format", default_value_t = String::from("bulder"))]
|
||||
format: String,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
// Inject config file stuff here
|
||||
|
||||
let inputs: Vec<PathBuf>;
|
||||
let output = match cli.command {
|
||||
Command::Ynab {
|
||||
plan,
|
||||
plan_id,
|
||||
account_id,
|
||||
account,
|
||||
inputs: inp,
|
||||
token,
|
||||
} => {
|
||||
inputs = inp;
|
||||
|
||||
let output_plan = ynab::Lookup::try_from((plan, plan_id))?;
|
||||
let output_account = ynab::Lookup::try_from((account, account_id))?;
|
||||
|
||||
Output::Ynab {
|
||||
token,
|
||||
plan: output_plan,
|
||||
account: output_account,
|
||||
}
|
||||
}
|
||||
Command::Csv { inputs: inp } => {
|
||||
inputs = inp;
|
||||
Output::Csv
|
||||
}
|
||||
};
|
||||
|
||||
// Config file reconciliation here?
|
||||
|
||||
let transactions: Vec<Transaction> = if inputs.is_empty() {
|
||||
fn read_transactions_from(
|
||||
inputs: &[PathBuf],
|
||||
format: &str,
|
||||
) -> Result<Vec<Transaction>, Box<dyn Error>> {
|
||||
Ok(if inputs.is_empty() {
|
||||
vec![Box::new(stdin()) as Box<dyn Read>]
|
||||
} else {
|
||||
inputs
|
||||
@@ -111,18 +91,57 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
.collect::<Result<Vec<_>, std::io::Error>>()?
|
||||
}
|
||||
.iter_mut()
|
||||
.map(|s| read_transactions(s, &cli.format))
|
||||
.map(|s| read_transactions(s, format))
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect();
|
||||
.collect())
|
||||
}
|
||||
|
||||
match output {
|
||||
Output::Ynab {
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
match cli.command {
|
||||
Command::Plans { token } => {
|
||||
let plans = Ynab::new(&token).list_plans()?;
|
||||
println!("Available plans:");
|
||||
for plan in plans {
|
||||
println!(" - {}", plan);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Command::Accounts {
|
||||
token,
|
||||
plan,
|
||||
plan_id,
|
||||
} => {
|
||||
let plan = Lookup::try_from((plan, plan_id))?;
|
||||
let accounts = Ynab::new(&token).list_accounts(plan)?;
|
||||
println!("Available accounts in plan:");
|
||||
for account in accounts {
|
||||
println!(" - {}", account);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Command::Import {
|
||||
token,
|
||||
plan,
|
||||
plan_id,
|
||||
account_id,
|
||||
account,
|
||||
} => ynab::Ynab::new(&token, plan, account)?.upload(&transactions),
|
||||
Output::Csv => output_csv(&transactions),
|
||||
format,
|
||||
inputs,
|
||||
} => {
|
||||
let plan = Lookup::try_from((plan, plan_id))?;
|
||||
let account = Lookup::try_from((account, account_id))?;
|
||||
|
||||
let transactions = read_transactions_from(&inputs, &format)?;
|
||||
|
||||
Ynab::new(&token).upload(&transactions, plan, account)
|
||||
}
|
||||
Command::Convert { format, inputs } => {
|
||||
let transactions = read_transactions_from(&inputs, &format)?;
|
||||
output_csv(stdout(), &transactions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user