Compare commits
No commits in common. "90c55062bf0e9309d2f17afd5489da829049335e" and "3f9a09465d43f1b5503795fb1359363a04e89d1d" have entirely different histories.
90c55062bf
...
3f9a09465d
32
.env.example
32
.env.example
|
@ -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
|
|
@ -5,4 +5,3 @@
|
|||
.vscode/*
|
||||
rls*
|
||||
Cargo.lock
|
||||
.env
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
106
src/main.rs
106
src/main.rs
|
@ -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,
|
||||
|
|
|
@ -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> {
|
||||
|
|
83
src/setup.rs
83
src/setup.rs
|
@ -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))
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue