Version bump. Changes to .env. Changes to cli flags. Removed support for period date separator character. Better error handling for invalid files. Updated --help descriptions.
This commit is contained in:
parent
d3c7c8c6a3
commit
fafe538eac
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "cryptools"
|
||||
version = "0.9.4"
|
||||
version = "0.10.0"
|
||||
authors = ["scoobybejesus <scoobybejesus@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
description = "Command-line utility for processing cryptocurrency transactions into 'lots' and 'movements'."
|
||||
|
|
|
@ -188,8 +188,9 @@ Until "spot" funds are spent to pay off the margin loans, it's simply an [unreco
|
|||
|
||||
* **txDate**: As a default, this parser expects a format of `MM-dd-YY` or `MM-dd-YYYY`.
|
||||
The ISO 8601 date format (`YYYY-MM-dd` or `YY-MM-dd` both work) may be indicated by setting the environment variable `ISO_DATE` to `1` or `true`.
|
||||
The hyphen, slash, or period delimiters (`-`, `/`, or `.`) may be indicated
|
||||
by setting `DATE_SEPARATOR` to `h`, `s`, or `p`, respectively (hyphen, `-`, is default).
|
||||
The hyphen date separator character (`-`) is the default. The slash date separator character (`/`) may be indicated
|
||||
by setting the `DATE_SEPARATOR_IS_SLASH` environment variable (or in .env file) to `1` or `true`,
|
||||
or by passing the `date_separator_is_slash` command line flag.
|
||||
|
||||
* **proceeds**: This is can be any **positive** number that will parse into a floating point 32-bit number,
|
||||
as long as the **decimal separator** is a **period**.
|
||||
|
|
|
@ -2,18 +2,20 @@
|
|||
##
|
||||
## If the defaults below are not suitable, copy this .env.example into a new .env file,
|
||||
## uncomment the respective enviroment variable, and set the value according to your needs.
|
||||
## Alternatively, command line flags are available for ISO_DATE and DATE_SEPARATOR_SWITCH.
|
||||
## Command line flags will override enviroment variables.
|
||||
|
||||
# Setting to `TRUE` or `1` will cause the program to expect the `txDate` field in the `file_to_import` to use
|
||||
# the format YYYY-MM-dd or YY-MM-dd (or YYYY/MM/dd or YY/MM/dd, depending on the date-separator option)
|
||||
# the format YYYY-MM-dd or YY-MM-dd (or YYYY/MM/dd or YY/MM/dd, depending on the date-separator character)
|
||||
# instead of the default US-style MM-dd-YYYY or MM-dd-YY (or MM/dd/YYYY or MM/dd/YY, depending on the
|
||||
# date separator option).
|
||||
# (bool; default is FALSE/0)
|
||||
#ISO_DATE=0
|
||||
|
||||
# Choose "h", "s", or "p" for hyphen, slash, or period (i.e., "-", "/", or ".") to indicate the separator
|
||||
# character used in the `file_to_import` `txDate` column (i.e. 2017/12/31, 2017-12-31, or 2017.12.31).
|
||||
# (String; default is 'h')
|
||||
#DATE_SEPARATOR=h
|
||||
# Switches the default date separator from hyphen to slash (i.e., from "-" to "/") to indicate the separator
|
||||
# character used in the file_to_import txDate column (i.e. 2017-12-31 to 2017/12/31).
|
||||
# (bool; default is FALSE/0)
|
||||
#DATE_SEPARATOR_IS_SLASH=0
|
||||
|
||||
# Home currency (currency in which all resulting reports are denominated).
|
||||
# (String; default is 'USD')
|
||||
|
|
|
@ -5,6 +5,7 @@ use std::error::Error;
|
|||
use std::io::{self, BufRead};
|
||||
use std::process;
|
||||
use std::path::PathBuf;
|
||||
use std::fs::File;
|
||||
|
||||
use chrono::NaiveDate;
|
||||
use rustyline::completion::{Completer, FilenameCompleter, Pair};
|
||||
|
@ -21,7 +22,7 @@ use crptls::string_utils;
|
|||
pub fn choose_file_for_import(flag_to_accept_cli_args: bool) -> Result<PathBuf, Box<dyn Error>> {
|
||||
|
||||
if flag_to_accept_cli_args {
|
||||
println!("WARN: Flag to 'accept args' was set, but 'file' is missing.\n");
|
||||
println!("\nWARN: Flag to 'accept args' was set, but 'file_to_import' is missing.\n");
|
||||
}
|
||||
|
||||
println!("Please input a file (absolute or relative path) to import: ");
|
||||
|
@ -30,6 +31,9 @@ pub fn choose_file_for_import(flag_to_accept_cli_args: bool) -> Result<PathBuf,
|
|||
|
||||
if has_tilde {
|
||||
choose_file_for_import(flag_to_accept_cli_args)
|
||||
} else if File::open(&file_string).is_err() {
|
||||
println!("WARN: Invalid file path.");
|
||||
choose_file_for_import(false)
|
||||
} else {
|
||||
Ok( PathBuf::from(file_string) )
|
||||
}
|
||||
|
@ -125,19 +129,19 @@ pub fn choose_inventory_costing_method(cmd_line_arg: String) -> Result<Inventory
|
|||
|
||||
let method = _costing_method(cmd_line_arg)?;
|
||||
|
||||
fn _costing_method(cmd_line_arg: String) -> Result<InventoryCostingMethod, Box<dyn Error>> {
|
||||
fn _costing_method(env_var_arg: String) -> Result<InventoryCostingMethod, Box<dyn Error>> {
|
||||
|
||||
let mut input = String::new();
|
||||
let stdin = io::stdin();
|
||||
stdin.lock().read_line(&mut input).expect("Failed to read stdin");
|
||||
|
||||
match input.trim() { // Without .trim(), there's a hidden \n or something preventing the match
|
||||
"" => Ok(inv_costing_from_cmd_arg(cmd_line_arg)?),
|
||||
"" => Ok(inv_costing_from_cmd_arg(env_var_arg)?),
|
||||
"1" => Ok(InventoryCostingMethod::LIFObyLotCreationDate),
|
||||
"2" => Ok(InventoryCostingMethod::LIFObyLotBasisDate),
|
||||
"3" => Ok(InventoryCostingMethod::FIFObyLotCreationDate),
|
||||
"4" => Ok(InventoryCostingMethod::FIFObyLotBasisDate),
|
||||
_ => { println!("Invalid choice. Please enter a valid choice."); _costing_method(cmd_line_arg) }
|
||||
_ => { println!("Invalid choice. Please enter a valid choice."); _costing_method(env_var_arg) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,10 +155,9 @@ pub fn inv_costing_from_cmd_arg(arg: String) -> Result<InventoryCostingMethod, &
|
|||
"3" => Ok(InventoryCostingMethod::FIFObyLotCreationDate),
|
||||
"4" => Ok(InventoryCostingMethod::FIFObyLotBasisDate),
|
||||
_ => {
|
||||
eprintln!("WARN: Invalid environment variable for 'INV_COSTING_METHOD'. Using default.");
|
||||
println!("WARN: Invalid environment variable for 'INV_COSTING_METHOD'. Using default.");
|
||||
Ok(InventoryCostingMethod::LIFObyLotCreationDate)
|
||||
}
|
||||
// _ => { Err("Invalid input parameter, probably from environment variable INV_COSTING_METHOD") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,7 +174,7 @@ pub(crate) fn elect_like_kind_treatment(cutoff_date_arg: &mut Option<String>) ->
|
|||
second_date_try_from_user(&mut cutoff_date_arg).unwrap()
|
||||
} ) );
|
||||
|
||||
println!("\nUse like-kind exchange treatment through {}? [Y/n/c] ('c' to 'change') ", provided_date);
|
||||
println!("Use like-kind exchange treatment through {}? [Y/n/c] ('c' to 'change') ", provided_date);
|
||||
|
||||
let (election, date_string) = _elect_like_kind_arg(&cutoff_date_arg, provided_date)?;
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ pub fn import_and_process_final(
|
|||
|
||||
if settings.lk_treatment_enabled {
|
||||
|
||||
println!(" Applying like-kind treatment for cut-off date: {}.", settings.lk_cutoff_date);
|
||||
println!(" Applying like-kind treatment through cut-off date: {}.", settings.lk_cutoff_date);
|
||||
|
||||
import_cost_proceeds_etc::apply_like_kind_treatment(
|
||||
&settings,
|
||||
|
|
|
@ -26,7 +26,17 @@ pub(crate) fn import_from_csv(
|
|||
transactions_map: &mut HashMap<u32, Transaction>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
|
||||
let file = File::open(import_file_path)?; println!("\nCSV ledger file opened successfully.\n");
|
||||
let file = match File::open(import_file_path) {
|
||||
Ok(x) => {
|
||||
println!("\nCSV ledger file opened successfully.\n");
|
||||
x
|
||||
},
|
||||
Err(e) => {
|
||||
println!("Invalid import_file_path");
|
||||
eprintln!("System error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let mut rdr = csv::ReaderBuilder::new()
|
||||
.has_headers(true)
|
||||
|
@ -184,6 +194,7 @@ fn import_transactions(
|
|||
let acct_idx = ind - 2; // acct_num and acct_key would be idx + 1, so subtract 2 from ind to get 1
|
||||
let account_key = acct_idx as u16;
|
||||
|
||||
// TODO: implement conversion for negative numbers surrounded in parentheses
|
||||
let amount_str = field.replace(",", "");
|
||||
let amount = amount_str.parse::<d128>().unwrap();
|
||||
let amount_rounded = round_d128_1e8(&amount);
|
||||
|
|
41
src/main.rs
41
src/main.rs
|
@ -31,24 +31,24 @@ pub struct Cli {
|
|||
|
||||
/// User is instructing the program to skip the data entry wizard.
|
||||
/// When set, default settings will be assumed if they are not set by
|
||||
/// environment variables (or .env file).
|
||||
/// environment variables (or .env file) or certain command line flags.
|
||||
#[structopt(name = "accept args", short = "a", long = "accept")]
|
||||
accept_args: bool,
|
||||
|
||||
/// This flag will suppress the printing of "all" reports, except that it *will* trigger the
|
||||
/// Suppresses the printing of "all" reports, except that it *will* trigger the
|
||||
/// exporting of a txt file containing an accounting journal entry for every transaction.
|
||||
/// Individual account and transaction reports may still be printed via the print_menu
|
||||
/// with the -p flag. Note: the journal entries are not suitable for like-kind transactions.
|
||||
#[structopt(name = "journal entries", short, long = "journal-entries")]
|
||||
journal_entries_only: bool,
|
||||
|
||||
/// Once the import file has been fully processed, the user will be presented
|
||||
/// Once the file_to_import has been fully processed, the user will be presented
|
||||
/// with a menu for manually selecting which reports to print/export. If this flag is not
|
||||
/// set, the program will print/export all available reports.
|
||||
#[structopt(name = "print menu", short, long = "print-menu")]
|
||||
print_menu: bool,
|
||||
|
||||
/// This will prevent the program from writing reports to files.
|
||||
/// Prevents the program from writing reports to files.
|
||||
/// This will be ignored if -a is not set (the wizard will always ask to output).
|
||||
#[structopt(name = "suppress reports", short, long = "suppress")]
|
||||
suppress_reports: bool,
|
||||
|
@ -57,10 +57,26 @@ pub struct Cli {
|
|||
#[structopt(name = "output directory", short, long = "output", default_value = ".", parse(from_os_str))]
|
||||
output_dir_path: PathBuf,
|
||||
|
||||
/// File to be imported. By default, the program expects the `txDate` column to be formatted as %m/%d/%y.
|
||||
/// You may alter this with ISO_DATE and DATE_SEPARATOR environment variables. See .env.example for
|
||||
/// further details.
|
||||
#[structopt(name = "file", parse(from_os_str))]
|
||||
/// Causes the program to expect the `txDate` field in the file_to_import to use the format YYYY-MM-dd
|
||||
/// or YY-MM-dd (or YYYY/MM/dd or YY/MM/dd) instead of the default US-style MM-dd-YYYY or MM-dd-YY
|
||||
/// (or MM/dd/YYYY or MM/dd/YY).
|
||||
/// NOTE: this flag overrides the ISO_DATE environment variable, including if set in the .env file.
|
||||
#[structopt(name = "imported file uses ISO 8601 date format", short = "i", long = "iso")]
|
||||
iso_date: bool,
|
||||
|
||||
/// Tells the program a non-default date separator (instead of a hyphen "-", a slash "/") was used
|
||||
/// in the file_to_import `txDate` column (i.e. 2017-12-31 instead of 2017/12/31).
|
||||
/// NOTE: this flag overrides the DATE_SEPARATOR_IS_SLASH environment variable, including if set in the .env file.
|
||||
#[structopt(name = "date separator character is slash", short = "d", long = "date-separator-is-slash")]
|
||||
date_separator_is_slash: bool,
|
||||
|
||||
/// File to be imported. Some notes on the columns: (a) by default, the program expects the `txDate` column to
|
||||
/// be formatted as %m-%d-%y. You may alter this with ISO_DATE and DATE_SEPARATOR_IS_SLASH flags or environment
|
||||
/// variables; (b) the `proceeds` column and any values in transactions must have a period (".") as the decimal
|
||||
/// separator; and (c) any transactions with negative values must not be wrapped in parentheses (use the python
|
||||
/// script for sanitizing/converting negative values).
|
||||
/// See .env.example for further details on environment variables.
|
||||
#[structopt(name = "file_to_import", parse(from_os_str))]
|
||||
file_to_import: Option<PathBuf>,
|
||||
}
|
||||
|
||||
|
@ -72,10 +88,9 @@ pub struct Cfg {
|
|||
/// The default value is `false`, meaning the program will expect default US-style MM-dd-YYYY or MM-dd-YY (or MM/dd/YYYY
|
||||
/// or MM/dd/YY, depending on the date separator option).
|
||||
iso_date: bool,
|
||||
/// Set the corresponding environment variable to "h", "s", or "p" for hyphen, slash, or period (i.e., "-", "/", or ".")
|
||||
/// to indicate the separator character used in the `Cli::file_to_import` `txDate` column (i.e. 2017/12/31, 2017-12-31, or 2017.12.31).
|
||||
/// The default is `h`.
|
||||
date_separator: String,
|
||||
/// Switches the default date separator from hyphen to slash (i.e., from "-" to "/") to indicate the separator
|
||||
/// character used in the file_to_import txDate column (i.e. 2017-12-31 to 2017/12/31).
|
||||
date_separator_is_slash: bool,
|
||||
/// Home currency (currency from the `proceeds` column of the `Cli::file_to_import` and in which all resulting reports are denominated).
|
||||
/// Default is `USD`.
|
||||
home_currency: String,
|
||||
|
@ -109,7 +124,7 @@ change default program behavior.
|
|||
Note: The software is designed to import a full history. Gains and losses may be incorrect otherwise.
|
||||
");
|
||||
|
||||
let cfg = setup::get_env()?;
|
||||
let cfg = setup::get_env(&args)?;
|
||||
|
||||
let (input_file_path, settings) = setup::run_setup(args, cfg)?;
|
||||
|
||||
|
|
87
src/setup.rs
87
src/setup.rs
|
@ -3,8 +3,8 @@
|
|||
|
||||
use std::path::PathBuf;
|
||||
use std::error::Error;
|
||||
use std::process;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
|
||||
use chrono::NaiveDate;
|
||||
use dotenv;
|
||||
|
@ -17,31 +17,53 @@ use crate::skip_wizard;
|
|||
use crate::wizard;
|
||||
|
||||
|
||||
pub fn get_env() -> Result<super::Cfg, Box<dyn Error>> {
|
||||
pub fn get_env(cmd_args: &super::Cli) -> Result<super::Cfg, Box<dyn Error>> {
|
||||
|
||||
match dotenv::dotenv() {
|
||||
Ok(_path) => {println!("Setting environment variables from .env file.")},
|
||||
Ok(_path) => { println!("Setting environment variables from .env file.") },
|
||||
Err(_e) => println!("Did not find .env file.")
|
||||
}
|
||||
|
||||
let iso_date: bool = match env::var("ISO_DATE") {
|
||||
Ok(val) => {
|
||||
if val == "1" || val.to_lowercase() == "true" {
|
||||
true
|
||||
} else {
|
||||
let iso_date: bool = if cmd_args.iso_date {
|
||||
println!(" Command line flag for ISO_DATE was set. Using YY-mm-dd or YY/mm/dd.");
|
||||
true
|
||||
} else {
|
||||
match env::var("ISO_DATE") {
|
||||
Ok(val) => {
|
||||
if val == "1" || val.to_lowercase() == "true" {
|
||||
println!(" Found ISO_DATE env var: {}. Using YY-mm-dd or YY/mm/dd.", val);
|
||||
true
|
||||
} else {
|
||||
println!(" Found ISO_DATE env var: {} (not 1 or true). Using MM-dd-YY or MM/dd/YY.", val);
|
||||
false
|
||||
}
|
||||
},
|
||||
Err(_e) => {
|
||||
println!(" Using default dating convention (MM-dd-YY or MM/dd/YY).");
|
||||
false
|
||||
}
|
||||
},
|
||||
Err(_e) => false,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let date_separator: String = match env::var("DATE_SEPARATOR") {
|
||||
Ok(val) => {
|
||||
println!(" Found DATE_SEPARATOR env var: {}", val);
|
||||
val.to_lowercase()},
|
||||
Err(_e) => {
|
||||
println!(" Using default date separator (hyphen).");
|
||||
"h".to_string()},
|
||||
let date_separator_is_slash: bool = if cmd_args.date_separator_is_slash {
|
||||
println!(" Command line flag for DATE_SEPARATOR_IS_SLASH was set. Date separator set to slash (\"/\").");
|
||||
true
|
||||
} else {
|
||||
match env::var("DATE_SEPARATOR_IS_SLASH") {
|
||||
Ok(val) => {
|
||||
if val == "1" || val.to_ascii_lowercase() == "true" {
|
||||
println!(" Found DATE_SEPARATOR_IS_SLASH env var: {}. Date separator set to slash (\"/\").", val);
|
||||
true
|
||||
} else {
|
||||
println!(" Found DATE_SEPARATOR_IS_SLASH env var: {} (not 1 or true). Date separator set to hyphen (\"-\").", val);
|
||||
false
|
||||
}
|
||||
}
|
||||
Err(_e) => {
|
||||
println!(" Using default date separator, hyphen (\"-\").");
|
||||
false
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let home_currency = match env::var("HOME_CURRENCY") {
|
||||
|
@ -71,7 +93,7 @@ pub fn get_env() -> Result<super::Cfg, Box<dyn Error>> {
|
|||
|
||||
let cfg = super::Cfg {
|
||||
iso_date,
|
||||
date_separator,
|
||||
date_separator_is_slash,
|
||||
home_currency,
|
||||
lk_cutoff_date,
|
||||
inv_costing_method,
|
||||
|
@ -90,19 +112,26 @@ pub struct ArgsForImportVarsTBD {
|
|||
|
||||
pub (crate) fn run_setup(cmd_args: super::Cli, cfg: super::Cfg) -> Result<(PathBuf, ImportProcessParameters), Box<dyn Error>> {
|
||||
|
||||
let date_separator = match cfg.date_separator.as_str() {
|
||||
"h" => { "-" }
|
||||
"s" => { "/" }
|
||||
"p" => { "." }
|
||||
_ => {
|
||||
println!("\nFATAL: ENV: The date-separator arg requires either an 'h', an 's', or a 'p'.\n");
|
||||
process::exit(1)
|
||||
}
|
||||
let date_separator = match cfg.date_separator_is_slash {
|
||||
false => { "-" } // Default
|
||||
true => { "/" } // Overridden by env var or cmd line flag
|
||||
};
|
||||
|
||||
let input_file_path = match cmd_args.file_to_import {
|
||||
Some(file) => file,
|
||||
None => cli_user_choices::choose_file_for_import(cmd_args.accept_args)?
|
||||
Some(file) => {
|
||||
if File::open(&file).is_ok() {
|
||||
file
|
||||
} else {
|
||||
cli_user_choices::choose_file_for_import(cmd_args.accept_args)?
|
||||
}
|
||||
},
|
||||
None => {
|
||||
if !cmd_args.accept_args {
|
||||
wizard::shall_we_proceed()?;
|
||||
println!("Note: No file was provided as a command line arg, or the provided file wasn't found.\n");
|
||||
}
|
||||
cli_user_choices::choose_file_for_import(cmd_args.accept_args)?
|
||||
}
|
||||
};
|
||||
|
||||
let wizard_or_not_args = ArgsForImportVarsTBD {
|
||||
|
|
|
@ -20,7 +20,8 @@ pub(crate) fn wizard(args: ArgsForImportVarsTBD) -> Result<(
|
|||
PathBuf,
|
||||
), Box<dyn Error>> {
|
||||
|
||||
shall_we_proceed()?;
|
||||
println!("\nThe following wizard will guide you through your choices:\n");
|
||||
// shall_we_proceed()?;
|
||||
|
||||
let costing_method_choice = cli_user_choices::choose_inventory_costing_method(args.inv_costing_method_arg)?;
|
||||
|
||||
|
@ -39,9 +40,9 @@ pub(crate) fn wizard(args: ArgsForImportVarsTBD) -> Result<(
|
|||
Ok((costing_method_choice, like_kind_election, like_kind_cutoff_date_string, should_export, output_dir_path.to_path_buf()))
|
||||
}
|
||||
|
||||
fn shall_we_proceed() -> Result<(), Box<dyn Error>> {
|
||||
pub fn shall_we_proceed() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
println!("Shall we proceed? [Y/n] ");
|
||||
println!("\n Shall we proceed? [Y/n] ");
|
||||
|
||||
_proceed()?;
|
||||
|
||||
|
@ -64,7 +65,7 @@ fn shall_we_proceed() -> Result<(), Box<dyn Error>> {
|
|||
|
||||
fn export_reports_to_output_dir(output_dir_path: PathBuf) -> Result<(bool, PathBuf), Box<dyn Error>> {
|
||||
|
||||
println!("\nThe directory currently selected for exporting reports is: {}", output_dir_path.to_str().unwrap());
|
||||
println!("The directory currently selected for exporting reports is: {}", output_dir_path.to_str().unwrap());
|
||||
|
||||
if output_dir_path.to_str().unwrap() == "." {
|
||||
println!(" (A 'dot' denotes the default value: current working directory.)");
|
||||
|
|
Loading…
Reference in New Issue