Compare commits

..

No commits in common. "90c55062bf0e9309d2f17afd5489da829049335e" and "3f9a09465d43f1b5503795fb1359363a04e89d1d" have entirely different histories.

9 changed files with 92 additions and 163 deletions

View File

@ -1,32 +0,0 @@
## 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

1
.gitignore vendored
View File

@ -5,4 +5,3 @@
.vscode/* .vscode/*
rls* rls*
Cargo.lock Cargo.lock
.env

View File

@ -1,6 +1,6 @@
[package] [package]
name = "cryptools" name = "cryptools"
version = "0.9.0" version = "0.8.5"
authors = ["scoobybejesus <scoobybejesus@users.noreply.github.com>"] authors = ["scoobybejesus <scoobybejesus@users.noreply.github.com>"]
edition = "2018" edition = "2018"
description = "Command-line utility for processing cryptocurrency transactions into 'lots' and 'movements'." description = "Command-line utility for processing cryptocurrency transactions into 'lots' and 'movements'."
@ -25,7 +25,6 @@ structopt = "0.2.10"
rustyline = "5.0.0" rustyline = "5.0.0"
tui = "0.5" tui = "0.5"
termion = "1.5" termion = "1.5"
dotenv = "0.14.1"
[profile.release] [profile.release]
lto = true lto = true

View File

@ -5,6 +5,7 @@ use std::error::Error;
use std::io::{self, BufRead}; use std::io::{self, BufRead};
use std::process; use std::process;
use std::path::PathBuf; use std::path::PathBuf;
use std::ffi::OsString;
use chrono::NaiveDate; use chrono::NaiveDate;
use rustyline::completion::{Completer, FilenameCompleter, Pair}; use rustyline::completion::{Completer, FilenameCompleter, Pair};
@ -115,7 +116,7 @@ fn _get_path() -> Result<(String, bool), Box<dyn Error>> {
} }
} }
pub fn choose_inventory_costing_method(cmd_line_arg: String) -> Result<InventoryCostingMethod, Box<dyn Error>> { pub fn choose_inventory_costing_method(cmd_line_arg: OsString) -> Result<InventoryCostingMethod, Box<dyn Error>> {
println!("Choose the lot inventory costing method. [Default/Chosen: {:?}]", cmd_line_arg); println!("Choose the lot inventory costing method. [Default/Chosen: {:?}]", cmd_line_arg);
println!("1. LIFO according to the order the lot was created."); println!("1. LIFO according to the order the lot was created.");
@ -125,7 +126,7 @@ pub fn choose_inventory_costing_method(cmd_line_arg: String) -> Result<Inventory
let method = _costing_method(cmd_line_arg)?; let method = _costing_method(cmd_line_arg)?;
fn _costing_method(cmd_line_arg: String) -> Result<InventoryCostingMethod, Box<dyn Error>> { fn _costing_method(cmd_line_arg: OsString) -> Result<InventoryCostingMethod, Box<dyn Error>> {
let mut input = String::new(); let mut input = String::new();
let stdin = io::stdin(); let stdin = io::stdin();
@ -143,18 +144,19 @@ pub fn choose_inventory_costing_method(cmd_line_arg: String) -> Result<Inventory
Ok(method) Ok(method)
} }
pub fn inv_costing_from_cmd_arg(arg: String) -> Result<InventoryCostingMethod, &'static str> { pub fn inv_costing_from_cmd_arg(arg: OsString) -> Result<InventoryCostingMethod, &'static str> {
match arg.trim() { 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 {
"1" => Ok(InventoryCostingMethod::LIFObyLotCreationDate), "1" => Ok(InventoryCostingMethod::LIFObyLotCreationDate),
"2" => Ok(InventoryCostingMethod::LIFObyLotBasisDate), "2" => Ok(InventoryCostingMethod::LIFObyLotBasisDate),
"3" => Ok(InventoryCostingMethod::FIFObyLotCreationDate), "3" => Ok(InventoryCostingMethod::FIFObyLotCreationDate),
"4" => Ok(InventoryCostingMethod::FIFObyLotBasisDate), "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") }
} }
} }

View File

@ -8,6 +8,7 @@
// #[warn(dead_code)] is the default (same for unused_variables) // #[warn(dead_code)] is the default (same for unused_variables)
use std::ffi::OsString;
use std::path::PathBuf; use std::path::PathBuf;
use std::error::Error; use std::error::Error;
@ -29,22 +30,41 @@ mod tests;
#[structopt(name = "cryptools")] #[structopt(name = "cryptools")]
pub struct Cli { 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<PathBuf>,
}
#[derive(StructOpt, Debug)]
pub struct Flags {
/// User is instructing the program to skip the data entry wizard. /// User is instructing the program to skip the data entry wizard.
/// When set, default settings will be assumed if they are not set by /// When set, default settings will be assumed if they are not indicated by flag or option.
/// environment variables (or .env file).
#[structopt(name = "accept args", short = "a", long = "accept")] #[structopt(name = "accept args", short = "a", long = "accept")]
accept_args: bool, accept_args: bool,
/// This flag will suppress the printing of "all" reports, except that it *will* trigger the /// 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. /// production 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 /// 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. /// with the -p flag. The journal entry report is only suitable for non-like-kind activity.
#[structopt(name = "journal entries", short, long = "journal-entries")] #[structopt(name = "journal entries", short, long = "journal-entries")]
journal_entries_only: bool, 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 /// Once the import file 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 /// with a menu for manually selecting which reports to print/export.
/// set, the program will print/export all available reports.
#[structopt(name = "print menu", short, long = "print-menu")] #[structopt(name = "print menu", short, long = "print-menu")]
print_menu: bool, print_menu: bool,
@ -52,66 +72,58 @@ pub struct Cli {
/// This will be ignored if -a is not set (the wizard will always ask to output). /// This will be ignored if -a is not set (the wizard will always ask to output).
#[structopt(name = "suppress reports", short, long = "suppress")] #[structopt(name = "suppress reports", short, long = "suppress")]
suppress_reports: bool, 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<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 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. /// Output directory for exported reports.
#[structopt(name = "output directory", short, long = "output", default_value = ".", parse(from_os_str))] #[structopt(name = "output directory", short, long = "output", default_value = ".", parse(from_os_str))]
output_dir_path: PathBuf, 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<PathBuf>,
} }
/// 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<String>,
/// method number for lot selection <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<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
let args = Cli::from_args(); let args = Cli::from_args();
let cfg = setup::get_env()?;
println!( println!(
" "
Hello, Hello, crypto-folk! Welcome to cryptools!
This software will import your csv file's ledger of cryptocurrency transactions. 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. 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. Along the way, it will keep track of income, expenses, gains, and losses.
See .env.example for environment variables that may be set in a .env file in order to Note: it is designed to import a full history. Gains and losses may be incorrect otherwise.
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, cfg)?; let (input_file_path, settings) = setup::run_setup(args)?;
let ( let (
raw_acct_map, raw_acct_map,

View File

@ -55,9 +55,9 @@ impl<I> ListState<I> {
pub struct PrintWindow<'a> { pub struct PrintWindow<'a> {
pub title: &'a str, pub title: &'a str,
pub should_quit: bool, pub should_quit: bool,
pub tasks: ListState<&'a str>, pub tasks: ListState<(&'a str)>,
pub to_print_by_idx: Vec<usize>, pub to_print_by_idx: Vec<usize>,
pub to_print_by_title: Vec<&'a str>, pub to_print_by_title: Vec<(&'a str)>,
} }
impl<'a> PrintWindow<'a> { impl<'a> PrintWindow<'a> {

View File

@ -1,14 +1,12 @@
// Copyright (c) 2017-2019, scoobybejesus // Copyright (c) 2017-2019, scoobybejesus
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools/blob/master/LEGAL.txt // 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::path::PathBuf;
use std::error::Error; use std::error::Error;
use std::process; use std::process;
use std::env;
use chrono::NaiveDate; use chrono::NaiveDate;
use dotenv;
use crptls::core_functions::ImportProcessParameters; use crptls::core_functions::ImportProcessParameters;
use crptls::costing_method::InventoryCostingMethod; use crptls::costing_method::InventoryCostingMethod;
@ -18,81 +16,32 @@ use crate::skip_wizard;
use crate::wizard; use crate::wizard;
pub fn get_env() -> Result<super::Cfg, Box<dyn Error>> {
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 struct ArgsForImportVarsTBD {
pub inv_costing_method_arg: String, pub inv_costing_method_arg: OsString,
pub lk_cutoff_date_arg: Option<String>, pub lk_cutoff_date_arg: Option<OsString>,
pub output_dir_path: PathBuf, pub output_dir_path: PathBuf,
pub suppress_reports: bool, pub suppress_reports: bool,
} }
pub (crate) fn run_setup(cmd_args: super::Cli, cfg: super::Cfg) -> Result<(PathBuf, ImportProcessParameters), Box<dyn Error>> { pub (crate) fn run_setup(args: super::Cli) -> Result<(PathBuf, ImportProcessParameters), Box<dyn Error>> {
let date_separator = match cfg.date_separator.as_str() { let date_separator = match args.opts.date_separator.into_string().unwrap().as_str() {
"h" => { "-" } "h" => { "-" }
"s" => { "/" } "s" => { "/" }
"p" => { "." } "p" => { "." }
_ => { println!("\nFATAL: The date-separator arg requires either an 'h', an 's', or a 'p'.\n"); process::exit(1) } _ => { println!("\nFATAL: The date-separator arg requires either an 'h', an 's', or a 'p'.\n"); process::exit(1) }
}; };
let input_file_path = match cmd_args.file_to_import { let input_file_path = match args.file_to_import {
Some(file) => file, Some(file) => file,
None => cli_user_choices::choose_file_for_import(cmd_args.accept_args)? None => cli_user_choices::choose_file_for_import(args.flags.accept_args)?
}; };
let wizard_or_not_args = ArgsForImportVarsTBD { let wizard_or_not_args = ArgsForImportVarsTBD {
inv_costing_method_arg: cfg.inv_costing_method, inv_costing_method_arg: args.opts.inv_costing_method,
lk_cutoff_date_arg: cfg.lk_cutoff_date, lk_cutoff_date_arg: args.opts.lk_cutoff_date,
output_dir_path: cmd_args.output_dir_path, output_dir_path: args.opts.output_dir_path,
suppress_reports: cmd_args.suppress_reports, suppress_reports: args.flags.suppress_reports,
}; };
let( let(
@ -101,7 +50,7 @@ pub (crate) fn run_setup(cmd_args: super::Cli, cfg: super::Cfg) -> Result<(PathB
like_kind_cutoff_date_string, like_kind_cutoff_date_string,
should_export, should_export,
output_dir_path, output_dir_path,
) = wizard_or_not(cmd_args.accept_args, wizard_or_not_args)?; ) = wizard_or_not(args.flags.accept_args, wizard_or_not_args)?;
let like_kind_cutoff_date = if like_kind_election { let like_kind_cutoff_date = if like_kind_election {
NaiveDate::parse_from_str(&like_kind_cutoff_date_string, "%y-%m-%d") NaiveDate::parse_from_str(&like_kind_cutoff_date_string, "%y-%m-%d")
@ -110,17 +59,17 @@ pub (crate) fn run_setup(cmd_args: super::Cli, cfg: super::Cfg) -> Result<(PathB
} else { NaiveDate::parse_from_str(&"1-1-1", "%y-%m-%d").unwrap() }; } else { NaiveDate::parse_from_str(&"1-1-1", "%y-%m-%d").unwrap() };
let settings = ImportProcessParameters { let settings = ImportProcessParameters {
input_file_uses_iso_date_style: cfg.iso_date, input_file_uses_iso_date_style: args.flags.iso_date,
input_file_date_separator: date_separator.to_string(), input_file_date_separator: date_separator.to_string(),
home_currency: cfg.home_currency.to_uppercase(), home_currency: args.opts.home_currency.into_string().unwrap().to_uppercase(),
costing_method: costing_method_choice, costing_method: costing_method_choice,
lk_treatment_enabled: like_kind_election, lk_treatment_enabled: like_kind_election,
lk_cutoff_date: like_kind_cutoff_date, lk_cutoff_date: like_kind_cutoff_date,
lk_basis_date_preserved: true, // TODO lk_basis_date_preserved: true, // TODO
should_export, should_export,
export_path: output_dir_path, export_path: output_dir_path,
print_menu: cmd_args.print_menu, print_menu: args.flags.print_menu,
journal_entry_export: cmd_args.journal_entries_only, journal_entry_export: args.flags.journal_entries_only,
}; };
Ok((input_file_path, settings)) Ok((input_file_path, settings))

View File

@ -25,7 +25,7 @@ pub(crate) fn skip_wizard(args: ArgsForImportVarsTBD) -> Result<(
if let Some(date) = args.lk_cutoff_date_arg { if let Some(date) = args.lk_cutoff_date_arg {
like_kind_election = true; like_kind_election = true;
like_kind_cutoff_date_string = date; like_kind_cutoff_date_string = date.into_string().unwrap();
} else { } else {
like_kind_election = false; like_kind_election = false;
like_kind_cutoff_date_string = "1-1-1".to_string(); like_kind_cutoff_date_string = "1-1-1".to_string();

View File

@ -27,7 +27,7 @@ pub(crate) fn wizard(args: ArgsForImportVarsTBD) -> Result<(
let mut lk_cutoff_date_opt_string; let mut lk_cutoff_date_opt_string;
if let Some(lk_cutoff) = args.lk_cutoff_date_arg { if let Some(lk_cutoff) = args.lk_cutoff_date_arg {
lk_cutoff_date_opt_string = Some(lk_cutoff) lk_cutoff_date_opt_string = Some(lk_cutoff.into_string().unwrap())
} else { } else {
lk_cutoff_date_opt_string = None lk_cutoff_date_opt_string = None
}; };