From 9d3564eb7f26fb6014890bb23d5b562aeb5729f5 Mon Sep 17 00:00:00 2001 From: scoobybejesus Date: Sun, 11 Oct 2020 12:00:46 -0400 Subject: [PATCH] Remove cmd line options, and add to .env file. Bump version. --- .env.example | 32 ++++++++++++ .gitignore | 1 + Cargo.toml | 3 +- src/cli_user_choices.rs | 20 ++++---- src/main.rs | 106 ++++++++++++++++++---------------------- src/setup.rs | 85 +++++++++++++++++++++++++------- src/skip_wizard.rs | 2 +- src/wizard.rs | 2 +- 8 files changed, 161 insertions(+), 90 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..68c7215 --- /dev/null +++ b/.env.example @@ -0,0 +1,32 @@ +## CONFIGURATION +## +## 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. + +# 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) +# 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 + +# Home currency (currency in which all resulting reports are denominated). +# (String; default is 'USD') +#HOME_CURRENCY=USD + +# 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. +# (Optional; default is not set) +#LK_CUTOFF_DATE=YYYY-mm-DD + +#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. +# (String: default is '1') +#INV_COSTING_METHOD=1 \ No newline at end of file diff --git a/.gitignore b/.gitignore index dc16f85..b047eef 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ .vscode/* rls* Cargo.lock +.env \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 0c4e748..cb0ddf6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cryptools" -version = "0.8.5" +version = "0.9.0" authors = ["scoobybejesus "] edition = "2018" description = "Command-line utility for processing cryptocurrency transactions into 'lots' and 'movements'." @@ -25,6 +25,7 @@ structopt = "0.2.10" rustyline = "5.0.0" tui = "0.5" termion = "1.5" +dotenv = "0.14.1" [profile.release] lto = true \ No newline at end of file diff --git a/src/cli_user_choices.rs b/src/cli_user_choices.rs index ca1430d..93918ec 100644 --- a/src/cli_user_choices.rs +++ b/src/cli_user_choices.rs @@ -5,7 +5,6 @@ use std::error::Error; use std::io::{self, BufRead}; use std::process; use std::path::PathBuf; -use std::ffi::OsString; use chrono::NaiveDate; use rustyline::completion::{Completer, FilenameCompleter, Pair}; @@ -116,7 +115,7 @@ fn _get_path() -> Result<(String, bool), Box> { } } -pub fn choose_inventory_costing_method(cmd_line_arg: OsString) -> Result> { +pub fn choose_inventory_costing_method(cmd_line_arg: String) -> Result> { println!("Choose the lot inventory costing method. [Default/Chosen: {:?}]", cmd_line_arg); println!("1. LIFO according to the order the lot was created."); @@ -126,7 +125,7 @@ pub fn choose_inventory_costing_method(cmd_line_arg: OsString) -> Result Result> { + fn _costing_method(cmd_line_arg: String) -> Result> { let mut input = String::new(); let stdin = io::stdin(); @@ -144,19 +143,18 @@ pub fn choose_inventory_costing_method(cmd_line_arg: OsString) -> Result Result { +pub fn inv_costing_from_cmd_arg(arg: String) -> Result { - let inv_costing_arg = match arg.into_string().expect("Could not convert OsString to String.").trim() { - "1" => {"1"} "2" => {"2"} "3" => {"3"} "4" => {"4"} - _ => { println!("WARN: Invalid command line arg passed for 'inv_costing_method'. Using default."); "1" } - }; - - match inv_costing_arg { + match arg.trim() { "1" => Ok(InventoryCostingMethod::LIFObyLotCreationDate), "2" => Ok(InventoryCostingMethod::LIFObyLotBasisDate), "3" => Ok(InventoryCostingMethod::FIFObyLotCreationDate), "4" => Ok(InventoryCostingMethod::FIFObyLotBasisDate), - _ => { Err("Invalid input parameter") } // Impossible code path + _ => { + eprintln!("WARN: Invalid environment variable for 'INV_COSTING_METHOD'. Using default."); + Ok(InventoryCostingMethod::LIFObyLotCreationDate) + } + // _ => { Err("Invalid input parameter, probably from environment variable INV_COSTING_METHOD") } } } diff --git a/src/main.rs b/src/main.rs index cb9d4af..10c81aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,6 @@ // #[warn(dead_code)] is the default (same for unused_variables) -use std::ffi::OsString; use std::path::PathBuf; use std::error::Error; @@ -30,41 +29,22 @@ mod tests; #[structopt(name = "cryptools")] pub struct Cli { - #[structopt(flatten)] - flags: Flags, - - #[structopt(flatten)] - opts: Options, - - /// File to be imported. (The default date format is %m/%d/%y. See -i flag and -d option for more formatting choices.) - #[structopt(name = "file", parse(from_os_str))] - file_to_import: Option, -} - -#[derive(StructOpt, Debug)] -pub struct Flags { - /// User is instructing the program to skip the data entry wizard. - /// When set, default settings will be assumed if they are not indicated by flag or option. + /// When set, default settings will be assumed if they are not set by + /// environment variables (or .env file). #[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 - /// production of a txt file containing an accounting journal entry for every transaction. + /// This flag will suppress 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. The journal entry report is only suitable for non-like-kind activity. + /// 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, - /// This 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) - /// 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). - #[structopt(name = "date conforms to ISO 8601", short = "i", long = "iso")] - iso_date: bool, - /// Once the import file has been fully processed, the user will be presented - /// with a menu for manually selecting which reports to print/export. + /// 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, @@ -72,58 +52,66 @@ pub struct Flags { /// 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, -} - -#[derive(StructOpt, Debug)] -pub struct Options { - - /// 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). - #[structopt(name = "date separator character", short, long = "date-separator", default_value = "h", parse(from_os_str))] - date_separator: 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, - - /// 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 = "lk-cutoff", parse(from_os_str))] - lk_cutoff_date: Option, - - /// Inventory costing method (in terms of lot selection, i.e., LIFO, FIFO, etc.). - /// There are currently four options (1 through 4). - #[structopt(name = "method number for lot selection", 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, /// Output directory for exported reports. #[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))] + file_to_import: Option, } +/// These are the values able to be captured from environment variables. +#[derive(Debug)] +pub struct Cfg { + /// Setting the corresponding environment variable to `true` (or `1`) will cause the program to expect the `txDate` field in the + /// `Cli::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 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, + /// 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, + /// Cutoff date through which like-kind exchange treatment should be applied. You must use %y-%m-%d (or %Y-%m-%d) + /// format for like-kind cutoff date entry. The default is blank/commented/`None`. + lk_cutoff_date: Option, + /// method number for lot selection + /// 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. + /// [default: 1] + inv_costing_method: String, +} fn main() -> Result<(), Box> { let args = Cli::from_args(); + let cfg = setup::get_env()?; + println!( " - Hello, crypto-folk! Welcome to cryptools! + Hello, 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. + See .env.example for environment variables that may be set in a .env file in order to + change default program behavior. + + Note: The software is designed to import a full history. Gains and losses may be incorrect otherwise. "); - let (input_file_path, settings) = setup::run_setup(args)?; + let (input_file_path, settings) = setup::run_setup(args, cfg)?; let ( raw_acct_map, diff --git a/src/setup.rs b/src/setup.rs index 8133e74..9ade040 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,12 +1,14 @@ // Copyright (c) 2017-2019, scoobybejesus // Redistributions must include the license: https://github.com/scoobybejesus/cryptools/blob/master/LEGAL.txt -use std::ffi::OsString; +// use std::ffi::OsString; use std::path::PathBuf; use std::error::Error; use std::process; +use std::env; use chrono::NaiveDate; +use dotenv; use crptls::core_functions::ImportProcessParameters; use crptls::costing_method::InventoryCostingMethod; @@ -16,32 +18,81 @@ use crate::skip_wizard; use crate::wizard; +pub fn get_env() -> Result> { + + dotenv::dotenv().expect("Failed to read .env file"); + + let iso_date: bool = match env::var("ISO_DATE") { + Ok(val) => { + let var_lower = val.to_lowercase(); + let val_str = var_lower.as_str(); + if val_str == "1" || val == "true" { + true + } else { + false + } + }, + Err(_e) => false, + }; + + let date_separator: String = match env::var("DATE_SEPARATOR") { + Ok(val) => val.to_lowercase(), + Err(_e) => "h".to_string(), + }; + + let home_currency = match env::var("HOME_CURRENCY") { + Ok(val) => val.to_uppercase(), + Err(_e) => "USD".to_string(), + }; + + let lk_cutoff_date = match env::var("LK_CUTOFF_DATE") { + Ok(val) => Some(val), + Err(_e) => None, + }; + + let inv_costing_method = match env::var("INV_COSTING_METHOD") { + Ok(val) => val, + Err(_e) => "1".to_string(), + }; + + let cfg = super::Cfg { + iso_date, + date_separator, + home_currency, + lk_cutoff_date, + inv_costing_method, + }; + + Ok(cfg) +} + +// These fields are subject to change by the user if they use the wizard pub struct ArgsForImportVarsTBD { - pub inv_costing_method_arg: OsString, - pub lk_cutoff_date_arg: Option, + pub inv_costing_method_arg: String, + pub lk_cutoff_date_arg: Option, pub output_dir_path: PathBuf, pub suppress_reports: bool, } -pub (crate) fn run_setup(args: super::Cli) -> Result<(PathBuf, ImportProcessParameters), Box> { +pub (crate) fn run_setup(cmd_args: super::Cli, cfg: super::Cfg) -> Result<(PathBuf, ImportProcessParameters), Box> { - let date_separator = match args.opts.date_separator.into_string().unwrap().as_str() { - "h" => { "-" } + let date_separator = match cfg.date_separator.as_str() { + "h" => { "-" } "s" => { "/" } "p" => { "." } _ => { println!("\nFATAL: The date-separator arg requires either an 'h', an 's', or a 'p'.\n"); process::exit(1) } }; - let input_file_path = match args.file_to_import { + let input_file_path = match cmd_args.file_to_import { Some(file) => file, - None => cli_user_choices::choose_file_for_import(args.flags.accept_args)? + None => cli_user_choices::choose_file_for_import(cmd_args.accept_args)? }; let wizard_or_not_args = ArgsForImportVarsTBD { - inv_costing_method_arg: args.opts.inv_costing_method, - lk_cutoff_date_arg: args.opts.lk_cutoff_date, - output_dir_path: args.opts.output_dir_path, - suppress_reports: args.flags.suppress_reports, + inv_costing_method_arg: cfg.inv_costing_method, + lk_cutoff_date_arg: cfg.lk_cutoff_date, + output_dir_path: cmd_args.output_dir_path, + suppress_reports: cmd_args.suppress_reports, }; let( @@ -50,7 +101,7 @@ pub (crate) fn run_setup(args: super::Cli) -> Result<(PathBuf, ImportProcessPara like_kind_cutoff_date_string, should_export, output_dir_path, - ) = wizard_or_not(args.flags.accept_args, wizard_or_not_args)?; + ) = wizard_or_not(cmd_args.accept_args, wizard_or_not_args)?; let like_kind_cutoff_date = if like_kind_election { NaiveDate::parse_from_str(&like_kind_cutoff_date_string, "%y-%m-%d") @@ -59,17 +110,17 @@ pub (crate) fn run_setup(args: super::Cli) -> Result<(PathBuf, ImportProcessPara } else { NaiveDate::parse_from_str(&"1-1-1", "%y-%m-%d").unwrap() }; let settings = ImportProcessParameters { - input_file_uses_iso_date_style: args.flags.iso_date, + input_file_uses_iso_date_style: cfg.iso_date, input_file_date_separator: date_separator.to_string(), - home_currency: args.opts.home_currency.into_string().unwrap().to_uppercase(), + home_currency: cfg.home_currency.to_uppercase(), costing_method: costing_method_choice, lk_treatment_enabled: like_kind_election, lk_cutoff_date: like_kind_cutoff_date, lk_basis_date_preserved: true, // TODO should_export, export_path: output_dir_path, - print_menu: args.flags.print_menu, - journal_entry_export: args.flags.journal_entries_only, + print_menu: cmd_args.print_menu, + journal_entry_export: cmd_args.journal_entries_only, }; Ok((input_file_path, settings)) diff --git a/src/skip_wizard.rs b/src/skip_wizard.rs index 7eeb08f..818339b 100644 --- a/src/skip_wizard.rs +++ b/src/skip_wizard.rs @@ -25,7 +25,7 @@ pub(crate) fn skip_wizard(args: ArgsForImportVarsTBD) -> Result<( if let Some(date) = args.lk_cutoff_date_arg { like_kind_election = true; - like_kind_cutoff_date_string = date.into_string().unwrap(); + like_kind_cutoff_date_string = date; } else { like_kind_election = false; like_kind_cutoff_date_string = "1-1-1".to_string(); diff --git a/src/wizard.rs b/src/wizard.rs index d65c258..123deed 100644 --- a/src/wizard.rs +++ b/src/wizard.rs @@ -27,7 +27,7 @@ pub(crate) fn wizard(args: ArgsForImportVarsTBD) -> Result<( let mut lk_cutoff_date_opt_string; if let Some(lk_cutoff) = args.lk_cutoff_date_arg { - lk_cutoff_date_opt_string = Some(lk_cutoff.into_string().unwrap()) + lk_cutoff_date_opt_string = Some(lk_cutoff) } else { lk_cutoff_date_opt_string = None };