cryptools-mirror/src/main.rs

300 lines
10 KiB
Rust
Raw Normal View History

2019-08-25 23:57:07 +00:00
// Copyright (c) 2017-2019, scoobybejesus
2019-08-26 03:42:33 +00:00
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/blob/master/LEGAL.txt
2019-08-25 23:57:07 +00:00
#![allow(dead_code)]
#![allow(unused_variables)]
#![allow(unused_assignments)]
// Note: the above are possibly temporary, to silence "x was not used" warnings.
// #[warn(dead_code)] is the default (same for unused_variables)
use std::ffi::OsString;
use structopt::StructOpt;
use std::path::PathBuf;
use std::process;
use std::io::{self, BufRead};
use std::error::Error;
mod account;
mod transaction;
mod core_functions;
mod csv_import_accts_txns;
2019-08-25 23:57:07 +00:00
mod create_lots_mvmts;
mod import_cost_proceeds_etc;
mod cli_user_choices;
mod csv_export;
mod string_utils;
mod decimal_utils;
2019-08-25 23:57:07 +00:00
mod tests;
use crate::core_functions::ImportProcessParameters;
2019-08-25 23:57:07 +00:00
#[derive(StructOpt, Debug)]
#[structopt(name = "cryptools-rs")]
struct Cli {
/// File to be imported. (Currently, the only supported date format is %m/%d/%y.)
#[structopt(name = "file", parse(from_os_str))]
file_to_import: Option<PathBuf>,
/// Output directory for exported reports.
#[structopt(name = "output directory", short, long = "output", default_value = ".", parse(from_os_str))]
output_dir_path: PathBuf,
/// This will prevent the program from writing the CSV to file. This will be ignored if -a is not set (the wizard will always ask to output).
2019-08-25 23:57:07 +00:00
#[structopt(name = "suppress reports", short, long = "suppress")]
suppress_reports: bool,
/// Cutoff date through which like-kind exchange treatment should be applied.
/// Please use %y-%m-%d (or %Y-%m-%d) format for like-kind cutoff date entry.
#[structopt(name = "like-kind cutoff date", short, long = "cutoff", parse(from_os_str))]
cutoff_date: Option<OsString>,
/// Inventory costing method (in terms of lot selection, i.e., LIFO, FIFO, etc.). There are currently four options (1 through 4).
#[structopt(name = "method", short, long, default_value = "1", parse(from_os_str), long_help =
r" 1. LIFO according to the order the lot was created.
2. LIFO according to the basis date of the lot.
3. FIFO according to the order the lot was created.
4. FIFO according to the basis date of the lot.
")]
inv_costing_method: OsString,
/// Home currency (currency in which all resulting reports are denominated). (Only available as a command line setting.)
#[structopt(name = "home currency", short = "c", long = "currency", default_value = "USD", parse(from_os_str))]
home_currency: OsString,
/// User is instructing the program to skip the data entry wizard. When set, program will error without required command-line args.
2019-08-25 23:57:07 +00:00
#[structopt(name = "accept args", short, long = "accept")]
accept_args: bool,
}
fn main() -> Result<(), Box<dyn Error>> {
2019-08-25 23:57:07 +00:00
let args = Cli::from_args();
println!(
"
Hello, crypto-folk! Welcome to cryptools-rs!
This software will import your csv file's ledger of cryptocurrency transactions.
It will then process it by creating 'lots' and posting 'movements' to those lots.
Along the way, it will keep track of income, expenses, gains, and losses.
Note: it is designed to import a full history. Gains and losses may be incorrect otherwise.
");
let input_file_path;
let output_dir_path = args.output_dir_path;
let should_export;
let account_map;
let raw_acct_map;
let action_records_map;
let transactions_map;
let mut settings;
let like_kind_settings;
let home_currency_choice = args.home_currency
.into_string()
.expect("Home currency should be in the form of a ticker in CAPS.")
.to_uppercase();
2019-08-25 23:57:07 +00:00
let costing_method_choice;
if !args.accept_args {
shall_we_proceed()?;
2019-08-25 23:57:07 +00:00
fn shall_we_proceed() -> Result<(), Box<dyn Error>> {
2019-08-25 23:57:07 +00:00
println!("Shall we proceed? [Y/n] ");
_proceed()?;
2019-08-25 23:57:07 +00:00
fn _proceed() -> Result<(), Box<dyn Error>> {
2019-08-25 23:57:07 +00:00
let mut input = String::new();
let stdin = io::stdin();
stdin.lock().read_line(&mut input)?;
match input.trim().to_ascii_lowercase().as_str() {
"y" | "ye" | "yes" | "" => { Ok(()) },
"n" | "no" => { println!("We have NOT proceeded..."); process::exit(0); },
_ => { println!("Please respond with 'y' or 'n' (or 'yes' or 'no')."); _proceed() }
}
}
Ok(())
2019-08-25 23:57:07 +00:00
}
if let Some(file) = args.file_to_import {
input_file_path = file
} else {
2019-08-29 03:22:48 +00:00
input_file_path = cli_user_choices::choose_file_for_import()?;
2019-08-25 23:57:07 +00:00
}
2019-08-29 03:22:48 +00:00
costing_method_choice = cli_user_choices::choose_inventory_costing_method()?;
2019-08-25 23:57:07 +00:00
let lk_cutoff_date_opt_string;
if let Some(lk_cutoff) = args.cutoff_date {
lk_cutoff_date_opt_string = Some(lk_cutoff.into_string().unwrap())
} else {
lk_cutoff_date_opt_string = None
};
2019-08-29 03:22:48 +00:00
let (like_kind_election, like_kind_cutoff_date) = cli_user_choices::elect_like_kind_treatment(&lk_cutoff_date_opt_string)?;
2019-08-25 23:57:07 +00:00
settings = ImportProcessParameters {
2019-08-25 23:57:07 +00:00
export_path: output_dir_path,
home_currency: home_currency_choice,
costing_method: costing_method_choice,
enable_like_kind_treatment: like_kind_election,
lk_cutoff_date_string: like_kind_cutoff_date,
};
let (
account_map1,
raw_acct_map1,
action_records_map1,
transactions_map1,
like_kind_settings1
) = core_functions::import_and_process_final(input_file_path, &settings)?;
2019-08-25 23:57:07 +00:00
account_map = account_map1;
raw_acct_map = raw_acct_map1;
action_records_map = action_records_map1;
transactions_map = transactions_map1;
like_kind_settings = like_kind_settings1;
should_export = export_reports_to_output_dir(&mut settings)?;
2019-08-25 23:57:07 +00:00
fn export_reports_to_output_dir(settings: &mut ImportProcessParameters) -> Result<(bool), Box<dyn Error>> {
2019-08-25 23:57:07 +00:00
println!("\nThe directory currently selected for exporting reports is: {}", settings.export_path.to_str().unwrap());
if &settings.export_path.to_str().unwrap() == &"." {
println!(" (A 'dot' denotes the default value: current working directory.)");
}
println!("\nExport reports to selected directory? [Y/n/c] ('c' to 'change') ");
let choice = _export(settings)?;
2019-08-25 23:57:07 +00:00
fn _export(settings: &mut ImportProcessParameters) -> Result<(bool), Box<dyn Error>> {
2019-08-25 23:57:07 +00:00
let mut input = String::new();
let stdin = io::stdin();
stdin.lock().read_line(&mut input)?;
match input.trim().to_ascii_lowercase().as_str() {
"y" | "ye" | "yes" | "" => { println!("Creating reports now."); Ok(true) },
"n" | "no" => { println!("Okay, no reports were created."); Ok(false) },
"c" | "change" => {
2019-08-29 03:22:48 +00:00
let new_dir = cli_user_choices::choose_export_dir()?;
2019-08-25 23:57:07 +00:00
settings.export_path = PathBuf::from(new_dir);
println!("Creating reports now in newly chosen path.");
Ok(true)
},
_ => { println!("Please respond with 'y', 'n', or 'c' (or 'yes' or 'no' or 'change').");
_export(settings)
}
}
}
Ok(choice)
2019-08-25 23:57:07 +00:00
}
} else {
if let Some(file) = args.file_to_import {
input_file_path = file
} else {
println!("Flag to 'accept args' was set, but 'file' is missing, though it is a required field. Exiting.");
process::exit(66); // EX_NOINPUT (66) An input file (not a system file) did not exist or was not readable
2019-08-25 23:57:07 +00:00
}
let like_kind_election;
let like_kind_cutoff_date_string: String;
if let Some(date) = args.cutoff_date {
like_kind_election = true;
like_kind_cutoff_date_string = date.into_string().unwrap();
} else {
like_kind_election = false;
like_kind_cutoff_date_string = "1-1-1".to_string();
};
let clean_inv_costing_arg = match args.inv_costing_method.clone().into_string().expect("Invalid choice on costing method. Aborting.").trim() {
"1" => {"1"} "2" => {"2"} "3" => {"3"} "4" => {"4"}
_ => { println!("WARN: Invalid command line arg passed for 'inv_costing_method'. Using default."); "1" }
};
let clean_inv_costing_arg_string = clean_inv_costing_arg.to_owned();
2019-08-25 23:57:07 +00:00
let costing_method_choice = cli_user_choices::inv_costing_from_cmd_arg(clean_inv_costing_arg_string)?;
2019-08-25 23:57:07 +00:00
settings = ImportProcessParameters {
2019-08-25 23:57:07 +00:00
export_path: output_dir_path,
home_currency: home_currency_choice,
costing_method: costing_method_choice,
enable_like_kind_treatment: like_kind_election,
lk_cutoff_date_string: like_kind_cutoff_date_string,
};
2019-08-25 23:57:07 +00:00
let (
account_map1,
raw_acct_map1,
action_records_map1,
transactions_map1,
like_kind_settings1
) = core_functions::import_and_process_final(input_file_path, &settings)?;
2019-08-25 23:57:07 +00:00
account_map = account_map1;
raw_acct_map = raw_acct_map1;
action_records_map = action_records_map1;
transactions_map = transactions_map1;
like_kind_settings = like_kind_settings1;
should_export = !args.suppress_reports;
}
if should_export {
csv_export::_1_account_sums_to_csv(
2019-08-25 23:57:07 +00:00
&settings,
&raw_acct_map,
&account_map
);
csv_export::_2_account_sums_nonzero_to_csv(
2019-08-25 23:57:07 +00:00
&account_map,
&settings,
&raw_acct_map
);
csv_export::_5_transaction_mvmt_summaries_to_csv(
2019-08-25 23:57:07 +00:00
&settings,
&action_records_map,
&raw_acct_map,
&account_map,
&transactions_map
)?;
2019-08-25 23:57:07 +00:00
}
// use tests::test;
// test::run_tests(
// &transactions_map,
// &action_records_map,
// &account_map
// );
Ok(())
// // csv_export::transactions_to_csv(&transactions);
2019-08-25 23:57:07 +00:00
// csv_export::accounts_to_csv(&accounts);
2019-08-25 23:57:07 +00:00
}