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/*
rls*
Cargo.lock
.env

View File

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

View File

@ -5,6 +5,7 @@ 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};
@ -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!("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)?;
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 stdin = io::stdin();
@ -143,18 +144,19 @@ pub fn choose_inventory_costing_method(cmd_line_arg: String) -> Result<Inventory
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),
"2" => Ok(InventoryCostingMethod::LIFObyLotBasisDate),
"3" => Ok(InventoryCostingMethod::FIFObyLotCreationDate),
"4" => Ok(InventoryCostingMethod::FIFObyLotBasisDate),
_ => {
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") }
_ => { Err("Invalid input parameter") } // Impossible code path
}
}

View File

@ -8,6 +8,7 @@
// #[warn(dead_code)] is the default (same for unused_variables)
use std::ffi::OsString;
use std::path::PathBuf;
use std::error::Error;
@ -29,22 +30,41 @@ 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<PathBuf>,
}
#[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 set by
/// environment variables (or .env file).
/// When set, default settings will be assumed if they are not indicated by flag or option.
#[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
/// exporting 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
/// 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
/// 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")]
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. If this flag is not
/// set, the program will print/export all available reports.
/// with a menu for manually selecting which reports to print/export.
#[structopt(name = "print menu", short, long = "print-menu")]
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).
#[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<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.
#[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<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>> {
let args = Cli::from_args();
let cfg = setup::get_env()?;
println!(
"
Hello,
Hello, crypto-folk! Welcome to cryptools!
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.
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.
Note: it 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 (
raw_acct_map,

View File

@ -55,9 +55,9 @@ impl<I> ListState<I> {
pub struct PrintWindow<'a> {
pub title: &'a str,
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_title: Vec<&'a str>,
pub to_print_by_title: Vec<(&'a str)>,
}
impl<'a> PrintWindow<'a> {

View File

@ -1,14 +1,12 @@
// 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;
@ -18,81 +16,32 @@ use crate::skip_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 inv_costing_method_arg: String,
pub lk_cutoff_date_arg: Option<String>,
pub inv_costing_method_arg: OsString,
pub lk_cutoff_date_arg: Option<OsString>,
pub output_dir_path: PathBuf,
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" => { "-" }
"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 cmd_args.file_to_import {
let input_file_path = match args.file_to_import {
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 {
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,
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,
};
let(
@ -101,7 +50,7 @@ pub (crate) fn run_setup(cmd_args: super::Cli, cfg: super::Cfg) -> Result<(PathB
like_kind_cutoff_date_string,
should_export,
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 {
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() };
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(),
home_currency: cfg.home_currency.to_uppercase(),
home_currency: args.opts.home_currency.into_string().unwrap().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: cmd_args.print_menu,
journal_entry_export: cmd_args.journal_entries_only,
print_menu: args.flags.print_menu,
journal_entry_export: args.flags.journal_entries_only,
};
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 {
like_kind_election = true;
like_kind_cutoff_date_string = date;
like_kind_cutoff_date_string = date.into_string().unwrap();
} else {
like_kind_election = false;
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;
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 {
lk_cutoff_date_opt_string = None
};