Initial commit.

This commit is contained in:
scoobybejesus 2019-08-25 19:57:07 -04:00
commit 2eeffb057d
18 changed files with 4380 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
/target
**/*.rs.bk
/private
.DS_Store
.vscode/*
rls*

1
AUTHORS Normal file
View File

@ -0,0 +1 @@
scoobybejesus <scoobybejesus@gmail.com>

730
Cargo.lock generated Normal file
View File

@ -0,0 +1,730 @@
[[package]]
name = "aho-corasick"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "arrayref"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "arrayvec"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "atty"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "autocfg"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "backtrace"
version = "0.3.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "backtrace-sys"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "base64"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitflags"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "blake2b_simd"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
"constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bstr"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "byteorder"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cc"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "chrono"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "chrono-tz"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
"parse-zoneinfo 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "clap"
version = "2.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "constant_time_eq"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "crossbeam-utils"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cryptools-rs"
version = "0.6.0"
dependencies = [
"chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono-tz 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"decimal 2.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"rustyline 5.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
"structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "csv"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bstr 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
"csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "csv-core"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "decimal"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"ord_subset 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "dirs"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "dirs-sys"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "failure"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"backtrace 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "failure_derive"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
"synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "heck"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "itoa"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lazy_static"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "log"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "memchr"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nix"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nodrop"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "num-integer"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-traits"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ord_subset"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "parse-zoneinfo"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro2"
version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro2"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quote"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quote"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rand_os"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "redox_syscall"
version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "redox_users"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
"rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-automata"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-syntax"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rust-argon2"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"blake2b_simd 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rustc-demangle"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rustc-serialize"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rustyline"
version = "5.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ryu"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_derive"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "structopt"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"structopt-derive 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "structopt-derive"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn"
version = "0.15.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "synstructure"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thread_local"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "time"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-segmentation"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-width"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "utf8parse"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vec_map"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
"checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba"
"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
"checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875"
"checksum backtrace 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)" = "1371048253fa3bac6704bfd6bbfc922ee9bdcee8881330d40f308b81cc5adc55"
"checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b"
"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd"
"checksum blake2b_simd 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bf775a81bb2d464e20ff170ac20316c7b08a43d11dbc72f0f82e8e8d3d6d0499"
"checksum bstr 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "94cdf78eb7e94c566c1f5dbe2abf8fc70a548fc902942a48c4b3a98b48ca9ade"
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
"checksum cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "b548a4ee81fccb95919d4e22cfea83c7693ebfd78f0495493178db20b3139da7"
"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
"checksum chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "77d81f58b7301084de3b958691458a53c3f7e0b1d702f77e550b6a88e3a88abe"
"checksum chrono-tz 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e0e430fad0384e4defc3dc6b1223d1b886087a8bf9b7080e5ae027f73851ea15"
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
"checksum constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120"
"checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6"
"checksum csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37519ccdfd73a75821cac9319d4fce15a81b9fcf75f951df5b9988aa3a0af87d"
"checksum csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9b5cadb6b25c77aeff80ba701712494213f4a8418fcda2ee11b6560c3ad0bf4c"
"checksum decimal 2.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e6458723bc760383275fbc02f4c769b2e5f3de782abaf5e7e0b9b7f0368a63ed"
"checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
"checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b"
"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2"
"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1"
"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba"
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce"
"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32"
"checksum ord_subset 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d7ce14664caf5b27f5656ff727defd68ae1eb75ef3c4d95259361df1eb376bef"
"checksum parse-zoneinfo 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "089a398ccdcdd77b8c38909d5a1e4b67da1bc4c9dbfe6d5b536c828eddb779e5"
"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
"checksum proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c5c2380ae88876faae57698be9e9775e3544decad214599c3a6266cca6ac802"
"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
"checksum redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d"
"checksum regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88c3d9193984285d544df4a30c23a4e62ead42edf70a4452ceb76dac1ce05c26"
"checksum regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "92b73c2a1770c255c240eaa4ee600df1704a38dc3feaa6e949e7fcd4f8dc09f9"
"checksum regex-syntax 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b143cceb2ca5e56d5671988ef8b15615733e7ee16cd348e064333b251b89343f"
"checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf"
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
"checksum rustyline 5.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f8ee0838a6594169a1c5f4bb9af0fe692cc99691941710a8cc6576395ede804e"
"checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997"
"checksum serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "fec2851eb56d010dc9a21b89ca53ee75e6528bab60c11e89d38390904982da9f"
"checksum serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "cb4dc18c61206b08dc98216c98faa0232f4337e1e1b8574551d5bad29ea1b425"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"checksum structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "16c2cdbf9cc375f15d1b4141bc48aeef444806655cd0e904207edc8d68d86ed7"
"checksum structopt-derive 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "53010261a84b37689f9ed7d395165029f9cc7abb9f56bbfe86bee2597ed25107"
"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
"checksum syn 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c65d951ab12d976b61a41cf9ed4531fc19735c6e6d84a4bb1453711e762ec731"
"checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
"checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9"
"checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

21
Cargo.toml Normal file
View File

@ -0,0 +1,21 @@
[package]
name = "cryptools-rs"
version = "0.6.0"
authors = ["scoobybejesus <scoobybejesus@users.noreply.github.com>"]
edition = "2018"
description = "This is a command-line utility for processing cryptocurrency transactions into 'lots' and 'movements'."
[dependencies]
csv = "1.0.0"
serde = { version = "1.0.75", features = ["derive"] }
serde_derive = "1.0.75"
decimal = "2.0.4"
chrono = { version = "0.4", features = ["serde"] }
chrono-tz = "0.5"
time = "0.1.42"
structopt = "0.2.10"
rustyline = "5.0.0"
[profile.release]
lto = true

31
LEGAL.txt Normal file
View File

@ -0,0 +1,31 @@
// Copyright (c) 2017-2019, scoobybejesus
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions, and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// 4. Individual files may or may not contain the entire contents of this file. If a an
// individual file is copied/redistributed/so on, this license must be pasted in place
// of the license notice on that page.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

84
README.md Normal file
View File

@ -0,0 +1,84 @@
# cryptools-rs
### Accounting library for cryptocurrency transaction activity.
It provides a way to measure cryptocurrency activity in one's home currency (the default value is USD, but anything can be used).
Reports may be exported as CSV files that reflect income/expense/gains/losses.
The activity that gets imported **must** be in a prescribed form that effectively looks like this:
|txDate |proceeds|memo |1 |2 |3 |4 |5 |
|-------|--------|--------|------|--------|--------|--------|------------|
| | | |Bank |Exchange|Exchange|Exchange|Simplewallet|
| | | |USD |BTC |BTC |XMR |XMR |
| | | |non |non |non |non |non |
|2/1/16 |0 |FIRST |-220 |0.25 | | | |
|3/1/16 |250 |SECOND | |-0.25 | |180 | |
|4/1/16 |0 |THIRD | | | |-90 |90 |
|5/1/16 |0 |FOURTH | | | |90 |-90 |
|5/2/16 |160 |FIFTH | |0.3 | |-90 | |
|6/1/16 |0 |SIXTH | |-0.3 |0.3 | | |
|7/1/16 |200 |SEVENTH | | |0.7 |-90 | |
|8/1/16 |0 |EIGHTH | |0.5 |-0.5 | | |
|9/1/16 |400 |NINTH | | |-0.5 |200 | |
|10/1/16|900 |TENTH | |1 | |-200 | |
|11/1/16|0 |ELEVENTH| |-1.5 |1.5 | | |
|12/1/16|2000 |TWELFTH | | |-1.5 |400 | |
#### CSV file components
* **txDate** is currently set to parse dates of the format MM/dd/YY.
* **proceeds** may seem tricky.
The way to understand it, since it can apply to any transaction (aside from transfers from one owned-account to another owned-account), is that this is the value transferred in the transaction.
For example, if one spends 0.01 BTC for an item at a time when BTC/USD is $10,000/BTC, then the user received value of $100, therefore the proceeds of that transaction would be $100.
This field is ignored when the user's home currency is used to purchase cryptocurrency.
* **memo** is useful for evaluating the final output but isn't important.
Currently, commas in the memo are **not** supported.
After three column of transaction metadata, the *Account* columns follow.
* *Accounts* (**1**, **2**, **3**, **4**, **5**, ...): the top row reflects the account number (which currently must start at 1 and increase sequentially).
The three other values are the *name*, *ticker*, and *margin_bool*.
*name* and *ticker* should be self-explanatory.
*margin_bool* is set usually set as 'no', 'non' (i.e., non-margin), or 'false.'
To indicate a margin account, set it as 'yes', 'margin' or 'true'.
###### Margin accounts
* Margin accounts must come in pairs, the base account and the quote account.
The base account is the coin being longed or shorted, and its ticker is reflected like normal.
The quote account is the market.
The quote account's ticker requires a different formatting.
For example, when using BTC to long XMR, the BTC account must be reflected with the ticker BTC_xmr.
#### Constraints
* *All* cryptocurrency-related activity for the user generally must be included in the input CSV file.
* There can only be either one or two accounts used in a given transaction (i.e., if a Counterparty token or Ethereum token transaction must be recorded, the XCP or ETH transaction fee must be reflected in a separate transaction/row).
* Currently, manual adjustments may need to be made to the output files in cases, for example, when the user used cryptocurrency to make atax-deductible charitable contribution.
## Installation
1. `cargo build` (or include `--release` for a non-debug build)
This will build `./cryptools-rs`.
## Usage
Run `./cryptools-rs` with no arguments (or `--help`, or `-h`) to see usage.
## Contributing
* Contributors welcome. New authors should add themselves to the `AUTHORS` file.
* Roadmap and todos: we're working through items in [Issues](https://github.com/scoobybejesus/cryptools-rs/issues); feel free to tackle or add issues.
## Legal
See LEGAL.txt

249
src/account.rs Normal file
View File

@ -0,0 +1,249 @@
// Copyright (c) 2017-2019, scoobybejesus
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
use std::rc::{Rc, Weak};
use std::cell::{Cell, RefCell};
use std::fmt;
use std::collections::{HashMap};
use chrono::{NaiveDate, NaiveTime, NaiveDateTime, DateTime, Utc, TimeZone};
use chrono_tz::US::Eastern;
use decimal::d128;
use serde_derive::{Serialize, Deserialize};
use crate::transaction::{Transaction, ActionRecord, Polarity, TxType};
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct RawAccount {
pub account_num: u16,
pub name: String,
pub ticker: String,
pub is_margin: bool,
}
impl RawAccount {
pub fn is_home_currency(&self, compare: &String) -> bool {
&self.ticker == compare
}
}
#[derive(Clone, Debug)]
pub struct Account {
pub raw_key: u16,
pub list_of_lots: RefCell<Vec<Rc<Lot>>>,
// pub vec_of_lot_keys: (RawAccount, u32),
}
impl Account {
pub fn is_home_currency(&self, compare: &String, raw_acct_map: &HashMap<u16, RawAccount>) -> bool {
let raw_acct = raw_acct_map.get(&self.raw_key).unwrap();
&raw_acct.ticker == compare
}
pub fn get_sum_of_amts_in_lots(&self) -> d128 {
let lots = self.list_of_lots.borrow();
let mut total_amount = d128!(0);
for lot in lots.iter() {
let sum = lot.get_sum_of_amts_in_lot();
total_amount += sum;
}
total_amount
}
pub fn get_sum_of_basis_in_lots(&self) -> d128 {
let lots = self.list_of_lots.borrow();
let mut total_amount = d128!(0);
for lot in lots.iter() {
let sum = lot.get_sum_of_basis_in_lot();
total_amount += sum;
}
total_amount
}
}
#[derive(Clone, Debug)]
pub struct RawMarginPair (pub Weak<RawAccount>, pub Weak<RawAccount>); // always (base_acct, quote_acct)
#[derive(Clone, Debug)]
pub struct Lot {
pub date_as_string: String,
pub date_of_first_mvmt_in_lot: NaiveDate,
pub date_for_basis_purposes: NaiveDate,
pub lot_number: u32, // Does NOT start at zero. First lot is lot 1.
pub account_key: u16,
pub movements: RefCell<Vec<Rc<Movement>>>,
}
impl Lot {
pub fn get_sum_of_amts_in_lot(&self) -> d128 {
let mut amts = d128!(0);
self.movements.borrow().iter().for_each(|movement| amts += movement.amount);
amts
}
pub fn sum_of_amts_in_lot_is_zero(&self) -> bool {
d128!(0) == Self::get_sum_of_amts_in_lot(&self)
}
pub fn get_sum_of_basis_in_lot(&self) -> d128 {
let mut amts = d128!(0);
self.movements.borrow().iter().for_each(|movement| amts += movement.cost_basis.get());
amts
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Movement {
pub amount: d128,
pub date_as_string: String,
pub date: NaiveDate,
pub transaction_key: u32,
pub action_record_key: u32,
pub cost_basis: Cell<d128>, // Initialized with 0. Set in add_cost_basis_to_movements()
pub ratio_of_amt_to_incoming_mvmts_in_a_r: d128, // Set in process_multiple_incoming_lots_and_mvmts() and incoming flow dual actionrecord transactions
pub ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell<d128>, // Set in wrap_mvmt_and_push()
pub lot_num: u32,
pub proceeds: Cell<d128>, // Initialized with 0. Set in add_proceeds_to_movements()
}
impl Movement {
pub fn get_lot(
&self,
acct_map: &HashMap<u16, Account>,
ar_map: &HashMap<u32, ActionRecord>
) -> Rc<Lot> {
let ar = ar_map.get(&self.action_record_key).unwrap();
let acct = acct_map.get(&ar.account_key).unwrap();
let lot = acct.list_of_lots.borrow()[self.lot_num as usize - 1].clone(); // lots start at 1 and indexes at 0
lot
}
pub fn ratio_of_amt_to_lots_first_mvmt(
&self,
acct_map: &HashMap<u16, Account>,
ar_map: &HashMap<u32, ActionRecord>
) -> d128 {
// println!("Lot #: {}", self.lot.lot_number);
let ar = ar_map.get(&self.action_record_key).unwrap();
let acct = acct_map.get(&ar.account_key).unwrap();
let lot = &acct.list_of_lots.borrow()[self.lot_num as usize - 1]; // lots start at 1 and indexes at 0
let borrowed_mvmt_list = lot.movements.borrow();
let ratio = self.amount / borrowed_mvmt_list.first().unwrap().amount;
// println!("ratio_of_amt_to_lots_first_mvmt: {}", ratio.abs());
ratio.abs()
}
pub fn get_gain_or_loss(&self) -> d128 {
self.proceeds.get() + self.cost_basis.get()
}
pub fn get_term(&self, acct_map: &HashMap<u16, Account>, ar_map: &HashMap<u32, ActionRecord>,) -> Term {
use time::Duration;
let ar = ar_map.get(&self.action_record_key).unwrap();
let lot = Self::get_lot(&self, acct_map, ar_map);
match ar.direction() {
Polarity::Incoming => {
let today = Utc::now();
let utc_lot_date = Self::create_date_time_from_atlantic(lot.date_for_basis_purposes, NaiveTime::from_hms_milli(12, 34, 56, 789));
// if today.signed_duration_since(self.lot.date_for_basis_purposes) > 365
if (today - utc_lot_date) > Duration::days(365) { // TODO: figure out how to instantiate today's date and convert it to compare to NaiveDate
Term::LT
}
else {
Term::ST
}
}
Polarity::Outgoing => {
let lot_date_for_basis_purposes = lot.date_for_basis_purposes;
if self.date.signed_duration_since(lot_date_for_basis_purposes) > Duration::days(365) {
return Term::LT
}
Term::ST
}
}
}
pub fn create_date_time_from_atlantic(date: NaiveDate, time: NaiveTime) -> DateTime<Utc> {
let naive_datetime = NaiveDateTime::new(date, time);
let east_time = Eastern.from_local_datetime(&naive_datetime).unwrap();
east_time.with_timezone(&Utc)
}
pub fn get_income(
&self,
ar_map: &HashMap<u32,
ActionRecord>,
raw_accts: &HashMap<u16, RawAccount>,
acct_map: &HashMap<u16, Account>,
txns_map: &HashMap<u32, Transaction>,
)-> d128 {
let txn = txns_map.get(&self.transaction_key).expect("Couldn't get txn. Tx num invalid?");
match txn.transaction_type(ar_map, raw_accts, acct_map) {
TxType::Flow => {
let ar = ar_map.get(&self.action_record_key).unwrap();
if ar.direction() == Polarity::Incoming {
self.proceeds.get()
}
else { d128!(0) }
}
TxType::Exchange => { d128!(0) }
TxType::ToSelf => { d128!(0) }
}
}
pub fn get_expense(
&self,
ar_map: &HashMap<u32, ActionRecord>,
raw_accts: &HashMap<u16, RawAccount>,
acct_map: &HashMap<u16, Account>,
txns_map: &HashMap<u32, Transaction>,
)-> d128 {
let txn = txns_map.get(&self.transaction_key).expect("Couldn't get txn. Tx num invalid?");
match txn.transaction_type(ar_map, raw_accts, acct_map) {
TxType::Flow => {
let ar = ar_map.get(&self.action_record_key).unwrap();
if ar.direction() == Polarity::Outgoing {
self.proceeds.get()
}
else { d128!(0) }
}
TxType::Exchange => { d128!(0) }
TxType::ToSelf => { d128!(0) }
}
}
pub fn direction(&self) -> Polarity {
if self.amount < d128!(0.0) { Polarity::Outgoing }
else { Polarity::Incoming }
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Term {
LT,
ST,
}
impl Term {
pub fn abbr_string(&self) -> String {
match *self {
Term::LT => "LT".to_string(),
Term::ST => "ST".to_string(),
}
}
}
impl fmt::Display for Term {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Term::LT => write!(f, "LT"),
Term::ST => write!(f, "ST"),
}
}
}

147
src/core_functions.rs Normal file
View File

@ -0,0 +1,147 @@
// Copyright (c) 2017-2019, scoobybejesus
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
use std::path::PathBuf;
use std::error::Error;
use std::fs::File;
use std::process;
use std::collections::{HashMap};
use chrono::NaiveDate;
use crate::account::{Account, RawAccount, Lot};
use crate::transaction::{Transaction, ActionRecord};
use crate::user_choices::{LotProcessingChoices, LikeKindSettings};
use crate::import_accts_txns;
use crate::import_cost_proceeds_etc;
use crate::create_lots_mvmts;
pub fn import_and_process_final(
input_file_path: PathBuf,
settings: &LotProcessingChoices,
) -> (
HashMap<u16, Account>,
HashMap<u16, RawAccount>,
HashMap<u32, ActionRecord>,
HashMap<u32, Transaction>,
Option<LikeKindSettings>
) {
let mut transactions_map: HashMap<u32, Transaction> = HashMap::new();
let mut action_records_map: HashMap<u32, ActionRecord> = HashMap::new();
let mut raw_account_map: HashMap<u16, RawAccount> = HashMap::new();
let mut account_map: HashMap<u16, Account> = HashMap::new();
let mut lot_map: HashMap<(RawAccount, u32), Lot> = HashMap::new();
match import_from_csv(
input_file_path,
&mut transactions_map,
&mut action_records_map,
&mut raw_account_map,
&mut account_map
) {
Ok(()) => { println!("Successfully imported csv file."); }
Err(err) => { println!("\nFailed to import accounts and transactions from CSV."); println!("{}", err); process::exit(1); }
};
pub fn import_from_csv(
import_file_path: PathBuf,
transactions_map: &mut HashMap<u32, Transaction>,
action_records: &mut HashMap<u32, ActionRecord>,
raw_acct_map: &mut HashMap<u16, RawAccount>,
acct_map: &mut HashMap<u16, Account>,
) -> Result<(), Box<Error>> {
let file = File::open(import_file_path)?; println!("CSV ledger file opened successfully.\n");
let mut rdr = csv::ReaderBuilder::new()
.has_headers(true)
.from_reader(file);
match import_accts_txns::import_accounts(&mut rdr, raw_acct_map, acct_map) {
Ok(()) => {}
Err(err) => { println!("\nFailed to import accounts from CSV."); println!("{}", err); }
};
match import_accts_txns::import_transactions(
&mut rdr,
transactions_map,
action_records,
raw_acct_map,
acct_map
) {
Ok(()) => {}
Err(err) => { println!("\nFailed to import transactions from CSV."); println!("{}", err); }
};
Ok(())
}
// println!("like_kind_cutoff_date is: {}...", like_kind_cutoff_date);
let likekind_settings: Option<LikeKindSettings> = if settings.enable_like_kind_treatment {
let like_kind_cutoff_date = &settings.lk_cutoff_date_string;
Some(
LikeKindSettings {
like_kind_cutoff_date: NaiveDate::parse_from_str(&like_kind_cutoff_date, "%y-%m-%d")
.unwrap_or(NaiveDate::parse_from_str(&like_kind_cutoff_date, "%Y-%m-%d")
.expect("Found date string with improper format")),
like_kind_basis_date_preserved: true,
}
)
} else {
None
};
transactions_map = create_lots_mvmts::create_lots_and_movements(
transactions_map,
&settings,
&likekind_settings,
&action_records_map,
&mut raw_account_map,
&mut account_map,
&mut lot_map,
);
println!(" Successfully created lots and movements.");
import_cost_proceeds_etc::add_cost_basis_to_movements(
&settings,
&action_records_map,
&raw_account_map,
&account_map,
&transactions_map
);
println!(" Successfully added cost basis to movements.");
import_cost_proceeds_etc::add_proceeds_to_movements(
&action_records_map,
&raw_account_map,
&account_map,
&transactions_map
);
println!(" Successfully added proceeds to movements.");
if let Some(lk_settings) = &likekind_settings {
let cutoff_date = lk_settings.like_kind_cutoff_date;
println!(" Applying like-kind treatment for cut-off date: {}.", cutoff_date);
import_cost_proceeds_etc::apply_like_kind_treatment(
cutoff_date,
&settings,
&action_records_map,
&raw_account_map,
&account_map,
&transactions_map
);
println!(" Successfully applied like-kind treatment.");
};
(account_map, raw_account_map, action_records_map, transactions_map, likekind_settings)
}

889
src/create_lots_mvmts.rs Normal file
View File

@ -0,0 +1,889 @@
// Copyright (c) 2017-2019, scoobybejesus
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
use std::rc::{Rc};
use std::cell::{RefCell, Ref, Cell};
use std::collections::{HashMap};
use decimal::d128;
use chrono::NaiveDate;
use crate::transaction::{Transaction, ActionRecord, TxType, Polarity, TxHasMargin};
use crate::account::{Account, RawAccount, Lot, Movement};
use crate::user_choices::{LotProcessingChoices, InventoryCostingMethod, LikeKindSettings};
use crate::utils::{round_d128_1e8};
pub fn create_lots_and_movements(
txns_map: HashMap<u32, Transaction>,
settings: &LotProcessingChoices,
likekind_settings: &Option<LikeKindSettings>,
ar_map: &HashMap<u32, ActionRecord>,
raw_acct_map: &HashMap<u16, RawAccount>,
acct_map: &HashMap<u16, Account>,
lot_map: &HashMap<(RawAccount, u32), Lot>,
) -> HashMap<u32,Transaction> {
// Set values to be referred to repeatedly, potentially, in Incoming Exchange transactions
let multiple_incoming_mvmts_per_ar = match &likekind_settings {
Some(likekind_settings) => {
likekind_settings.like_kind_basis_date_preserved
}
None => {
false
}
};
let mut like_kind_cutoff_date: NaiveDate = NaiveDate::parse_from_str("1/1/1", "%m/%d/%y")
.expect("NaiveDate string parsing failed. It shouldn't have. Try again.");
// Above sets date never to be used. Below modifies date in case it will be used.
if likekind_settings.is_some() {
let likekind_settings_clone = likekind_settings.clone().unwrap();
like_kind_cutoff_date = likekind_settings_clone.like_kind_cutoff_date;
}
// On with the creating of lots and movements.
let length = txns_map.len();
for num in 1..=length {
let txn_num = num as u32;
let txn = txns_map.get(&(txn_num)).expect("Couldn't get txn. Tx num invalid?");
if txn.marginness(&ar_map, &raw_acct_map, &acct_map) == TxHasMargin::TwoARs {
assert_eq!(txn.transaction_type(&ar_map, &raw_acct_map, &acct_map), TxType::Exchange);
assert_eq!(txn.action_record_idx_vec.len(), 2);
let the_raw_pair_keys = txn.get_base_and_quote_raw_acct_keys(&ar_map, &raw_acct_map, &acct_map);
let base_acct = acct_map.get(&the_raw_pair_keys.0).expect("Couldn't get acct. Raw pair keys invalid?");
let quote_acct = acct_map.get(&the_raw_pair_keys.1).expect("Couldn't get acct. Raw pair keys invalid?");
let (base_ar_idx, quote_ar_idx) = get_base_and_quote_ar_idxs(
the_raw_pair_keys,
&txn,
&ar_map,
&raw_acct_map,
&acct_map
);
let base_ar = ar_map.get(&base_ar_idx).unwrap();
let quote_ar = ar_map.get(&quote_ar_idx).unwrap();
let mut base_acct_lot_list = base_acct.list_of_lots.borrow_mut();
let mut quote_acct_lot_list = quote_acct.list_of_lots.borrow_mut();
let base_number_of_lots = base_acct_lot_list.len() as u32;
let quote_number_of_lots = quote_acct_lot_list.len() as u32;
assert_eq!(base_number_of_lots, quote_number_of_lots, "");
let acct_balances_are_zero: bool;
if !base_acct_lot_list.is_empty() && !quote_acct_lot_list.is_empty() {
let base_balance_is_zero = base_acct_lot_list.last().unwrap().get_sum_of_amts_in_lot() == d128!(0);
let quote_balance_is_zero = quote_acct_lot_list.last().unwrap().get_sum_of_amts_in_lot() == d128!(0);
if base_balance_is_zero && quote_balance_is_zero {
acct_balances_are_zero = true
} else {
acct_balances_are_zero = false
}
} else {
assert_eq!(true, base_acct_lot_list.is_empty(),
"One margin account's list_of_lots is empty, but its pair's isn't.");
assert_eq!(true, quote_acct_lot_list.is_empty(),
"One margin account's list_of_lots is empty, but its pair's isn't.");
acct_balances_are_zero = true
}
let mut base_lot: Rc<Lot>;
let mut quote_lot: Rc<Lot>;
if acct_balances_are_zero {
base_lot =
Rc::new(
Lot {
date_as_string: txn.date_as_string.clone(),
date_of_first_mvmt_in_lot: txn.date,
date_for_basis_purposes: txn.date,
lot_number: base_number_of_lots + 1,
account_key: the_raw_pair_keys.0,
movements: RefCell::new([].to_vec()),
}
)
;
quote_lot =
Rc::new(
Lot {
date_as_string: txn.date_as_string.clone(),
date_of_first_mvmt_in_lot: txn.date,
date_for_basis_purposes: txn.date,
lot_number: quote_number_of_lots + 1,
account_key: the_raw_pair_keys.1,
movements: RefCell::new([].to_vec()),
}
)
;
} else {
base_lot = base_acct_lot_list.last().expect("Couldn't get lot. Base acct lot list empty?").clone();
quote_lot = quote_acct_lot_list.last().expect("Couldn't get lot. Quote acct lot list empty?").clone();
}
let base_mvmt = Movement {
amount: base_ar.amount,
date_as_string: txn.date_as_string.clone(),
date: txn.date,
transaction_key: txn_num,
action_record_key: base_ar_idx,
cost_basis: Cell::new(d128!(0.0)),
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0),
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
lot_num: base_lot.lot_number,
proceeds: Cell::new(d128!(0.0)),
};
wrap_mvmt_and_push(base_mvmt, &base_ar, &base_lot, &settings, &raw_acct_map, &acct_map);
let quote_mvmt = Movement {
amount: quote_ar.amount,
date_as_string: txn.date_as_string.clone(),
date: txn.date,
transaction_key: txn_num,
action_record_key: quote_ar_idx,
cost_basis: Cell::new(d128!(0.0)),
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0),
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
lot_num: quote_lot.lot_number,
proceeds: Cell::new(d128!(0.0)),
};
wrap_mvmt_and_push(quote_mvmt, &quote_ar, &quote_lot, &settings, &raw_acct_map, &acct_map);
if acct_balances_are_zero {
base_acct_lot_list.push(base_lot);
quote_acct_lot_list.push(quote_lot);
}
continue
} else {
for ar_num in txn.action_record_idx_vec.iter() {
let ar = ar_map.get(ar_num).unwrap();
let acct = acct_map.get(&ar.account_key).unwrap();
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
let length_of_list_of_lots = acct.list_of_lots.borrow().len();
if raw_acct.is_home_currency(&settings.home_currency) {
if length_of_list_of_lots == 0 {
let lot =
Rc::new(
Lot {
date_as_string: txn.date_as_string.clone(),
date_of_first_mvmt_in_lot: txn.date,
date_for_basis_purposes: txn.date,
lot_number: 1,
account_key: acct.raw_key,
movements: RefCell::new([].to_vec()),
}
)
;
let whole_mvmt = Movement {
amount: ar.amount,
date_as_string: txn.date_as_string.clone(),
date: txn.date,
transaction_key: txn_num,
action_record_key: *ar_num,
cost_basis: Cell::new(d128!(0.0)),
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0),
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
lot_num: lot.lot_number,
proceeds: Cell::new(d128!(0.0)),
};
wrap_mvmt_and_push(whole_mvmt, &ar, &lot, &settings, &raw_acct_map, &acct_map);
acct.list_of_lots.borrow_mut().push(lot);
continue
}
else {
assert_eq!(1, length_of_list_of_lots); // Only true for home currency
let lot = acct.list_of_lots.borrow_mut()[0 as usize].clone();
let whole_mvmt = Movement {
amount: ar.amount,
date_as_string: txn.date_as_string.clone(),
date: txn.date,
transaction_key: txn_num,
action_record_key: *ar_num,
cost_basis: Cell::new(d128!(0.0)),
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0),
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
lot_num: lot.lot_number,
proceeds: Cell::new(d128!(0.0)),
};
wrap_mvmt_and_push(whole_mvmt, &ar, &lot, &settings, &raw_acct_map, &acct_map);
continue
}
}
// Note: a_r is not in home currency if here or below
let polarity = ar.direction();
let tx_type = txn.transaction_type(&ar_map, &raw_acct_map, &acct_map);
match polarity {
Polarity::Outgoing => {
// println!("Txn: {}, outgoing {:?}-type of {} {}",
// txn.tx_number, txn.transaction_type(), ar.amount, acct.ticker);
//
if raw_acct.is_margin {
let this_acct = acct_map.get(&ar.account_key).unwrap();
let lot = this_acct.list_of_lots.borrow().last()
.expect("Couldn't get lot. Acct lot list empty?").clone();
let whole_mvmt = Movement {
amount: ar.amount,
date_as_string: txn.date_as_string.clone(),
date: txn.date,
transaction_key: txn_num,
action_record_key: *ar_num,
cost_basis: Cell::new(d128!(0.0)),
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0),
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
lot_num: lot.lot_number,
proceeds: Cell::new(d128!(0.0)),
};
wrap_mvmt_and_push(whole_mvmt, &ar, &lot, &settings, &raw_acct_map, &acct_map);
continue
} else {
let list_of_lots_to_use = acct.list_of_lots.clone();
// the following returns vec to be iterated from beginning to end, which provides the index for the correct lot
let vec_of_ordered_index_values = match settings.costing_method {
InventoryCostingMethod::LIFObyLotCreationDate => {
get_lifo_by_creation_date(&list_of_lots_to_use.borrow())}
InventoryCostingMethod::LIFObyLotBasisDate => {
get_lifo_by_lot_basis_date(&list_of_lots_to_use.borrow())}
InventoryCostingMethod::FIFObyLotCreationDate => {
get_fifo_by_creation_date(&list_of_lots_to_use.borrow())}
InventoryCostingMethod::FIFObyLotBasisDate => {
get_fifo_by_lot_basis_date(&list_of_lots_to_use.borrow())}
};
fn get_lifo_by_creation_date(list_of_lots: &Ref<Vec<Rc<Lot>>>) -> Vec<usize> {
let mut vec_of_indexes = [].to_vec();
for (idx, lot) in list_of_lots.iter().enumerate() {
vec_of_indexes.insert(0, idx)
}
let vec = vec_of_indexes;
vec
}
fn get_lifo_by_lot_basis_date(list_of_lots: &Ref<Vec<Rc<Lot>>>) -> Vec<usize> {
let mut reordered_vec = list_of_lots.clone().to_vec();
let length = reordered_vec.len();
for _ in 0..length {
for j in 0..length-1 {
if reordered_vec[j].date_for_basis_purposes > reordered_vec[j+1].date_for_basis_purposes {
reordered_vec.swap(j, j+1)
}
}
}
let mut vec_of_indexes = [].to_vec();
for (idx, lot) in reordered_vec.iter().enumerate() {
vec_of_indexes.insert(0, idx)
}
let vec = vec_of_indexes;
vec
}
fn get_fifo_by_creation_date(list_of_lots: &Ref<Vec<Rc<Lot>>>) -> Vec<usize> {
let mut vec_of_indexes = [].to_vec();
for (idx, lot) in list_of_lots.iter().enumerate() {
vec_of_indexes.push(idx)
}
let vec = vec_of_indexes;
vec
}
fn get_fifo_by_lot_basis_date(list_of_lots: &Ref<Vec<Rc<Lot>>>) -> Vec<usize> {
let mut reordered_vec = list_of_lots.clone().to_vec();
let length = reordered_vec.len();
for _ in 0..length {
for j in 0..length-1 {
if reordered_vec[j].date_for_basis_purposes > reordered_vec[j+1].date_for_basis_purposes {
reordered_vec.swap(j, j+1)
}
}
}
let mut vec_of_indexes = [].to_vec();
for (idx, lot) in reordered_vec.iter().enumerate() {
vec_of_indexes.push(idx)
}
let vec = vec_of_indexes;
vec
}
let index_position: usize = 0;
let lot_index = vec_of_ordered_index_values[index_position];
let lot_to_use = list_of_lots_to_use.borrow()[lot_index].clone();
let whole_mvmt = Movement {
amount: ar.amount,
date_as_string: txn.date_as_string.clone(),
date: txn.date,
transaction_key: txn_num,
action_record_key: *ar_num,
cost_basis: Cell::new(d128!(0.0)),
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0),
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
lot_num: lot_to_use.lot_number,
proceeds: Cell::new(d128!(0.0)),
};
fit_into_lots(
acct.raw_key,
txn_num,
*ar_num,
whole_mvmt,
list_of_lots_to_use,
vec_of_ordered_index_values,
index_position,
&settings,
&ar_map,
&raw_acct_map,
&acct_map,
);
continue
}
}
Polarity::Incoming => {
// println!("Txn: {}, Incoming {:?}-type of {} {}",
// txn.tx_number, txn.transaction_type(), ar.amount, acct.ticker);
match tx_type {
TxType::Flow => {
let mut lot: Rc<Lot>;
if raw_acct.is_margin {
let this_acct = acct_map.get(&ar.account_key).unwrap();
let lot_list = this_acct.list_of_lots.borrow_mut();
lot = lot_list.last().unwrap().clone();
let mvmt = Movement {
amount: ar.amount,
date_as_string: txn.date_as_string.clone(),
date: txn.date,
transaction_key: txn_num,
action_record_key: *ar_num,
cost_basis: Cell::new(d128!(0.0)),
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0),
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
lot_num: lot.lot_number,
proceeds: Cell::new(d128!(0.0)),
};
wrap_mvmt_and_push(mvmt, &ar, &lot, &settings, &raw_acct_map, &acct_map);
continue
} else {
let mvmt: Movement;
if txn.action_record_idx_vec.len() == 1 {
lot =
Rc::new(
Lot {
date_as_string: txn.date_as_string.clone(),
date_of_first_mvmt_in_lot: txn.date,
date_for_basis_purposes: txn.date,
lot_number: length_of_list_of_lots as u32 + 1,
account_key: acct.raw_key,
movements: RefCell::new([].to_vec()),
}
)
;
mvmt = Movement {
amount: ar.amount,
date_as_string: txn.date_as_string.clone(),
date: txn.date,
transaction_key: txn_num,
action_record_key: *ar_num,
cost_basis: Cell::new(d128!(0.0)),
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0),
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
lot_num: lot.lot_number,
proceeds: Cell::new(d128!(0.0)),
};
} else {
assert_eq!(txn.action_record_idx_vec.len(), 2);
// create list of incoming/positive amounts(mvmts) in margin lot, add them
let mut positive_mvmt_list: Vec<Rc<Movement>> = [].to_vec();
let mut total_positive_amounts = d128!(0);
let (base_acct_key, quote_acct_key) = get_base_and_quote_acct_for_dual_actionrecord_flow_tx(
txn_num,
&ar_map,
&raw_acct_map,
&acct_map,
&txns_map,
);
let base_acct = acct_map.get(&base_acct_key).unwrap();
let base_acct_lot = base_acct.list_of_lots.borrow().last().unwrap().clone();
// TODO: generalize this to work with margin shorts as well
for mvmt in base_acct_lot.movements.borrow().iter() {
if mvmt.amount > d128!(0) {
// println!("In lot# {}, positive mvmt amount: {} {},",
// base_acct_lot.lot_number,
// mvmt.borrow().amount,
// base_acct_lot.account.raw.ticker);
total_positive_amounts += mvmt.amount;
positive_mvmt_list.push(mvmt.clone())
}
}
let mut amounts_used = d128!(0);
let mut percentages_used = d128!(0);
for pos_mvmt in positive_mvmt_list.iter().take(positive_mvmt_list.len()-1) {
let inner_lot =
Rc::new(
Lot {
date_as_string: txn.date_as_string.clone(),
date_of_first_mvmt_in_lot: txn.date,
date_for_basis_purposes: pos_mvmt.date,
lot_number: acct.list_of_lots.borrow().len() as u32 + 1,
account_key: acct.raw_key,
movements: RefCell::new([].to_vec()),
}
);
let percentage_used = round_d128_1e8(&(pos_mvmt.amount/&total_positive_amounts));
let amount_used = round_d128_1e8(&(ar.amount*percentage_used));
let inner_mvmt = Movement {
amount: amount_used,
date_as_string: txn.date_as_string.clone(),
date: txn.date,
transaction_key: txn_num,
action_record_key: *ar_num,
cost_basis: Cell::new(d128!(0.0)),
ratio_of_amt_to_incoming_mvmts_in_a_r: percentage_used,
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
lot_num: inner_lot.lot_number,
proceeds: Cell::new(d128!(0.0)),
};
wrap_mvmt_and_push(inner_mvmt, &ar, &inner_lot, &settings, &raw_acct_map, &acct_map);
acct.list_of_lots.borrow_mut().push(inner_lot);
// acct.push_lot(inner_lot);
amounts_used += amount_used;
percentages_used += percentage_used;
}
let final_mvmt = positive_mvmt_list.last().unwrap();
lot =
Rc::new(
Lot {
date_as_string: txn.date_as_string.clone(),
date_of_first_mvmt_in_lot: txn.date,
date_for_basis_purposes: final_mvmt.date,
lot_number: acct.list_of_lots.borrow().len() as u32 + 1,
account_key: acct.raw_key,
movements: RefCell::new([].to_vec()),
}
)
;
mvmt = Movement {
amount: round_d128_1e8(&(ar.amount - amounts_used)),
date_as_string: txn.date_as_string.clone(),
date: txn.date,
transaction_key: txn_num,
action_record_key: *ar_num,
cost_basis: Cell::new(d128!(0.0)),
ratio_of_amt_to_incoming_mvmts_in_a_r: round_d128_1e8(&(d128!(1.0) - percentages_used)),
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
lot_num: lot.lot_number,
proceeds: Cell::new(d128!(0.0)),
};
}
wrap_mvmt_and_push(mvmt, &ar, &lot, &settings, &raw_acct_map, &acct_map);
acct.list_of_lots.borrow_mut().push(lot);
continue
}
}
TxType::Exchange => {
let both_are_non_home_curr: bool;
let og_ar = ar_map.get(txn.action_record_idx_vec.first().unwrap()).unwrap();
let og_acct = acct_map.get(&og_ar.account_key).unwrap();
let og_raw_acct = raw_acct_map.get(&og_acct.raw_key).unwrap();
let ic_ar = ar;
let ic_raw_acct = raw_acct;
both_are_non_home_curr = !og_raw_acct.is_home_currency(&settings.home_currency)
&& !ic_raw_acct.is_home_currency(&settings.home_currency);
if both_are_non_home_curr && multiple_incoming_mvmts_per_ar && (txn.date <= like_kind_cutoff_date) {
process_multiple_incoming_lots_and_mvmts(
txn_num,
&og_ar,
&ic_ar,
&settings,
*ar_num,
&raw_acct_map,
&acct_map,
&txns_map,
&ar_map,
);
continue
}
else {
let lot =
Rc::new(
Lot {
date_as_string: txn.date_as_string.clone(),
date_of_first_mvmt_in_lot: txn.date,
date_for_basis_purposes: txn.date,
lot_number: length_of_list_of_lots as u32 + 1,
account_key: acct.raw_key,
movements: RefCell::new([].to_vec()),
}
)
;
let whole_mvmt = Movement {
amount: ar.amount,
date_as_string: txn.date_as_string.clone(),
date: txn.date,
transaction_key: txn_num,
action_record_key: *ar_num,
cost_basis: Cell::new(d128!(0.0)),
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0),
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
lot_num: lot.lot_number,
proceeds: Cell::new(d128!(0.0)),
};
wrap_mvmt_and_push(whole_mvmt, &ar, &lot, &settings, &raw_acct_map, &acct_map);
acct.list_of_lots.borrow_mut().push(lot);
continue
}
}
TxType::ToSelf => {
if raw_acct.is_margin {
{ println!("\n Found margin actionrecord in toself txn # {} \n", txn.tx_number); use std::process::exit; exit(1) };
} else {
process_multiple_incoming_lots_and_mvmts(
txn_num,
&ar_map.get(txn.action_record_idx_vec.first().unwrap()).unwrap(), // outgoing
&ar, // incoming
&settings,
*ar_num,
&raw_acct_map,
&acct_map,
&txns_map,
&ar_map,
);
}
continue
}
}
}
}
} // end for ar in txn.actionrecords
} // end of tx does not have marginness of TwoARs
} // end for txn in transactions
txns_map
}
fn get_base_and_quote_acct_for_dual_actionrecord_flow_tx(
txn_num: u32,
ar_map: &HashMap<u32, ActionRecord>,
raw_acct_map: &HashMap<u16, RawAccount>,
acct_map: &HashMap<u16, Account>,
txns_map: &HashMap<u32, Transaction>,
) -> (u16, u16) {
let txn = txns_map.get(&txn_num).expect("Couldn't get txn. Tx num invalid?");
let og_flow_ar = ar_map.get(txn.action_record_idx_vec.first().unwrap()).unwrap();
// println!("Acct: {}, Amount: {}, Tx: {}, ar: {}",
// outgoing_flow_ar.account_key, outgoing_flow_ar.amount, outgoing_flow_ar.tx_key, outgoing_flow_ar.self_ar_key);
let og_ar_mvmts_list = &og_flow_ar.get_mvmts_in_ar(acct_map, txns_map); // TODO: ... in margin profit, this just takes a list of one mvmt
let og_ar_list_first_mvmt = &og_ar_mvmts_list.first().unwrap(); // TODO: then this takes the one mvmt
let og_ar_list_first_mvmt_ar = ar_map.get(&og_ar_list_first_mvmt.action_record_key).unwrap();
let og_ar_list_first_mvmt_ar_acct = acct_map.get(&og_ar_list_first_mvmt_ar.account_key).unwrap();
let og_mvmt_lot = &og_ar_list_first_mvmt_ar_acct.list_of_lots.borrow()[(og_ar_list_first_mvmt.lot_num - 1) as usize];
// let og_mvmt_lot_strong = &og_mvmt_lot;
let og_mvmt_lot_mvmts = og_mvmt_lot.movements.borrow();
let og_mvmt_lot_first_mvmt = &og_mvmt_lot_mvmts.first().unwrap();
let txn_of_og_mvmt_lot_first_mvmt = txns_map.get(&og_mvmt_lot_first_mvmt.transaction_key).unwrap();
let (base_key,quote_key) = txn_of_og_mvmt_lot_first_mvmt.get_base_and_quote_raw_acct_keys(
ar_map,
&raw_acct_map,
&acct_map); // TODO: should this panic on margin loss?
(base_key, quote_key)
}
fn get_base_and_quote_ar_idxs(
pair_keys: (u16,u16),
txn: &Transaction,
ars: &HashMap<u32, ActionRecord>,
raw_acct_map: &HashMap<u16, RawAccount>,
acct_map: &HashMap<u16, Account>
) -> (u32, u32){
let incoming_ar = ars.get(&txn.action_record_idx_vec[0]).unwrap();
let incoming_acct = acct_map.get(&incoming_ar.account_key).unwrap();
let raw_ic_acct = raw_acct_map.get(&incoming_acct.raw_key).unwrap();
let compare = raw_acct_map.get(&pair_keys.0).unwrap(); // key.0 is base, and key.1 is quote
if raw_ic_acct == compare {
(txn.action_record_idx_vec[0], txn.action_record_idx_vec[1])
} else {
(txn.action_record_idx_vec[1], txn.action_record_idx_vec[0])
}
}
fn wrap_mvmt_and_push(
this_mvmt: Movement,
ar: &ActionRecord,
lot: &Lot,
settings: &LotProcessingChoices,
raw_acct_map: &HashMap<u16, RawAccount>,
acct_map: &HashMap<u16, Account>,
) {
let acct = acct_map.get(&ar.account_key).unwrap();
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
if ar.direction() == Polarity::Outgoing && !raw_acct.is_home_currency(&settings.home_currency) {
let ratio = this_mvmt.amount / ar.amount;
this_mvmt.ratio_of_amt_to_outgoing_mvmts_in_a_r.set(round_d128_1e8(&ratio));
}
let amt = this_mvmt.amount;
let amt2 = round_d128_1e8(&amt);
assert_eq!(amt, amt2);
// println!("Unrounded: {}; Rounded: {}; on {}", amt, amt2, mvmt_ref.borrow().date);
let mvmt = Rc::from(this_mvmt);
lot.movements.borrow_mut().push(mvmt.clone());
ar.movements.borrow_mut().push(mvmt);
}
fn fit_into_lots(
acct_key: u16,
txn_num: u32,
spawning_ar_key: u32,
mvmt_to_fit: Movement,
list_of_lots_to_use: RefCell<Vec<Rc<Lot>>>,
vec_of_ordered_index_values: Vec<usize>,
index_position: usize,
settings: &LotProcessingChoices,
ar_map: &HashMap<u32, ActionRecord>,
raw_acct_map: &HashMap<u16, RawAccount>,
acct_map: &HashMap<u16, Account>,
) {
let ar = ar_map.get(&spawning_ar_key).unwrap();
let acct = acct_map.get(&ar.account_key).unwrap();
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
assert_eq!(raw_acct.is_home_currency(&settings.home_currency), false);
let spawning_ar = ar_map.get(&spawning_ar_key).unwrap();
let mut current_index_position = index_position;
let lot = mvmt_to_fit.get_lot(acct_map, ar_map);
let mut mut_sum_of_mvmts_in_lot: d128 = d128!(0.0);
for movement in lot.movements.borrow().iter() {
mut_sum_of_mvmts_in_lot += movement.amount;
}
let sum_of_mvmts_in_lot = mut_sum_of_mvmts_in_lot;
assert!(sum_of_mvmts_in_lot >= d128!(0.0));
if sum_of_mvmts_in_lot == d128!(0.0) { // If the lot is "full", go to the next
current_index_position += 1;
assert!(current_index_position < vec_of_ordered_index_values.len());
let lot_index = vec_of_ordered_index_values[current_index_position];
let newly_chosen_lot = list_of_lots_to_use.borrow()[lot_index].clone();
let possible_mvmt_to_fit = Movement {
amount: mvmt_to_fit.amount,
date_as_string: mvmt_to_fit.date_as_string.clone(),
date: mvmt_to_fit.date,
transaction_key: mvmt_to_fit.transaction_key,
action_record_key: mvmt_to_fit.action_record_key,
cost_basis: mvmt_to_fit.cost_basis,
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0), // Outgoing mvmt, so it's irrelevant
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
lot_num: newly_chosen_lot.lot_number,
proceeds: Cell::new(d128!(0.0)),
};
fit_into_lots(
acct.raw_key,
txn_num,
spawning_ar_key,
possible_mvmt_to_fit,
list_of_lots_to_use,
vec_of_ordered_index_values,
current_index_position,
&settings,
&ar_map,
&raw_acct_map,
&acct_map
);
return;
}
assert!(sum_of_mvmts_in_lot > d128!(0.0));
let remainder_amt = mvmt_to_fit.amount;
// println!("Sum of mvmts in lot: {}; Remainder amount: {}; Net: {}",
// sum_of_mvmts_in_lot, remainder_amt, sum_of_mvmts_in_lot + remainder_amt);
let does_remainder_fit: bool = (sum_of_mvmts_in_lot + remainder_amt) >= d128!(0.0);
if does_remainder_fit {
let remainder_that_fits = Movement {
amount: mvmt_to_fit.amount,
date_as_string: mvmt_to_fit.date_as_string.clone(),
date: mvmt_to_fit.date,
transaction_key: mvmt_to_fit.transaction_key,
action_record_key: mvmt_to_fit.action_record_key,
cost_basis: mvmt_to_fit.cost_basis,
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0), // Outgoing mvmt, so it's irrelevant
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
lot_num: lot.lot_number,
proceeds: Cell::new(d128!(0.0)),
};
wrap_mvmt_and_push(remainder_that_fits, &spawning_ar, &lot, &settings, &raw_acct_map, &acct_map);
return // And we're done
}
// Note: at this point, we know the movement doesn't fit in a single lot & sum_of_mvmts_in_lot > 0
let mvmt = RefCell::new(mvmt_to_fit);
let mvmt_rc = Rc::from(mvmt);
let mvmt_that_fits_in_lot = Movement {
amount: (-sum_of_mvmts_in_lot).reduce(),
date_as_string: mvmt_rc.borrow().date_as_string.clone(),
date: mvmt_rc.borrow().date,
transaction_key: txn_num,
action_record_key: spawning_ar_key,
cost_basis: Cell::new(d128!(0.0)),
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0), // Outgoing mvmt, so it's irrelevant
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
lot_num: lot.lot_number,
proceeds: Cell::new(d128!(0.0)),
};
wrap_mvmt_and_push(mvmt_that_fits_in_lot, &spawning_ar, &lot, &settings, &raw_acct_map, &acct_map);
let remainder_amt_to_recurse = remainder_amt + sum_of_mvmts_in_lot;
// println!("Remainder amount to recurse: {}", remainder_amt_to_recurse);
current_index_position += 1;
let lot_index = vec_of_ordered_index_values[current_index_position];
let newly_chosen_lot = list_of_lots_to_use.borrow()[lot_index].clone();
let remainder_mvmt_to_recurse = Movement {
amount: remainder_amt_to_recurse.reduce(),
date_as_string: mvmt_rc.borrow().date_as_string.clone(),
date: mvmt_rc.borrow().date,
transaction_key: txn_num,
action_record_key: spawning_ar_key,
cost_basis: Cell::new(d128!(0.0)), // This acts as a dummy value.
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0), // Outgoing mvmt, so it's irrelevant
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)), // This acts as a dummy value.
lot_num: newly_chosen_lot.lot_number,
proceeds: Cell::new(d128!(0.0)),
};
assert!(current_index_position < vec_of_ordered_index_values.len());
fit_into_lots(
acct.raw_key,
txn_num,
spawning_ar_key,
remainder_mvmt_to_recurse,
list_of_lots_to_use,
vec_of_ordered_index_values,
current_index_position,
&settings,
&ar_map,
&raw_acct_map,
&acct_map
);
}
fn process_multiple_incoming_lots_and_mvmts(
txn_num: u32,
outgoing_ar: &ActionRecord,
incoming_ar: &ActionRecord,
settings: &LotProcessingChoices,
incoming_ar_key: u32,
raw_acct_map: &HashMap<u16, RawAccount>,
acct_map: &HashMap<u16, Account>,
txns_map: &HashMap<u32, Transaction>,
ar_map: &HashMap<u32, ActionRecord>,
) {
let round_to_places = d128::from(1).scaleb(d128::from(-8));
let txn = txns_map.get(&txn_num).expect("Couldn't get txn. Tx num invalid?");
let acct_of_incoming_ar = acct_map.get(&incoming_ar.account_key).unwrap();
let mut all_but_last_incoming_mvmt_amt = d128!(0.0);
let mut all_but_last_incoming_mvmt_ratio = d128!(0.0);
// println!("Txn date: {}. Outgoing mvmts: {}, Outgoing amount: {}", txn.date, outgoing_ar.movements.borrow().len(), outgoing_ar.amount);
let list_of_mvmts_of_outgoing_ar = outgoing_ar.get_mvmts_in_ar(acct_map, txns_map);
let final_mvmt = list_of_mvmts_of_outgoing_ar.last().unwrap();
// First iteration, for all but final movement
for outgoing_mvmt in list_of_mvmts_of_outgoing_ar
.iter()
.take(outgoing_ar.get_mvmts_in_ar(acct_map, txns_map).len() - 1) {
let ratio_of_outgoing_mvmt_to_total_ar = outgoing_mvmt.amount / outgoing_ar.amount; // Negative divided by negative is positive
// println!("Ratio of outgoing amt to total actionrecord amt: {:.8}", ratio_of_outgoing_to_total_ar);
let tentative_incoming_amt = ratio_of_outgoing_mvmt_to_total_ar * incoming_ar.amount;
// println!("Unrounded incoming amt: {}", tentative_incoming_amt);
let corresponding_incoming_amt = tentative_incoming_amt.quantize(round_to_places);
// println!("Rounded incoming amt: {}", corresponding_incoming_amt);
if corresponding_incoming_amt == d128!(0) { continue } // Due to rounding, this could be zero.
assert!(corresponding_incoming_amt > d128!(0.0));
let this_acct = acct_of_incoming_ar;
let length_of_list_of_lots: usize = this_acct.list_of_lots.borrow().len();
let inherited_date = outgoing_mvmt.get_lot(acct_map, ar_map).date_of_first_mvmt_in_lot;
let lot =
Rc::new(
Lot {
date_as_string: txn.date_as_string.clone(),
date_of_first_mvmt_in_lot: txn.date,
date_for_basis_purposes: inherited_date,
lot_number: length_of_list_of_lots as u32 + 1,
account_key: this_acct.raw_key,
movements: RefCell::new([].to_vec()),
}
)
;
let incoming_mvmt = Movement {
amount: corresponding_incoming_amt.reduce(),
date_as_string: txn.date_as_string.clone(),
date: txn.date,
transaction_key: txn_num,
action_record_key: incoming_ar_key,
cost_basis: Cell::new(d128!(0.0)),
ratio_of_amt_to_incoming_mvmts_in_a_r: round_d128_1e8(&ratio_of_outgoing_mvmt_to_total_ar),
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
lot_num: lot.lot_number,
proceeds: Cell::new(d128!(0.0)),
};
// println!("From first set of incoming movements, amount: {} {} to account: {}",
// incoming_mvmt.amount, acct_incoming_ar.ticker, acct_incoming_ar.account_num);
all_but_last_incoming_mvmt_ratio += round_d128_1e8(&ratio_of_outgoing_mvmt_to_total_ar);
all_but_last_incoming_mvmt_amt += incoming_mvmt.amount;
wrap_mvmt_and_push(incoming_mvmt, &incoming_ar, &lot, &settings, &raw_acct_map, &acct_map);
this_acct.list_of_lots.borrow_mut().push(lot);
}
// Second iteration, for final movement
let corresponding_incoming_amt = incoming_ar.amount - all_but_last_incoming_mvmt_amt;
assert!(corresponding_incoming_amt > d128!(0.0));
let this_acct = acct_of_incoming_ar;
let length_of_list_of_lots = this_acct.list_of_lots.borrow().len();
let inherited_date = final_mvmt.get_lot(acct_map, ar_map).date_of_first_mvmt_in_lot;
let lot =
Rc::new(
Lot {
date_as_string: txn.date_as_string.clone(),
date_of_first_mvmt_in_lot: txn.date,
date_for_basis_purposes: inherited_date,
lot_number: length_of_list_of_lots as u32 + 1,
account_key: this_acct.raw_key,
movements: RefCell::new([].to_vec()),
}
)
;
let incoming_mvmt = Movement {
amount: corresponding_incoming_amt.reduce(),
date_as_string: txn.date_as_string.clone(),
date: txn.date,
transaction_key: txn_num,
action_record_key: incoming_ar_key,
cost_basis: Cell::new(d128!(0.0)),
ratio_of_amt_to_incoming_mvmts_in_a_r: d128!(1.0) - all_but_last_incoming_mvmt_ratio,
ratio_of_amt_to_outgoing_mvmts_in_a_r: Cell::new(d128!(1.0)),
lot_num: lot.lot_number,
proceeds: Cell::new(d128!(0.0)),
};
// println!("Final incoming mvmt for this actionrecord, amount: {} {} to account: {}",
// incoming_mvmt.amount, acct_incoming_ar.ticker, acct_incoming_ar.account_num);
wrap_mvmt_and_push(incoming_mvmt, &incoming_ar, &lot, &settings, &raw_acct_map, &acct_map);
this_acct.list_of_lots.borrow_mut().push(lot);
}

433
src/export.rs Normal file
View File

@ -0,0 +1,433 @@
// Copyright (c) 2017-2019, scoobybejesus
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
use std::rc::{Rc};
use std::fs::File;
use std::collections::{HashMap};
use std::path::PathBuf;
use decimal::d128;
use crate::transaction::{Transaction, ActionRecord, Polarity, TxType};
use crate::account::{Account, RawAccount, Term};
use crate::user_choices::{LotProcessingChoices};
pub fn _1_account_sums_to_csv(
settings: &LotProcessingChoices,
raw_acct_map: &HashMap<u16, RawAccount>,
acct_map: &HashMap<u16, Account>
) {
let mut rows: Vec<Vec<String>> = [].to_vec();
let mut header: Vec<String> = [].to_vec();
header.extend_from_slice(&[
"Account".to_string(),
"Balance".to_string(),
"Ticker".to_string(),
"Cost Basis".to_string(),
"Total lots".to_string(),
]);
rows.push(header);
let length = acct_map.len();
for j in 1..=length {
let acct = acct_map.get(&(j as u16)).unwrap();
let mut row: Vec<String> = [].to_vec();
let balance: String;
let tentative_balance = acct.get_sum_of_amts_in_lots();
if tentative_balance == d128!(0) {
balance = "0.00".to_string()
} else { balance = tentative_balance.to_string() }
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
let cost_basis: String;
if raw_acct.is_margin { cost_basis = "0.00".to_string() } else {
let tentative_cost_basis = acct.get_sum_of_basis_in_lots();
if tentative_cost_basis == d128!(0) {
cost_basis = "0.00".to_string()
} else { cost_basis = tentative_cost_basis.to_string() }
}
row.push(raw_acct.name.to_string());
row.push(balance);
row.push(raw_acct.ticker.to_string());
row.push(cost_basis);
row.push(acct.list_of_lots.borrow().len().to_string());
rows.push(row);
}
let file_name = PathBuf::from("1_Acct_Sum_with_cost_basis.csv");
let path = PathBuf::from(&settings.export_path.clone());
let full_path: PathBuf = [path, file_name].iter().collect();
let buffer = File::create(full_path).unwrap();
let mut wtr = csv::Writer::from_writer(buffer);
for row in rows.iter() {
wtr.write_record(row).expect("Could not write row to CSV file");
}
wtr.flush().expect("Could not flush Writer, though file should exist and be complete");
}
pub fn _2_account_sums_nonzero_to_csv(
acct_map: &HashMap<u16, Account>,
settings: &LotProcessingChoices,
raw_acct_map: &HashMap<u16, RawAccount>
) {
let mut rows: Vec<Vec<String>> = [].to_vec();
let mut header: Vec<String> = [].to_vec();
header.extend_from_slice(&[
"Account".to_string(),
"Balance".to_string(),
"Ticker".to_string(),
"Cost basis".to_string(),
"Total lots".to_string(),
]);
rows.push(header);
let length = acct_map.len();
for j in 1..=length {
let acct = acct_map.get(&(j as u16)).unwrap();
let mut row: Vec<String> = [].to_vec();
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
let name = raw_acct.name.to_string();
let balance: String;
let mut balance_d128 = d128!(0);
let tentative_balance = acct.get_sum_of_amts_in_lots();
if tentative_balance == d128!(0) {
balance = "0.00".to_string()
} else { balance_d128 += tentative_balance; balance = tentative_balance.to_string() }
let cost_basis: String;
if raw_acct.is_margin { cost_basis = "0.00".to_string() } else {
let tentative_cost_basis = acct.get_sum_of_basis_in_lots();
if tentative_cost_basis == d128!(0) {
cost_basis = "0.00".to_string()
} else { cost_basis = tentative_cost_basis.to_string() }
}
if balance_d128 != d128!(0) {
row.push(name);
row.push(balance);
row.push(raw_acct.ticker.to_string());
row.push(cost_basis);
row.push(acct.list_of_lots.borrow().len().to_string());
rows.push(row);
}
}
let file_name = PathBuf::from("2_Acct_Sum_with_nonzero_cost_basis.csv");
let path = PathBuf::from(&settings.export_path.clone());
let full_path: PathBuf = [path, file_name].iter().collect();
let buffer = File::create(full_path).unwrap();
let mut wtr = csv::Writer::from_writer(buffer);
for row in rows.iter() {
wtr.write_record(row).expect("Could not write row to CSV file");
}
wtr.flush().expect("Could not flush Writer, though file should exist and be complete");
}
// pub fn transactions_to_csv(
// transactions: &[Rc<Transaction>],
// ars: &HashMap<u32, ActionRecord>,
// raw_acct_map: &HashMap<u16, RawAccount>,
// acct_map: &HashMap<u16, Account>,
// txns_map: &HashMap<u32, Transaction>,) {
// let mut rows: Vec<Vec<String>> = [].to_vec();
// let mut header: Vec<String> = [].to_vec();
// header.extend_from_slice(&[
// "Date".to_string(),
// "Txn#".to_string(),
// "Type".to_string(),
// "Memo".to_string(),
// "Amount".to_string(),
// "Ticker".to_string(),
// "Proceeds".to_string(),
// "Cost basis".to_string(),
// "Gain/loss".to_string(),
// "Term".to_string(),
// "Income".to_string(),
// "Expense".to_string(),
// ]);
// rows.push(header);
// for txn in transactions {
// for mvmt in txn.flow_or_outgoing_exchange_movements.borrow().iter() {
// let lot = mvmt.borrow().get_lot(acct_map, ars);
// let acct = acct_map.get(&lot.account_key).unwrap();
// let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
// let mut row: Vec<String> = [].to_vec();
// row.push(txn.date.format("%Y/%m/%d").to_string());
// row.push(txn.tx_number.to_string());
// row.push(txn.transaction_type(&ars, &raw_acct_map, &acct_map).to_string());
// row.push(txn.memo.to_string());
// row.push(mvmt.borrow().amount.to_string());
// row.push(raw_acct.ticker.to_string());
// row.push(mvmt.borrow().proceeds.to_string());
// row.push(mvmt.borrow().cost_basis.to_string());
// row.push(mvmt.borrow().get_gain_or_loss().to_string());
// row.push(mvmt.borrow().get_term(acct_map, ars).to_string());
// row.push(mvmt.borrow().get_income(ars, &raw_acct_map, &acct_map, &txns_map).to_string());
// row.push(mvmt.borrow().get_expense(ars, &raw_acct_map, &acct_map, &txns_map).to_string());
// rows.push(row);
// }
// }
// let buffer = File::create("/Users/scoob/Documents/Testing/rust_exports/test/txns-3rd-try.csv").unwrap();
// let mut wtr = csv::Writer::from_writer(buffer);
// for row in rows.iter() {
// wtr.write_record(row).expect("Could not write row to CSV file");
// }
// wtr.flush().expect("Could not flush Writer, though file should exist and be complete");
// }
pub fn _5_transaction_mvmt_summaries_to_csv(
settings: &LotProcessingChoices,
ars: &HashMap<u32, ActionRecord>,
raw_acct_map: &HashMap<u16, RawAccount>,
acct_map: &HashMap<u16, Account>,
txns_map: &HashMap<u32, Transaction>,
) {
let mut rows: Vec<Vec<String>> = [].to_vec();
let mut header: Vec<String> = [].to_vec();
header.extend_from_slice(&[
"Date".to_string(),
"Txn#".to_string(),
"Type".to_string(),
"Memo".to_string(),
"Amount".to_string(),
"Ticker".to_string(),
"Term".to_string(),
"Proceeds".to_string(),
"Cost basis".to_string(),
"Gain/loss".to_string(),
"Income".to_string(),
"Expense".to_string(),
]);
rows.push(header);
let length = txns_map.len();
for txn_num in 1..=length {
let txn_num = txn_num as u32;
let txn = txns_map.get(&(txn_num)).unwrap();
let txn_date_string = txn.date.format("%Y/%m/%d").to_string();
let tx_num_string = txn.tx_number.to_string();
let tx_type_string = txn.transaction_type(ars, &raw_acct_map, &acct_map).to_string();
let tx_memo_string = txn.memo.to_string();
let mut term_st: Option<Term> = None;
let mut term_lt: Option<Term> = None;
let mut ticker: Option<String> = None;
let mut polarity: Option<Polarity> = None;
let mut amount_st = d128!(0);
let mut proceeds_st = d128!(0);
let mut cost_basis_st = d128!(0);
let mut income_st = d128!(0);
let mut expense_st = d128!(0);
let mut amount_lt = d128!(0);
let mut proceeds_lt = d128!(0);
let mut cost_basis_lt = d128!(0);
let mut income_lt = d128!(0);
let mut expense_lt = d128!(0);
let flow_or_outgoing_exchange_movements = txn.get_outgoing_exchange_and_flow_mvmts(
settings,
ars,
raw_acct_map,
acct_map,
txns_map
);
for mvmt in flow_or_outgoing_exchange_movements.iter() {
let lot = mvmt.get_lot(acct_map, ars);
let acct = acct_map.get(&lot.account_key).unwrap();
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
if let None = ticker { ticker = Some(raw_acct.ticker.clone()) };
if let None = polarity {
polarity = if mvmt.amount > d128!(0) {
Some(Polarity::Incoming)
} else { Some(Polarity::Outgoing)
};
}
let term = mvmt.get_term(acct_map, ars);
if term == Term::LT {
amount_lt += mvmt.amount;
proceeds_lt += mvmt.proceeds.get();
cost_basis_lt += mvmt.cost_basis.get();
match term_lt {
None => { term_lt = Some(term)}
_ => {}
}
} else {
assert_eq!(term, Term::ST);
amount_st += mvmt.amount;
proceeds_st += mvmt.proceeds.get();
cost_basis_st += mvmt.cost_basis.get();
if term_st == None {
term_st = Some(term);
}
}
}
if (txn.transaction_type(ars, &raw_acct_map, &acct_map) == TxType::Flow) & (polarity == Some(Polarity::Incoming)) {
// println!("Incoming flow {}", txn.tx_number);
income_st = proceeds_st;
proceeds_st = d128!(0);
cost_basis_st = d128!(0);
income_lt = proceeds_lt;
proceeds_lt = d128!(0);
cost_basis_lt = d128!(0);
}
if (txn.transaction_type(ars, &raw_acct_map, &acct_map) == TxType::Flow) & (polarity == Some(Polarity::Outgoing)) {
// println!("Outgoing flow {}, proceeds_st {}, proceeds_lt {}", txn.tx_number, proceeds_st, proceeds_lt);
expense_st -= proceeds_st;
expense_lt -= proceeds_lt;
}
if let Some(term) = term_st {
let mut row: Vec<String> = [].to_vec();
row.push(txn_date_string.clone());
row.push(tx_num_string.clone());
row.push(tx_type_string.clone());
row.push(tx_memo_string.clone());
row.push(amount_st.to_string());
row.push(ticker.clone().unwrap());
row.push(term.abbr_string());
row.push(proceeds_st.to_string());
row.push(cost_basis_st.to_string());
row.push((proceeds_st + cost_basis_st).to_string());
row.push(income_st.to_string());
row.push(expense_st.to_string());
rows.push(row);
}
if let Some(term) = term_lt {
let mut row: Vec<String> = [].to_vec();
row.push(txn_date_string);
row.push(tx_num_string);
row.push(tx_type_string);
row.push(tx_memo_string);
row.push(amount_lt.to_string());
row.push(ticker.unwrap());
row.push(term.abbr_string());
row.push(proceeds_lt.to_string());
row.push(cost_basis_lt.to_string());
row.push((proceeds_lt + cost_basis_lt).to_string());
row.push(income_lt.to_string());
row.push(expense_lt.to_string());
rows.push(row);
}
}
let file_name = PathBuf::from("5_Txns_mvmts_summary.csv");
let path = PathBuf::from(&settings.export_path);
let full_path: PathBuf = [path, file_name].iter().collect();
let buffer = File::create(full_path).unwrap();
let mut wtr = csv::Writer::from_writer(buffer);
for row in rows.iter() {
wtr.write_record(row).expect("Could not write row to CSV file");
}
wtr.flush().expect("Could not flush Writer, though file should exist and be complete");
}
pub fn accounts_to_csv(
accounts: &[Rc<Account>],
ars: &HashMap<u32, ActionRecord>,
raw_acct_map: &HashMap<u16, RawAccount>,
acct_map: &HashMap<u16, Account>,
txns_map: &HashMap<u32, Transaction>,
) {
let mut rows: Vec<Vec<String>> = [].to_vec();
let mut header: Vec<String> = [].to_vec();
header.extend_from_slice(&[
"#".to_string(),
"Account".to_string(),
"Ticker".to_string(),
"Margin".to_string(),
"Date".to_string(),
"Txn#".to_string(),
"Type".to_string(),
"Memo".to_string(),
"Amount".to_string(),
"Proceeds".to_string(),
"Cost basis\n".to_string(),
"Gain/loss".to_string(),
"Term".to_string(),
"Income".to_string(),
"Expense".to_string(),
]);
rows.push(header);
for acct in accounts {
for lot in acct.list_of_lots.borrow().iter() {
for mvmt in lot.movements.borrow().iter() {
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
let txn = txns_map.get(&mvmt.transaction_key).unwrap();
let mut row: Vec<String> = [].to_vec();
row.push(raw_acct.account_num.to_string());
row.push(raw_acct.name.to_string());
row.push(raw_acct.ticker.to_string());
row.push(raw_acct.is_margin.to_string());
row.push(mvmt.date.format("%Y/%m/%d").to_string());
row.push(txn.tx_number.to_string());
row.push(txn.transaction_type(ars, &raw_acct_map, &acct_map).to_string());
row.push(txn.memo.to_string());
row.push(mvmt.amount.to_string());
row.push(mvmt.proceeds.get().to_string());
row.push(mvmt.cost_basis.get().to_string());
row.push(mvmt.get_gain_or_loss().to_string());
row.push(mvmt.get_term(acct_map, ars).to_string());
row.push(mvmt.get_income(ars, &raw_acct_map, &acct_map, &txns_map).to_string());
row.push(mvmt.get_expense(ars, &raw_acct_map, &acct_map, &txns_map).to_string());
rows.push(row);
}
}
}
let buffer = File::create("/Users/scoob/Documents/Testing/rust_exports/test/accts-1st-try.csv").unwrap();
let mut wtr = csv::Writer::from_writer(buffer);
for row in rows.iter() {
wtr.write_record(row).expect("Could not write row to CSV file");
}
wtr.flush().expect("Could not flush Writer, though file should exist and be complete");
}

230
src/import_accts_txns.rs Normal file
View File

@ -0,0 +1,230 @@
// Copyright (c) 2017-2019, scoobybejesus
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
use std::error::Error;
use std::process;
use std::fs::File;
use std::cell::{RefCell};
use std::collections::{HashMap};
use chrono::NaiveDate;
use decimal::d128;
use crate::transaction::{Transaction, ActionRecord};
use crate::account::{Account, RawAccount};
use crate::utils::{round_d128_1e8};
pub fn import_accounts(
rdr: &mut csv::Reader<File>,
raw_acct_map: &mut HashMap<u16, RawAccount>,
acct_map: &mut HashMap<u16, Account>,
) -> Result<(), Box<Error>> {
let header1: csv::StringRecord;
header1 = rdr.headers()?.clone(); // account_num
// Declare remaining headers, and initialize with None
let mut header2: Option<csv::StringRecord> = None; // name
let mut header3: Option<csv::StringRecord> = None; // ticker
let header4: csv::StringRecord; // is_margin
// Declare string vector (array) to which [all but the first three] Strings of the header row will be appended
let mut headerstrings: Vec<String> = [].to_vec();
// Basically, append three empty strings, and then the list of account numbers one by one
for element in header1.into_iter() {
match element { // Previously had `let element_str: String = element.to_string();` and then `match &*element_str`
"txDate" => { headerstrings.push("".to_string()) },
"proceeds" => { headerstrings.push("".to_string()) },
"memo" => { headerstrings.push("".to_string()) },
_ => headerstrings.push(element.to_string())
};
} // End result is headerstrings = ["","","",1,2,3...n]
// Account Creation loop. Iterate through 'data' records. We set hasheaders() to true above, so the first record here is the second row of the CSV
for result in rdr.records() {
// This initial iteration through records will break after the 4th row, after accounts have been created
let record = result?;
if header2 == None {
header2 = Some(record.clone());
continue // After header2 is set, continue to next record
}
else if header3 == None {
header3 = Some(record.clone());
continue // After header3 is set, continue to next record
}
else {
header4 = record.clone();
// println!("Assigned last header, record: {:?}", record);
let warn = "FATAL: Transactions will not import correctly if account numbers in the CSV import file aren't ordered chronologically (i.e., beginning in column 4 - the 1st account column - the values should be 1. The next column should be 2, then 3, etc, until the final account).";
// We've got all our header rows. It's now that we set up the accounts.
println!("Attempting to create accounts...");
let mut no_dup_acct_nums = HashMap::new();
let length = &headerstrings.len();
for num in headerstrings[3..*length].iter().enumerate() {
let counter = no_dup_acct_nums.entry(num).or_insert(0);
*counter += 1;
}
for acct_num in no_dup_acct_nums.keys() {
assert_eq!(no_dup_acct_nums[acct_num], 1, "Found accounts with duplicate numbers during import.");
}
for (idx, item) in headerstrings[3..*length].iter().enumerate() {
// println!("Headerstrings value: {:?}", item);
let ind = idx+3; // Add three because the idx skips the first three 'key' columns
let account_num = item.parse::<u16>()?;
assert_eq!((idx + 1) as u16, account_num, "Found improper Account Number usage: {}", warn);
let name:String = header2.clone().unwrap()[ind].trim().to_string();
let ticker:String = header3.clone().unwrap()[ind].trim().to_string();
let margin_string = &header4.clone()[ind];
let is_margin:bool = match margin_string.trim().to_lowercase().as_str() {
"no" | "non" | "false" => false,
"yes" | "margin" | "true" => true,
_ => { println!("\n Couldn't parse margin value for acct {} {} \n",account_num, name); process::exit(1) }
};
let just_account: RawAccount = RawAccount {
account_num: account_num,
name: name,
ticker: ticker,
is_margin: is_margin,
};
raw_acct_map.insert(account_num, just_account);
let account: Account = Account {
raw_key: account_num,
list_of_lots: RefCell::new([].to_vec())
};
acct_map.insert(account_num, account);
}
break // This `break` exits this scope so `accounts` can be accessed in `import_transactions`. The rdr stays put.
}
};
Ok(())
}
pub fn import_transactions(
rdr: &mut csv::Reader<File>,
txns_map: &mut HashMap<u32, Transaction>,
action_records: &mut HashMap<u32, ActionRecord>,
raw_acct_map: &mut HashMap<u16, RawAccount>,
acct_map: &mut HashMap<u16, Account>,
) -> Result<(), Box<Error>> {
let mut this_tx_number = 0;
let mut this_ar_number = 0;
let mut changed_action_records = 0;
let mut changed_txn_num = Vec::new();
println!("Attempting to create transactions...");
for result in rdr.records() {
// rdr's cursor is at row 5, which is the first transaction row
let record = result?;
this_tx_number += 1;
// First, initialize metadata fields.
let mut this_tx_date: &str = "";
let mut this_proceeds: &str = "";
let mut this_memo: &str = "";
let mut this: String = "".to_string();
// Next, create action_records.
let mut action_records_map_keys_vec: Vec<u32> = [].to_vec();
let mut outgoing_ar: Option<ActionRecord> = None;
let mut incoming_ar: Option<ActionRecord> = None;
let mut outgoing_ar_num: Option<u32> = None;
let mut incoming_ar_num: Option<u32> = None;
for (idx, field) in record.iter().enumerate() {
// Set metadata fields on first three fields.
if idx == 0 { this_tx_date = field; }
else if idx == 1 {
this = field.replace(",", "");
this_proceeds = this.as_str();
}
else if idx == 2 { this_memo = field; }
// Check for empty strings. If not empty, it's a value for an action_record.
else if field != "" {
this_ar_number += 1;
let ind = idx; // starts at 3, which is the fourth field
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;
let amount_str = field.replace(",", "");
let amount = amount_str.parse::<d128>().unwrap();
let amount_rounded = round_d128_1e8(&amount);
if amount != amount_rounded { changed_action_records += 1; changed_txn_num.push(this_tx_number); }
let action_record = ActionRecord {
account_key: account_key,
amount: amount_rounded,
tx_key: this_tx_number,
self_ar_key: this_ar_number,
movements: RefCell::new([].to_vec()),
};
if amount > d128!(0.0) {
incoming_ar = Some(action_record);
incoming_ar_num = Some(this_ar_number);
action_records_map_keys_vec.push(incoming_ar_num.unwrap())
} else {
outgoing_ar = Some(action_record);
outgoing_ar_num = Some(this_ar_number);
action_records_map_keys_vec.insert(0, outgoing_ar_num.unwrap())
};
}
}
match incoming_ar {
Some(incoming_ar) => {
let x = incoming_ar_num.unwrap();
action_records.insert(x, incoming_ar);
},
None => {}
}
match outgoing_ar {
Some(outgoing_ar) => {
let y = outgoing_ar_num.unwrap();
action_records.insert(y, outgoing_ar);
},
None => {}
}
let tx_date = NaiveDate::parse_from_str(this_tx_date, "%m/%d/%y")
.unwrap_or(NaiveDate::parse_from_str(this_tx_date, "%m/%d/%Y")
.expect("%m/%d/%y (or %Y) format required for ledger import")
);
let transaction = Transaction {
tx_number: this_tx_number,
date_as_string: this_tx_date.to_string(),
date: tx_date,
memo: this_memo.to_string(),
proceeds: this_proceeds.parse::<f32>()?,
action_record_idx_vec: action_records_map_keys_vec,
};
txns_map.insert(this_tx_number, transaction);
};
if changed_action_records > 0 {
println!(" Changed actionrecord amounts: {}. Changed txn numbers: {:?}.", changed_action_records, changed_txn_num);
}
Ok(())
}

View File

@ -0,0 +1,373 @@
// Copyright (c) 2017-2019, scoobybejesus
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
use std::collections::{HashMap};
use chrono::NaiveDate;
use decimal::d128;
use crate::transaction::{Transaction, TxType, ActionRecord, Polarity};
use crate::account::{Account, RawAccount};
use crate::utils::{round_d128_1e2};
use crate::user_choices::{LotProcessingChoices};
pub fn add_cost_basis_to_movements(
settings: &LotProcessingChoices,
ars: &HashMap<u32, ActionRecord>,
raw_acct_map: &HashMap<u16, RawAccount>,
acct_map: &HashMap<u16, Account>,
txns_map: &HashMap<u32, Transaction>,
) {
let length = txns_map.len();
for txn_num in 1..=length {
let txn_num = txn_num as u32;
let txn = txns_map.get(&(txn_num)).unwrap();
for ar_num in txn.action_record_idx_vec.iter() {
let ar = ars.get(ar_num).unwrap();
let acct = acct_map.get(&ar.account_key).unwrap();
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
let movements = ar.get_mvmts_in_ar(acct_map, txns_map);
for mvmt in movements.iter() {
let polarity = ar.direction();
let tx_type = txn.transaction_type(ars, raw_acct_map, acct_map);
let is_home_curr = raw_acct.is_home_currency(&settings.home_currency);
let mvmt_copy = mvmt.clone();
let borrowed_mvmt = mvmt_copy.clone();
// println!("Txn: {} on {} of type: {:?}",txn.tx_number,txn.date, txn.transaction_type());
if !raw_acct.is_margin {
match polarity {
Polarity::Outgoing => {
if is_home_curr {
let mvmts_amt = mvmt_copy.amount;
mvmt.cost_basis.set(mvmts_amt);
} else {
let mvmt_lot = mvmt_copy.get_lot(acct_map, ars);
let borrowed_mvmt_list = mvmt_lot.movements.borrow().clone();
let lots_first_mvmt = borrowed_mvmt_list.first().unwrap().clone();
let cb_of_lots_first_mvmt = lots_first_mvmt.cost_basis.get();
let ratio_of_amt_to_lots_first_mvmt = borrowed_mvmt.ratio_of_amt_to_lots_first_mvmt(acct_map, ars);
let unrounded_basis = -(cb_of_lots_first_mvmt * ratio_of_amt_to_lots_first_mvmt);
let rounded_basis = round_d128_1e2(&unrounded_basis);
mvmt.cost_basis.set(rounded_basis);
// if txn.tx_number == 5 {
// println!("Txn#: {}, ratio: {}, cb of lot's 1st mvmt: {}, cost basis: {}", txn.tx_number, ratio_of_amt_to_lots_first_mvmt, cb_of_lots_first_mvmt, cost_basis)
// };
}
// println!("Outgoing a_r from txn: {} of type: {:?}, with {} {} with cost basis: {}",
// txn.tx_number, txn.transaction_type(), borrowed_mvmt.amount, ar.account.ticker, cost_basis);
assert!(mvmt.cost_basis.get() <= d128!(0));
continue
}
Polarity::Incoming => {
if is_home_curr {
let mvmts_amt = mvmt_copy.amount;
mvmt.cost_basis.set(mvmts_amt);
} else {
match tx_type {
TxType::Exchange => {
let other_ar = ars.get(&txn.action_record_idx_vec[0]).unwrap();
let other_acct = acct_map.get(&other_ar.account_key).unwrap();
let raw_other_acct = raw_acct_map.get(&other_acct.raw_key).unwrap();
assert_eq!(other_ar.direction(), Polarity::Outgoing);
let other_ar_is_home_curr = raw_other_acct.is_home_currency(&settings.home_currency);
if other_ar_is_home_curr {
mvmt.cost_basis.set(-(other_ar.amount));
} else {
let ratio_of_amt_to_incoming_mvmts_in_a_r =
borrowed_mvmt.ratio_of_amt_to_incoming_mvmts_in_a_r;
let txn_proceeds = txn.proceeds.to_string().parse::<d128>().unwrap();
let unrounded_basis = txn_proceeds * ratio_of_amt_to_incoming_mvmts_in_a_r;
let rounded_basis = round_d128_1e2(&unrounded_basis);
mvmt.cost_basis.set(rounded_basis);
}
}
TxType::ToSelf => {
let cb_outgoing_ar = retrieve_cost_basis_from_corresponding_outgoing_toself(
txn_num, &ars, txns_map, acct_map);
let ratio_of_amt_to_incoming_mvmts_in_a_r = borrowed_mvmt.ratio_of_amt_to_incoming_mvmts_in_a_r;
let this_ratio = (ratio_of_amt_to_incoming_mvmts_in_a_r)
.to_string()
.parse::<d128>()
.unwrap();
let unrounded_basis = cb_outgoing_ar * this_ratio;
let rounded_basis = round_d128_1e2(&unrounded_basis);
mvmt.cost_basis.set(-rounded_basis);
}
TxType::Flow => {
let txn_proceeds = txn.proceeds.to_string().parse::<d128>().unwrap();
let mvmt_proceeds = round_d128_1e2(&(txn_proceeds *
borrowed_mvmt.ratio_of_amt_to_incoming_mvmts_in_a_r));
mvmt.cost_basis.set(mvmt_proceeds);
}
}
}
// println!("Incoming a_r from txn: {} of type: {:?}, with {} {} with cost basis: {}",
// txn.tx_number, txn.transaction_type(), borrowed_mvmt.amount, ar.account.ticker, cost_basis);
assert!(mvmt.cost_basis.get() >= d128!(0));
continue
}
}
}
}
}
}
fn retrieve_cost_basis_from_corresponding_outgoing_toself(
txn_num: u32,
ars: &HashMap<u32, ActionRecord>,
txns_map: &HashMap<u32, Transaction>,
acct_map: &HashMap<u16, Account>,
) -> d128 {
let txn = txns_map.get(&txn_num).unwrap();
let other_ar_borrowed = &ars.get(&txn.action_record_idx_vec[0]).unwrap();
assert_eq!(other_ar_borrowed.direction(), Polarity::Outgoing);
let mut basis = d128!(0);
let movements = other_ar_borrowed.get_mvmts_in_ar(acct_map, txns_map);
for mvmt in movements.iter() {
basis += mvmt.cost_basis.get();
}
basis
};
}
pub fn add_proceeds_to_movements(
ars: &HashMap<u32, ActionRecord>,
raw_acct_map: &HashMap<u16, RawAccount>,
acct_map: &HashMap<u16, Account>,
txns_map: &HashMap<u32, Transaction>,
) {
let length = txns_map.len();
for txn_num in 1..=length {
let txn_num = txn_num as u32;
let txn = txns_map.get(&(txn_num)).unwrap();
for ar_num in txn.action_record_idx_vec.iter() {
let ar = ars.get(ar_num).unwrap();
let movements = ar.get_mvmts_in_ar(acct_map, txns_map);
for mvmt in movements.iter() {
let polarity = ar.direction();
let tx_type = txn.transaction_type(ars, raw_acct_map, acct_map);
let mvmt_copy = mvmt.clone();
let borrowed_mvmt = mvmt_copy.clone();
match tx_type {
TxType::Exchange => {
match polarity {
Polarity::Outgoing => {
let ratio = borrowed_mvmt.amount / ar.amount;
let proceeds_unrounded = txn.proceeds.to_string().parse::<d128>().unwrap() * ratio;
let proceeds_rounded = round_d128_1e2(&proceeds_unrounded);
mvmt.proceeds.set(proceeds_rounded);
}
Polarity::Incoming => {}
}
}
TxType::Flow => {
let ratio = borrowed_mvmt.amount / ar.amount;
let proceeds_unrounded = txn.proceeds.to_string().parse::<d128>().unwrap() * ratio;
let proceeds_rounded = round_d128_1e2(&proceeds_unrounded);
mvmt.proceeds.set(proceeds_rounded);
}
TxType::ToSelf => {}
}
// println!("Txn: {}, type: {:?} of {} {} w/ proceeds: {} & basis: {}",
// txn.tx_number, txn.transaction_type(), borrowed_mvmt.amount, ar.account.ticker, proceeds, borrowed_mvmt.cost_basis);
}
}
}
}
pub fn apply_like_kind_treatment(
cutoff_date: NaiveDate,
settings: &LotProcessingChoices,
ars: &HashMap<u32, ActionRecord>,
raw_acct_map: &HashMap<u16, RawAccount>,
acct_map: &HashMap<u16, Account>,
txns_map: &HashMap<u32, Transaction>,
) {
let length = txns_map.len();
for txn_num in 1..=length {
let txn_num = txn_num as u32;
let txn = txns_map.get(&(txn_num)).unwrap();
update_current_txn_for_prior_likekind_treatment(txn_num, &settings, &ars, &raw_acct_map, &acct_map, &txns_map);
if txn.date <= cutoff_date {
perform_likekind_treatment_on_txn(txn_num, &settings, &ars, &raw_acct_map, &acct_map, &txns_map);
}
}
}
fn update_current_txn_for_prior_likekind_treatment(
txn_num: u32,
settings: &LotProcessingChoices,
ars: &HashMap<u32, ActionRecord>,
raw_acct_map: &HashMap<u16, RawAccount>,
acct_map: &HashMap<u16, Account>,
txns_map: &HashMap<u32, Transaction>,
) {
let mut sum_of_outgoing_cost_basis_in_ar = d128!(0);
let txn = txns_map.get(&txn_num).unwrap();
for ar_num in txn.action_record_idx_vec.iter() {
let ar = ars.get(ar_num).unwrap();
let acct = acct_map.get(&ar.account_key).unwrap();
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
let movements = ar.get_mvmts_in_ar(acct_map, txns_map);
for mvmt in movements.iter() {
let polarity = ar.direction();
let tx_type = txn.transaction_type(ars, raw_acct_map, acct_map);
let is_home_curr = raw_acct.is_home_currency(&settings.home_currency);
let mvmt_copy = mvmt.clone();
let borrowed_mvmt = mvmt_copy.clone();
if !raw_acct.is_margin {
match polarity {
Polarity::Outgoing => {
if !is_home_curr {
let borrowed_mvmt_lot = borrowed_mvmt.get_lot(acct_map, ars);
let borrowed_mvmt_list = borrowed_mvmt_lot.movements.borrow();
let cb_of_lots_first_mvmt = borrowed_mvmt_list.first().unwrap().cost_basis.get();
let ratio_of_amt_to_lots_first_mvmt = borrowed_mvmt.ratio_of_amt_to_lots_first_mvmt(acct_map, ars);
let unrounded_basis = -(cb_of_lots_first_mvmt * ratio_of_amt_to_lots_first_mvmt);
let rounded_basis = round_d128_1e2(&unrounded_basis);
mvmt.cost_basis.set(rounded_basis);
}
sum_of_outgoing_cost_basis_in_ar += mvmt.cost_basis.get()
}
Polarity::Incoming => {
match tx_type {
TxType::Exchange => {}
TxType::Flow => {}
TxType::ToSelf => {
if !is_home_curr {
let ratio_of_amt_to_incoming_mvmts_in_a_r =
borrowed_mvmt.ratio_of_amt_to_incoming_mvmts_in_a_r;
let unrounded_basis = sum_of_outgoing_cost_basis_in_ar *
ratio_of_amt_to_incoming_mvmts_in_a_r;
let rounded_basis = round_d128_1e2(&unrounded_basis);
mvmt.cost_basis.set(-rounded_basis);
}
}
}
}
}
}
}
}
}
fn perform_likekind_treatment_on_txn(
txn_num: u32,
settings: &LotProcessingChoices,
ars: &HashMap<u32, ActionRecord>,
raw_acct_map: &HashMap<u16, RawAccount>,
acct_map: &HashMap<u16, Account>,
txns_map: &HashMap<u32, Transaction>,
) {
let txn = txns_map.get(&txn_num).unwrap();
let tx_type = txn.transaction_type(ars, raw_acct_map, acct_map);
let og_ar = ars.get(&txn.action_record_idx_vec.first().unwrap()).unwrap();
let ic_ar = ars.get(&txn.action_record_idx_vec.last().unwrap()).unwrap();
let og_acct = acct_map.get(&og_ar.account_key).unwrap();
let ic_acct = acct_map.get(&ic_ar.account_key).unwrap();
let raw_og_acct = raw_acct_map.get(&og_acct.raw_key).unwrap();
let raw_ic_acct = raw_acct_map.get(&ic_acct.raw_key).unwrap();
fn both_are_non_home_curr(raw_og_acct: &RawAccount, raw_ic_acct: &RawAccount, settings: &LotProcessingChoices) -> bool {
let og_is_home_curr = raw_og_acct.is_home_currency(&settings.home_currency);
let ic_is_home_curr = raw_ic_acct.is_home_currency(&settings.home_currency);
let both_are_non_home_curr = !ic_is_home_curr && !og_is_home_curr;
both_are_non_home_curr
}
match tx_type {
TxType::Exchange => {
if both_are_non_home_curr(raw_og_acct, raw_ic_acct, settings) {
let mut sum_of_outgoing_cost_basis_in_ar = d128!(0);
for ar_num in txn.action_record_idx_vec.iter() {
let ar = ars.get(ar_num).unwrap();
let movements = ar.get_mvmts_in_ar(acct_map, txns_map);
for mvmt in movements.iter() {
let polarity = ar.direction();
let mvmt_copy = mvmt.clone();
let borrowed_mvmt = mvmt_copy.clone();
match polarity {
Polarity::Outgoing => {
let cb = borrowed_mvmt.cost_basis.get();
sum_of_outgoing_cost_basis_in_ar += cb;
mvmt.proceeds.set(-cb);
}
Polarity::Incoming => {
let ratio_of_amt_to_incoming_mvmts_in_a_r =
borrowed_mvmt.ratio_of_amt_to_incoming_mvmts_in_a_r;
let unrounded_basis = sum_of_outgoing_cost_basis_in_ar *
ratio_of_amt_to_incoming_mvmts_in_a_r;
let rounded_basis = round_d128_1e2(&unrounded_basis);
mvmt.cost_basis.set(-rounded_basis);
}
}
}
}
}
}
TxType::Flow => {
if txn.action_record_idx_vec.len() == 2 {
for ar_num in txn.action_record_idx_vec.iter() {
let ar = ars.get(ar_num).unwrap();
let movements = ar.get_mvmts_in_ar(acct_map, txns_map);
let polarity = ar.direction();
for mvmt in movements.iter() {
match polarity {
Polarity::Outgoing => {}
Polarity::Incoming => {
// Reminder: May need extra logic here if margin exchange trades get cost_basis and proceeds
mvmt.cost_basis.set(d128!(0));
mvmt.proceeds.set(d128!(0));
}
}
}
}
}
}
TxType::ToSelf => {
return
}
}
}

300
src/main.rs Normal file
View File

@ -0,0 +1,300 @@
// Copyright (c) 2017-2019, scoobybejesus
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
#![allow(dead_code)]
#![allow(unused_variables)]
#![allow(unused_assignments)]
// Note: the above are possibly temporary, to silence "x was not used" warnings.
// #[warn(dead_code)] is the default (same for unused_variables)
use std::ffi::OsString;
use structopt::StructOpt;
use std::path::PathBuf;
use std::process;
use std::io::{self, BufRead};
use std::error::Error;
mod account;
mod transaction;
mod core_functions;
mod import_accts_txns;
mod create_lots_mvmts;
mod import_cost_proceeds_etc;
mod user_choices;
mod export;
mod utils;
mod tests;
use crate::user_choices::LotProcessingChoices;
#[derive(StructOpt, Debug)]
#[structopt(name = "cryptools-rs")]
struct Cli {
/// File to be imported. (Currently, the only supported date format is %m/%d/%y.)
#[structopt(name = "file", parse(from_os_str))]
file_to_import: Option<PathBuf>,
/// Output directory for exported reports.
#[structopt(name = "output directory", short, long = "output", default_value = ".", parse(from_os_str))]
output_dir_path: PathBuf,
/// When the 'accept' flag is set, this program will ultimately export CSV files to either the default or chosen 'output directory' unless this flag is set.
#[structopt(name = "suppress reports", short, long = "suppress")]
suppress_reports: bool,
/// 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 = "cutoff", parse(from_os_str))]
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", 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,
/// 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,
/// User is instructing the program to use the command line flags/options/arg they provide without confirming them during runtime. (Faster)
#[structopt(name = "accept args", short, long = "accept")]
accept_args: bool,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Cli::from_args();
println!(
"
Hello, crypto-folk! Welcome to cryptools-rs!
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.
");
let input_file_path;
let output_dir_path = args.output_dir_path;
let should_export;
let account_map;
let raw_acct_map;
let action_records_map;
let transactions_map;
let mut settings;
let like_kind_settings;
let home_currency_choice = args.home_currency.into_string().expect("Home currency should be in the form of a ticker in CAPS.");
let costing_method_choice;
if !args.accept_args {
shall_we_proceed();
fn shall_we_proceed() {
println!("Shall we proceed? [Y/n] ");
match _proceed() {
Ok(()) => {}
Err(err) => { println!("Failure to proceed. {}", err); process::exit(1); }
};
fn _proceed() -> Result<(), Box<Error>> {
let mut input = String::new();
let stdin = io::stdin();
stdin.lock().read_line(&mut input)?;
match input.trim().to_ascii_lowercase().as_str() {
"y" | "ye" | "yes" | "" => { Ok(()) },
"n" | "no" => { println!("We have NOT proceeded..."); process::exit(0); },
_ => { println!("Please respond with 'y' or 'n' (or 'yes' or 'no')."); _proceed() }
}
}
}
if let Some(file) = args.file_to_import {
input_file_path = file
} else {
input_file_path = user_choices::choose_file_for_import();
}
costing_method_choice = LotProcessingChoices::choose_inventory_costing_method();
let lk_cutoff_date_opt_string;
if let Some(lk_cutoff) = args.cutoff_date {
lk_cutoff_date_opt_string = Some(lk_cutoff.into_string().unwrap())
} else {
lk_cutoff_date_opt_string = None
};
let (like_kind_election, like_kind_cutoff_date) = LotProcessingChoices::elect_like_kind_treatment(&lk_cutoff_date_opt_string);
settings = LotProcessingChoices {
export_path: output_dir_path,
home_currency: home_currency_choice,
costing_method: costing_method_choice,
enable_like_kind_treatment: like_kind_election,
lk_cutoff_date_string: like_kind_cutoff_date,
};
let (
account_map1,
raw_acct_map1,
action_records_map1,
transactions_map1,
like_kind_settings1
) = core_functions::import_and_process_final(input_file_path, &settings);
account_map = account_map1;
raw_acct_map = raw_acct_map1;
action_records_map = action_records_map1;
transactions_map = transactions_map1;
like_kind_settings = like_kind_settings1;
should_export = export_reports_to_output_dir(&mut settings);
fn export_reports_to_output_dir(settings: &mut LotProcessingChoices) -> bool {
println!("\nThe directory currently selected for exporting reports is: {}", settings.export_path.to_str().unwrap());
if &settings.export_path.to_str().unwrap() == &"." {
println!(" (A 'dot' denotes the default value: current working directory.)");
}
println!("\nExport reports to selected directory? [Y/n/c] ('c' to 'change') ");
let choice = match _export(settings) {
Ok(choice) => { choice }
Err(err) => { println!("Export choice error. {}", err); process::exit(1); }
};
fn _export(settings: &mut LotProcessingChoices) -> Result<(bool), Box<Error>> {
let mut input = String::new();
let stdin = io::stdin();
stdin.lock().read_line(&mut input)?;
match input.trim().to_ascii_lowercase().as_str() {
"y" | "ye" | "yes" | "" => { println!("Creating reports now."); Ok(true) },
"n" | "no" => { println!("Okay, no reports were created."); Ok(false) },
"c" | "change" => {
let new_dir = user_choices::choose_export_dir();
settings.export_path = PathBuf::from(new_dir);
println!("Creating reports now in newly chosen path.");
Ok(true)
},
_ => { println!("Please respond with 'y', 'n', or 'c' (or 'yes' or 'no' or 'change').");
_export(settings)
}
}
}
choice
}
} else {
if let Some(file) = args.file_to_import {
input_file_path = file
} else {
println!("Flag to 'accept args' was set, but 'file' is missing, though it is a required field. Exiting.");
process::exit(0);
}
let like_kind_election;
let like_kind_cutoff_date_string: String;
if let Some(date) = args.cutoff_date {
like_kind_election = true;
like_kind_cutoff_date_string = date.into_string().unwrap();
} else {
like_kind_election = false;
like_kind_cutoff_date_string = "1-1-1".to_string();
};
match args.inv_costing_method.clone().into_string().expect("Invalid choice on costing method. Aborting.").trim() {
"1" | "2" | "3" | "4" => {}
_ => { println!("Invalid choice for inventory costing method. Exiting."); process::exit(0); }
}
let costing_method_choice = LotProcessingChoices::inv_costing_from_cmd_arg(args.inv_costing_method.into_string().unwrap());
settings = LotProcessingChoices {
export_path: output_dir_path,
home_currency: home_currency_choice,
costing_method: costing_method_choice,
enable_like_kind_treatment: like_kind_election,
lk_cutoff_date_string: like_kind_cutoff_date_string,
};
let (
account_map1,
raw_acct_map1,
action_records_map1,
transactions_map1,
like_kind_settings1
) = core_functions::import_and_process_final(input_file_path, &settings);
account_map = account_map1;
raw_acct_map = raw_acct_map1;
action_records_map = action_records_map1;
transactions_map = transactions_map1;
like_kind_settings = like_kind_settings1;
should_export = !args.suppress_reports;
}
if should_export {
export::_1_account_sums_to_csv(
&settings,
&raw_acct_map,
&account_map
);
export::_2_account_sums_nonzero_to_csv(
&account_map,
&settings,
&raw_acct_map
);
export::_5_transaction_mvmt_summaries_to_csv(
&settings,
&action_records_map,
&raw_acct_map,
&account_map,
&transactions_map
);
}
// use tests::test;
// test::run_tests(
// &transactions_map,
// &action_records_map,
// &account_map
// );
Ok(())
// // export::transactions_to_csv(&transactions);
// // println!("\nReturned from `fn transactions_to_csv`. It worked!! Right?");
// export::accounts_to_csv(&accounts);
// println!("\nReturned from `fn accounts_to_csv`. It worked!! Right?");
}

4
src/tests/mod.rs Normal file
View File

@ -0,0 +1,4 @@
// Copyright (c) 2017-2019, scoobybejesus
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
pub mod test;

243
src/tests/test.rs Normal file
View File

@ -0,0 +1,243 @@
// Copyright (c) 2017-2019, scoobybejesus
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
use std::fs;
use std::collections::{HashMap};
use decimal::d128;
use crate::account::{Account};
use crate::transaction::{Transaction, ActionRecord};
use crate::utils::*;
pub fn run_tests(
transactions_map: &HashMap<u32, Transaction>,
action_records_map: &HashMap<u32, ActionRecord>,
account_map: &HashMap<u16, Account>,
) {
compare_movements_across_implementations(
&transactions_map,
&action_records_map,
&account_map
);
do_mvmts_know_what_lot_they_are_in(&account_map);
test_action_records_amts_vs_mvmt_amts(
&transactions_map,
&action_records_map,
&account_map
);
test_quantize_from_incoming_multiple_lots_fn(d128!(20), d128!(200), d128!(50));
test_quantize_from_incoming_multiple_lots_fn(d128!(1), d128!(6), d128!(1234567.1234567896));
// test_dec_rounded("123456789.123456789");
// test_dec_rounded("123456.123456");
// test_dec_rounded("1234567891234.1234567891234");
// test_dec_rounded_1e8("123456789.123456789");
// test_dec_rounded_1e8("123456.123456");
// test_dec_rounded_1e8("1234567891234.1234567891234");
// test_dec_rounded_1e2("123456789.123456789");
// test_dec_rounded_1e2("123456.123456");
// test_dec_rounded_1e2("1234567891234.1234567891234");
}
fn compare_movements_across_implementations(
transactions_map: &HashMap<u32, Transaction>,
action_records_map: &HashMap<u32, ActionRecord>,
account_map: &HashMap<u16, Account>,
) {
// THIS BIG ASS TEST CREATES TWO FILES TO COMPARE actionrecords.movements with ar.get_mvmts_in_ar()
// DELETE THIS WHEN movements FIELD OF ActionRecords IS DELETED
let mut line: String = "".to_string();
for tx_num in 1..=transactions_map.len() {
let txn_num = tx_num as u32;
line += &("Transaction ".to_string() + &txn_num.to_string() + &"\n".to_string());
let txn = transactions_map.get(&(txn_num)).unwrap();
for (idx, ar_num) in txn.action_record_idx_vec.iter().enumerate() {
line += &("ActionRecord index: ".to_string() + &idx.to_string());
let ar = action_records_map.get(&(ar_num)).unwrap();
line += &(
" with actionRecord key: ".to_string()
+ &ar_num.to_string()
+ " with amount: "
+ &ar.amount.to_string() + &"\n".to_string()
);
let mvmts = ar.get_mvmts_in_ar(&account_map, &transactions_map);
let mut amts = d128!(0);
for mvmt in mvmts {
amts += mvmt.amount;
line += &("Movement ".to_string() +
&mvmt.amount.to_string() +
&" on ".to_string() +
&mvmt.date_as_string +
&"\n".to_string());
}
line += &("Amount total: ".to_string() + &amts.to_string() + &"\n".to_string());
if amts - ar.amount != d128!(0) {
line += &("&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&".to_string());
println!("Movement amounts via get_mvmts_in_ar() different from actionRecord.amount. Aborting.");
use std::process::exit; exit(1)
};
}
}
fs::write("/tmp/foo", &line).expect("Unable to write file");
let mut line2: String = "".to_string();
for tx_num in 1..=transactions_map.len() {
let txn_num = tx_num as u32;
line2 += &("Transaction ".to_string() + &txn_num.to_string() + &"\n".to_string());
let txn = transactions_map.get(&(txn_num)).unwrap();
for (idx, ar_num) in txn.action_record_idx_vec.iter().enumerate() {
line2 += &("ActionRecord index: ".to_string() + &idx.to_string());
let ar = action_records_map.get(&(ar_num)).unwrap();
line2 += &(
" with actionRecord key: ".to_string()
+ &ar_num.to_string()
+ " with amount: "
+ &ar.amount.to_string()
+ &"\n".to_string()
);
// let mvmts = ar.get_mvmts_in_ar(&account_map);
let mut amts = d128!(0);
for mvmt in ar.movements.borrow().iter() {
amts += mvmt.amount;
line2 += &("Movement ".to_string() +
&mvmt.amount.to_string() +
&" on ".to_string() +
&mvmt.date_as_string +
&"\n".to_string());
}
line2 += &("Amount total: ".to_string() + &amts.to_string() + &"\n".to_string());
if amts - ar.amount != d128!(0) {
line2 += &("&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&".to_string());
println!("Movement amounts via ar.movements different from actionRecord.amount. Aborting.");
use std::process::exit; exit(1)
};
}
}
fs::write("/tmp/foo2", &line2).expect("Unable to write file");
}
fn do_mvmts_know_what_lot_they_are_in(account_map: &HashMap<u16, Account>,) {
for (acct_num, acct) in account_map.iter() {
for lot in acct.list_of_lots.borrow().iter() {
for mvmt in lot.movements.borrow().iter() {
if mvmt.lot_num != lot.lot_number {
println!("ERROR: For txn {} on {}, movement.lot_num != lot.lot_number.",
mvmt.transaction_key, mvmt.date);
}
}
}
}
}
pub fn test_action_records_amts_vs_mvmt_amts(
transactions_map: &HashMap<u32, Transaction>,
action_records_map: &HashMap<u32, ActionRecord>,
account_map: &HashMap<u16, Account>,
) {
let mut mvmt_amt_acct_lot: d128 = d128!(0);
let mut mvmt_amt_ar: d128 = d128!(0);
let mut ar_amts: d128 = d128!(0);
for tx_num in 1..=transactions_map.len() {
let txn_num = tx_num as u32;
let txn = transactions_map.get(&(txn_num)).unwrap();
for ar_num in &txn.action_record_idx_vec {
let ar = action_records_map.get(&(ar_num)).unwrap();
let mvmts = ar.get_mvmts_in_ar(&account_map, &transactions_map);
for mvmt in mvmts {
mvmt_amt_ar += mvmt.amount
}
}
}
for acct_num in 1..=account_map.len() {
let acct = account_map.get(&(acct_num as u16)).unwrap();
for lot in acct.list_of_lots.borrow().iter() {
for mvmt in lot.movements.borrow().iter() {
mvmt_amt_acct_lot += mvmt.amount
}
}
}
for tx_num in 1..=transactions_map.len() {
let txn_num = tx_num as u32;
let txn = transactions_map.get(&(txn_num)).unwrap();
for ar_num in &txn.action_record_idx_vec {
let ar = action_records_map.get(&(ar_num)).unwrap();
ar_amts += ar.amount
}
}
println!(" \n\t\t\tActionRecord amounts pulled from iterating over transactions: {}
\n\t\t\tMovement amounts pulled from actionRecords dynamically after: {}
\n\t\t\tMovement amounts where movements are recorded directly in lots: {}\n",
ar_amts,
mvmt_amt_ar,
mvmt_amt_acct_lot
);
}
fn test_quantize_from_incoming_multiple_lots_fn (
outgoing_mvmt_amt: d128,
outgoing_ar_amt: d128,
incoming_ar_amt: d128,
) {
let rounded_example = d128::from(1).scaleb(d128::from(-8));
//
println!("Og mvmt amt: {:?}, Og ar amt: {:?}, Ic ar amt: {:?}", outgoing_mvmt_amt, outgoing_ar_amt, incoming_ar_amt);
let ratio_of_outgoing_to_total_ar = outgoing_mvmt_amt / outgoing_ar_amt; // Negative divided by negative is positive
println!("ratio_of_outgoing: {:.20}", ratio_of_outgoing_to_total_ar);
let tentative_incoming_amt = ratio_of_outgoing_to_total_ar * incoming_ar_amt;
println!("tentative_inc_amt: {:.20}", tentative_incoming_amt);
let corresponding_incoming_amt = tentative_incoming_amt.quantize(rounded_example);
println!("corresponding_inc_amt: {}", corresponding_incoming_amt);
}
// Yields:
// Og mvmt amt: 20, Og ar amt: 200, Ic ar amt: 50
// ratio_of_outgoing: 0.1
// tentative_inc_amt: 5.0
// corresponding_inc_amt: 5.00000000
// Og mvmt amt: 1, Og ar amt: 6, Ic ar amt: 1234567.1234567896
// ratio_of_outgoing: 0.166666666666666666
// tentative_inc_amt: 205761.1872427982666
// corresponding_inc_amt: 205761.18724280
fn test_dec_rounded(random_float_string: &str) {
let places_past_decimal = d128!(8);
let amt = random_float_string.parse::<d128>().unwrap();
let amt2 = round_d128_generalized(&amt, places_past_decimal);
println!("Float into d128: {:?}; d128 rounded to {}: {:?}", amt, places_past_decimal, amt2);
// Results of this test suggest that quantize() is off by one. round_d128_1e8() was adjusted accordingly.
}
fn test_dec_rounded_1e8(random_float_string: &str) {
let amt = random_float_string.parse::<d128>().unwrap();
let amt2 = round_d128_1e8(&amt);
println!("Float into d128: {:?}; d128 rounded to 8 places: {:?}", amt, amt2);
// Results of this test suggest that quantize() is off by one. round_d128_1e8() was adjusted accordingly.
}
fn test_dec_rounded_1e2(random_float_string: &str) {
let amt = random_float_string.parse::<d128>().unwrap();
let amt2 = round_d128_1e2(&amt);
println!("String into d128: {:?}; d128 rounded to 2 places: {:?}", amt, amt2);
// Results of this test suggest that quantize() is off by one. round_d128_1e8() was adjusted accordingly.
}

313
src/transaction.rs Normal file
View File

@ -0,0 +1,313 @@
// Copyright (c) 2017-2019, scoobybejesus
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
use std::rc::{Rc};
use std::cell::{RefCell};
use std::process::exit;
use std::fmt;
use std::collections::{HashMap};
use decimal::d128;
use chrono::NaiveDate;
use serde_derive::{Serialize, Deserialize};
use crate::user_choices::LotProcessingChoices;
use crate::account::{Account, Movement, RawAccount};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Transaction {
pub tx_number: u32, // Does NOT start at zero. First txn is 1.
pub date_as_string: String,
pub date: NaiveDate,
pub memo: String,
pub proceeds: f32,
pub action_record_idx_vec: Vec<u32>,
}
impl Transaction {
pub fn transaction_type(
&self,
ars: &HashMap<u32, ActionRecord>,
raw_acct_map: &HashMap<u16, RawAccount>,
acct_map: &HashMap<u16, Account>,
) -> TxType {
if self.action_record_idx_vec.len() == 1 {
TxType::Flow
}
else if self.action_record_idx_vec.len() == 2 {
// This exercise of splitting the strings is because of margin accounts, where BTC borrowed to buy XMR would reflect as BTC_xmr
let first_ar = ars.get(&self.action_record_idx_vec[0]).unwrap();
let second_ar = ars.get(&self.action_record_idx_vec[1]).unwrap();
let first_acct = acct_map.get(&first_ar.account_key).unwrap();
let second_acct = acct_map.get(&second_ar.account_key).unwrap();
let ar1_raw_acct = raw_acct_map.get(&first_acct.raw_key).unwrap();
let ar2_raw_acct = raw_acct_map.get(&second_acct.raw_key).unwrap();
let ar1_ticker_full = ar1_raw_acct.ticker.clone();
let ar2_ticker_full = ar2_raw_acct.ticker.clone();
let ar1_ticker_comp: Vec<&str> = ar1_ticker_full.split('_').collect();
let ar2_ticker_comp: Vec<&str> = ar2_ticker_full.split('_').collect();
let ar1_ticker = ar1_ticker_comp[0];
let ar2_ticker = ar2_ticker_comp[0];
if first_ar.direction() == second_ar.direction() {
println!("Program exiting. Found transaction with two actionRecords with the same polarity: {:?}", self); exit(1);
}
if ar1_ticker == ar2_ticker {
if ar1_raw_acct.is_margin != ar2_raw_acct.is_margin {
TxType::Flow
}
else {
TxType::ToSelf
}
}
else {
TxType::Exchange
}
}
else if self.action_record_idx_vec.len() > 2 {
println!("Program exiting. Found transaction with too many actionRecords: {:?}", self); exit(1);
}
else {
println!("Program exiting. Found transaction with no actionRecords: {:?}", self); exit(1);
}
}
pub fn marginness(
&self,
ars: &HashMap<u32, ActionRecord>,
raw_acct_map: &HashMap<u16, RawAccount>,
acct_map: &HashMap<u16, Account>
) -> TxHasMargin {
if self.action_record_idx_vec.len() == 1 {
let ar = ars.get(&self.action_record_idx_vec[0]).unwrap();
let acct = acct_map.get(&ar.account_key).unwrap();
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
if raw_acct.is_margin {
TxHasMargin::OneAR
} else {
TxHasMargin::NoARs
}
} else {
assert_eq!(self.action_record_idx_vec.len(),2);
let first_ar = ars.get(&self.action_record_idx_vec[0]).unwrap();
let second_ar = ars.get(&self.action_record_idx_vec[1]).unwrap();
let first_acct = acct_map.get(&first_ar.account_key).unwrap();
let second_acct = acct_map.get(&second_ar.account_key).unwrap();
let first_raw_acct = &raw_acct_map.get(&first_acct.raw_key).unwrap();
let second_raw_acct = &raw_acct_map.get(&second_acct.raw_key).unwrap();
if first_raw_acct.is_margin {
if second_raw_acct.is_margin {TxHasMargin::TwoARs} else {TxHasMargin::OneAR}
} else if second_raw_acct.is_margin {TxHasMargin::OneAR} else {TxHasMargin::NoARs}
}
}
pub fn get_base_and_quote_raw_acct_keys(
&self,
ars: &HashMap<u32, ActionRecord>,
raw_accts: &HashMap<u16, RawAccount>,
acct_map: &HashMap<u16, Account>
) -> (u16, u16) {
assert_eq!(self.transaction_type(ars, raw_accts, acct_map), TxType::Exchange,
"This can only be called on exchange transactions.");
let first_ar = ars.get(&self.action_record_idx_vec[0]).unwrap();
let second_ar = ars.get(&self.action_record_idx_vec[1]).unwrap();
let first_acct = acct_map.get(&first_ar.account_key).unwrap();
let second_acct = acct_map.get(&second_ar.account_key).unwrap();
let first_acct_raw_key = first_acct.raw_key;
let second_acct_raw_key = second_acct.raw_key;
let first_raw_acct = raw_accts.get(&first_acct_raw_key).unwrap();
let second_raw_acct = raw_accts.get(&second_acct_raw_key).unwrap();
assert_eq!(first_raw_acct.is_margin, true, "First actionrecord wasn't a margin account. Both must be.");
assert_eq!(second_raw_acct.is_margin, true, "Second actionrecord wasn't a margin account. Both must be.");
let quote: u16;
let base: u16;
if first_raw_acct.ticker.contains('_') {
quote = first_acct_raw_key;
base = second_acct_raw_key;
(base, quote)
} else if second_raw_acct.ticker.contains('_') {
base = first_acct_raw_key;
quote = second_acct_raw_key;
(base, quote)
} else {
println!("{}", VariousErrors::MarginNoUnderbar); use std::process::exit; exit(1)
}
}
pub fn get_outgoing_exchange_and_flow_mvmts(
&self,
settings: &LotProcessingChoices,
ars: &HashMap<u32, ActionRecord>,
raw_acct_map: &HashMap<u16, RawAccount>,
acct_map: &HashMap<u16, Account>,
txns_map: &HashMap<u32, Transaction>,
) -> Vec<Rc<Movement>> {
let mut flow_or_outgoing_exchange_movements = [].to_vec();
for ar_num in self.action_record_idx_vec.iter() {
let ar = ars.get(ar_num).unwrap();
let acct = acct_map.get(&ar.account_key).unwrap();
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
if !raw_acct.is_home_currency(&settings.home_currency) & !raw_acct.is_margin {
let movements = ar.get_mvmts_in_ar(acct_map, txns_map);
match self.transaction_type(ars, raw_acct_map, acct_map) {
TxType::Exchange => {
if Polarity::Outgoing == ar.direction() {
for mvmt in movements.iter() {
flow_or_outgoing_exchange_movements.push(mvmt.clone());
}
}
}
TxType::Flow => {
for mvmt in movements.iter() {
flow_or_outgoing_exchange_movements.push(mvmt.clone());
}
}
_ => {}
}
}
}
flow_or_outgoing_exchange_movements
}
}
#[derive(Clone, Debug)]
pub struct ActionRecord {
pub account_key: u16,
pub amount: d128,
pub tx_key: u32,
pub self_ar_key: u32,
pub movements: RefCell<Vec<Rc<Movement>>>,
}
impl ActionRecord {
pub fn direction(&self) -> Polarity {
if self.amount < d128!(0.0) { Polarity::Outgoing}
else { Polarity::Incoming }
}
pub fn is_quote_acct_for_margin_exch(
&self,
raw_accts: &HashMap<u16, RawAccount>,
acct_map: &HashMap<u16, Account>
) -> bool {
let acct = acct_map.get(&self.account_key).unwrap();
let raw_acct = raw_accts.get(&acct.raw_key).unwrap();
raw_acct.ticker.contains('_')
}
pub fn get_mvmts_in_ar(
&self,
acct_map: &HashMap<u16, Account>,
txns_map: &HashMap<u32, Transaction>,
) -> Vec<Rc<Movement>> {
let polarity = Self::direction(self);
let txn = txns_map.get(&self.tx_key).unwrap();
let mut movements_in_ar = [].to_vec();
let acct = acct_map.get(&self.account_key).unwrap();
for lot in acct.list_of_lots.borrow().iter() {
for mvmt in lot.movements.borrow().iter() {
if (mvmt.date) <= txn.date {
if mvmt.action_record_key == self.self_ar_key {
// if polarity == Polarity::Incoming{
// movements_in_ar.push(mvmt.clone())
// } else {
movements_in_ar.insert(0, mvmt.clone())
// }
// ^^ leaving that ugliness for this commit on purpose
}
}
}
}
movements_in_ar
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum TxType {
Exchange,
ToSelf,
Flow,
}
impl fmt::Display for TxType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
TxType::Exchange => write!(f, "Exchange"),
TxType::ToSelf => write!(f, "ToSelf"),
TxType::Flow => write!(f, "Flow"),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum TxHasMargin {
NoARs,
OneAR,
TwoARs,
}
impl fmt::Display for TxHasMargin {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
TxHasMargin::NoARs => write!(f, "No actionrecord is a margin account"),
TxHasMargin::OneAR => write!(f, "One actionrecord is a margin account"),
TxHasMargin::TwoARs => write!(f, "Two actionrecords are margin accounts"),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Polarity {
Outgoing,
Incoming,
}
impl fmt::Display for Polarity {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Polarity::Outgoing => write!(f, "Outgoing"),
Polarity::Incoming => write!(f, "Incoming"),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum VariousErrors {
MarginNoUnderbar,
}
impl fmt::Display for VariousErrors {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
VariousErrors::MarginNoUnderbar => write!(f,
"Neither account ticker contained an underbar '_', so the quote account couldn't be determined.
For example, for the 'USD/EUR' pair, USD is the base account and EUR is the quote account.
In order for this software to function correctly, the quote ticker should be denoted as 'EUR_usd'.")
}
}
}

292
src/user_choices.rs Normal file
View File

@ -0,0 +1,292 @@
// Copyright (c) 2017-2019, scoobybejesus
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
use std::error::Error;
use std::io::{self, BufRead};
use std::process;
use std::path::PathBuf;
use chrono::NaiveDate;
use rustyline::completion::{Completer, FilenameCompleter, Pair};
use rustyline::{CompletionType, Config, Context, EditMode, Editor, Helper};
use rustyline::config::OutputStreamType;
use rustyline::hint::{Hinter};
use rustyline::error::ReadlineError;
use rustyline::highlight::{Highlighter};
use structopt::StructOpt;
use crate::utils;
pub fn choose_file_for_import() -> PathBuf {
println!("Please input a file (absolute or relative path) to import: ");
// PathBuf::from("/Users/scoob/Documents/Repos/cryptools-rs/private/RawTxForImport-pycleaned.csv")
let file_str = _get_path();
PathBuf::from(file_str.unwrap())
}
pub fn choose_export_dir() -> PathBuf {
println!("Please input a file path for exports: ");
// PathBuf::from("/Users/scoob/Documents/Testing/rust_exports/")
let file_str = _get_path();
PathBuf::from(file_str.unwrap())
}
fn _get_path() -> Result<(String), Box<Error>> {
struct MyHelper {
completer: FilenameCompleter,
colored_prompt: String,
}
impl Completer for MyHelper {
type Candidate = Pair;
fn complete(
&self,
line: &str,
pos: usize,
ctx: &Context<'_>,
) -> Result<(usize, Vec<Pair>), ReadlineError> {
self.completer.complete(line, pos, ctx)
}
}
impl Hinter for MyHelper {}
impl Highlighter for MyHelper {}
impl Helper for MyHelper {}
let h = MyHelper {
completer: FilenameCompleter::new(),
colored_prompt: "".to_owned(),
};
let config = Config::builder()
.history_ignore_space(true)
.completion_type(CompletionType::Circular)
.edit_mode(EditMode::Vi)
.output_stream(OutputStreamType::Stdout)
.build();
let count = 1;
let mut rl = Editor::with_config(config);
let p = format!("{}> ", count);
rl.set_helper(Some(h));
rl.helper_mut().unwrap().colored_prompt = format!("\x1b[1;32m{}\x1b[0m", p);
let readline = rl.readline(">> ");
match readline {
Ok(line) => {
rl.add_history_entry(line.as_str());
println!("");
Ok(line)
},
Err(err) => {
println!("Error during Rustyline: {:?}", err);
process::exit(1)
}
}
}
#[derive(Clone, Debug, PartialEq, StructOpt)]
pub enum InventoryCostingMethod {
/// 1. LIFO according to the order the lot was created.
LIFObyLotCreationDate,
/// 2. LIFO according to the basis date of the lot.
LIFObyLotBasisDate,
/// 3. FIFO according to the order the lot was created.
FIFObyLotCreationDate,
/// 4. FIFO according to the basis date of the lot.
FIFObyLotBasisDate,
}
// impl std::convert::From<OsStr> for InventoryCostingMethod {
// fn from(osstr: OsStr) -> InventoryCostingMethod {
// let osstring1 = OsString::from(Box::<osstr>);
// let new_string = osstr.into_string().expect("Invalid input. Could not convert to string.");
// let method = match new_string.trim() {
// "1" => InventoryCostingMethod::LIFObyLotCreationDate,
// "2" => InventoryCostingMethod::LIFObyLotBasisDate,
// "3" => InventoryCostingMethod::FIFObyLotCreationDate,
// "4" => InventoryCostingMethod::FIFObyLotBasisDate,
// _ => { println!("Invalid choice. Could not convert."); process::exit(1)
// }
// };
// method
// }
// }
pub struct LotProcessingChoices {
pub export_path: PathBuf,
pub home_currency: String,
pub enable_like_kind_treatment: bool,
pub costing_method: InventoryCostingMethod,
pub lk_cutoff_date_string: String,
}
impl LotProcessingChoices {
pub fn choose_inventory_costing_method() -> InventoryCostingMethod {
println!("Choose the lot inventory costing method. [Default: 1]");
println!("1. LIFO according to the order the lot was created.");
println!("2. LIFO according to the basis date of the lot.");
println!("3. FIFO according to the order the lot was created.");
println!("4. FIFO according to the basis date of the lot.");
let method = match _costing_method() {
Ok(x) => {x},
Err(err) => { process::exit(1) }
};
fn _costing_method() -> Result<(InventoryCostingMethod), Box<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
"1" | "" => Ok(InventoryCostingMethod::LIFObyLotCreationDate),
"2" => Ok(InventoryCostingMethod::LIFObyLotBasisDate),
"3" => Ok(InventoryCostingMethod::FIFObyLotCreationDate),
"4" => Ok(InventoryCostingMethod::FIFObyLotBasisDate),
_ => { println!("Invalid choice. Please enter a valid number."); _costing_method() }
}
}
method
}
pub fn inv_costing_from_cmd_arg(arg: String) -> InventoryCostingMethod {
let method = match _costing_method(arg) {
Ok(x) => {x},
Err(err) => { process::exit(1) }
};
fn _costing_method(input: String) -> Result<(InventoryCostingMethod), Box<Error>> {
match input.trim() { // Without .trim(), there's a hidden \n or something preventing the match
"1" => Ok(InventoryCostingMethod::LIFObyLotCreationDate),
"2" => Ok(InventoryCostingMethod::LIFObyLotBasisDate),
"3" => Ok(InventoryCostingMethod::FIFObyLotCreationDate),
"4" => Ok(InventoryCostingMethod::FIFObyLotBasisDate),
_ => { println!("Invalid choice. Please enter a valid number."); _costing_method(input) }
}
}
method
}
pub fn elect_like_kind_treatment(cutoff_date_arg: &Option<String>) -> (bool, String) {
let election: bool;
let date: String;
if cutoff_date_arg.is_some() {
let provided_date = NaiveDate::parse_from_str(&cutoff_date_arg.clone().unwrap(), "%y-%m-%d")
.unwrap_or(NaiveDate::parse_from_str(&cutoff_date_arg.clone().unwrap(), "%Y-%m-%d")
.expect("Date entered as -c command line arg has an incorrect format."));
println!("\nUse like-kind exchange treatment through {}? [Y/n/c] ('c' to 'change') ", provided_date);
let (election, date) = match _elect_like_kind_arg(&cutoff_date_arg, provided_date) {
Ok(x) => {x},
Err(err) => { println!("Fatal error in fn elect_like_kind_treatment()."); process::exit(1) }
};
fn _elect_like_kind_arg(cutoff_date_arg: &Option<String>, provided_date: NaiveDate) -> Result<(bool, String), Box<Error>> {
let mut input = String::new();
let stdin = io::stdin();
stdin.lock().read_line(&mut input)?;
match input.trim().to_ascii_lowercase().as_str() {
"y" | "ye" | "yes" | "" => {
println!(" Using like-kind treatment through {}.\n", provided_date);
Ok( (true, cutoff_date_arg.clone().unwrap()) )
},
"n" | "no" => { println!(" Proceeding without like-kind treatment.\n");
Ok( (false, "1-1-1".to_string()) )
},
"c" | "change" => {
println!("Please enter your desired like-kind exchange treatment cutoff date.");
println!(" You must use the format %y-%m-%d (e.g., 2017-12-31, 17-12-31, and 9-6-1 are all acceptable).\n");
let mut input = String::new();
let stdin = io::stdin();
stdin.lock().read_line(&mut input)?;
utils::trim_newline(&mut input);
let newly_chosen_date = NaiveDate::parse_from_str(&input, "%y-%m-%d")
.unwrap_or(NaiveDate::parse_from_str(&input, "%Y-%m-%d")
.expect("Date entered has an incorrect format. Program must abort."));
// TODO: figure out how to make this fail gracefully and let the user input the date again
println!(" Using like-kind treatment through {}.\n", newly_chosen_date);
Ok( (true, input) )
},
_ => {
println!("Please respond with 'y', 'n', or 'c' (or 'yes' or 'no' or 'change').");
_elect_like_kind_arg(&cutoff_date_arg, provided_date)
}
}
}
return (election, date)
} else {
println!("\nContinue without like-kind exchange treatment? [Y/n] ");
let (election, date) = match _no_elect_like_kind_arg() {
Ok(x) => {x},
Err(err) => { println!("Fatal error in like_kind selection. Perhaps incorrect date format."); process::exit(1) }
};
fn _no_elect_like_kind_arg() -> Result<(bool, String), Box<Error>> {
let mut input = String::new();
let stdin = io::stdin();
stdin.lock().read_line(&mut input)?;
match input.trim().to_ascii_lowercase().as_str() {
"y" | "ye" | "yes" | "" => { println!(" Proceeding without like-kind treatment.\n");
Ok( (false, "1-1-1".to_string()) )
},
"n" | "no" => {
println!("Please enter your desired like-kind exchange treatment cutoff date.");
println!(" You must use the format %y-%m-%d (e.g., 2017-12-31, 17-12-31, and 9-6-1 are all acceptable).\n");
let mut input = String::new();
let stdin = io::stdin();
stdin.lock().read_line(&mut input)?;
utils::trim_newline(&mut input);
let newly_chosen_date = NaiveDate::parse_from_str(&input, "%y-%m-%d")
.unwrap_or(NaiveDate::parse_from_str(&input, "%Y-%m-%d")
.expect("Date entered has an incorrect format. Program must abort."));
// TODO: figure out how to make this fail gracefully and let the user input the date again
println!(" Using like-kind treatment through {}.\n", newly_chosen_date);
Ok( (true, input) )
},
_ => { println!("Please respond with 'y' or 'n' (or 'yes' or 'no')."); _no_elect_like_kind_arg() }
}
}
return (election, date)
}
}
}
#[derive(Clone)]
pub struct LikeKindSettings {
pub like_kind_cutoff_date: NaiveDate,
pub like_kind_basis_date_preserved: bool,
}

34
src/utils.rs Normal file
View File

@ -0,0 +1,34 @@
// Copyright (c) 2017-2019, scoobybejesus
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt
use decimal::d128;
pub fn round_d128_generalized(to_round: &d128, places_past_decimal: d128) -> d128 {
let rounded: d128 = ((to_round * d128!(10).scaleb(places_past_decimal)).quantize(d128!(1e1))) / d128!(10).scaleb(places_past_decimal);
rounded//.reduce()
}
pub fn round_d128_1e2(to_round: &d128) -> d128 {
let rounded: d128 = ((to_round * d128!(10).scaleb(d128!(2))).quantize(d128!(1e1))) / d128!(10).scaleb(d128!(2));
rounded//.reduce()
}
pub fn round_d128_1e8(to_round: &d128) -> d128 {
let rounded: d128 = ((to_round * d128!(10).scaleb(d128!(8))).quantize(d128!(1e1))) / d128!(10).scaleb(d128!(8));
rounded//.reduce()
// Note: quantize() rounds the number to the right of decimal and keeps it, discarding the rest to the right (it appears). See test.
// In other words, it's off by one. If you raise 0.123456789 by 10e8, quantize to 1e1 (which is 10), it'll get 12345678.9, round off to 12345679, and end up .12345679
// If you quantize the same number to 1e2 (which is 100), it starts back toward the left, so it'll get 12345678.9, round off to 12345680
// If you quantize the same number to 1e3 (which is 1000), it starts back toward the left, so it'll get 12345678.9, round off to 12345700
// As you can see, the quantize is off by one. Quantizing to 10 rounds off the nearest one. Quantizing to 100 rounds off to nearest 10, etc.
}
pub fn trim_newline(s: &mut String) {
if s.ends_with('\n') {
s.pop();
if s.ends_with('\r') {
s.pop();
}
}
}