From 05f01c58870dcedddd8b2415ccc7845840f38b60 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Sat, 8 Feb 2020 15:51:52 -0800 Subject: [PATCH] Initial Commit --- .gitignore | 6 ++ Cargo.lock | 174 +++++++++++++++++++++++++++++++++++ Cargo.toml | 9 ++ src/account.rs | 23 +++++ src/main.rs | 221 +++++++++++++++++++++++++++++++++++++++++++++ src/transaction.rs | 87 ++++++++++++++++++ 6 files changed, 520 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/account.rs create mode 100644 src/main.rs create mode 100644 src/transaction.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a654b2e --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/target +**/*.rs.bk +bisq +ledger.dat +.idea/ +openpriv diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d64e171 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,174 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bstr" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.0 (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.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "chrono" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "coblynau" +version = "0.1.0" +dependencies = [ + "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "csv 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "csv" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bstr 0.2.11 (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.5 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (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.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itoa" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.66" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memchr" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-integer" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (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 = "regex-automata" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ryu" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "1.0.104" +source = "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.66 (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.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi" +version = "0.3.8" +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 autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +"checksum bstr 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "502ae1441a0a5adb8fbd38a5955a6416b9493e92b465de5e4a9bde6a539c2c48" +"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +"checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" +"checksum csv 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "00affe7f6ab566df61b4be3ce8cf16bc2576bca0963ceb0955e45d514bf9a279" +"checksum csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9b5cadb6b25c77aeff80ba701712494213f4a8418fcda2ee11b6560c3ad0bf4c" +"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" +"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" +"checksum memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223" +"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +"checksum regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "92b73c2a1770c255c240eaa4ee600df1704a38dc3feaa6e949e7fcd4f8dc09f9" +"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" +"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" +"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" +"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +"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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..848f027 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "coblynau" +version = "0.1.0" +authors = ["Sarah Jamie Lewis "] +edition = "2018" + +[dependencies] +csv = "1.1" +chrono = "0.4.10" diff --git a/src/account.rs b/src/account.rs new file mode 100644 index 0000000..b057ce8 --- /dev/null +++ b/src/account.rs @@ -0,0 +1,23 @@ +use crate::transaction::Transaction; + +pub struct Account { + pub transactions: Vec +} + +impl Account { + pub fn new() -> Account { + Account { + transactions: vec!{} + } + } + + pub fn add_transaction(&mut self, transaction: Transaction) { + self.transactions.push(transaction); + } + + pub fn print(&self) { + for transaction in self.transactions.iter() { + transaction.print(); + } + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..6ec8520 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,221 @@ +use std::error::Error; +use std::fs::File; +use chrono::{NaiveDateTime}; +use crate::transaction::{Transaction, Input, Output}; + +pub mod account; +pub mod transaction; +use std::env; +use std::borrow::Borrow; + +fn parse_bisq_transactions(account:&String) -> Result, Box>{ + // Build the CSV reader and iterate over each record. + let file = File::open(String::from(account) + "/bisq/transactions.csv")?; + let mut rdr = csv::Reader::from_reader(file); + let mut trade_transactions: Vec= vec!(); + for result in rdr.records() { + // The iterator yields Result, so we check the + // error here.. + let record = result?; + let details = &record[1]; + let date = &record[0]; + let date_time = NaiveDateTime::parse_from_str(date,"%b %e, %Y %I:%M:%S %p")?; + let amount:f64 = record[4].parse().unwrap(); + + if details.contains("Maker and tx fee") { + let mut transaction = Transaction::new(date_time, format!("Maker Fee")); + transaction.add_input(Input{ + source: String::from("Assets:Trading"), + amount: amount.abs(), + value: String::from("BTC") + }); + transaction.add_output(Output{ + destination: String::from("Expenses:MakerFee"), + amount: amount.abs(), + value: String::from("BTC") + }); + transaction.add_comment(record[2].to_string()); + trade_transactions.push(transaction); + } + + if details.contains("Taker and tx fee") { + let mut transaction = Transaction::new(date_time, format!("Taker Fee")); + transaction.add_input(Input{ + source: String::from("Assets:Trading"), + amount: amount.abs(), + value: String::from("BTC") + }); + transaction.add_output(Output{ + destination: String::from("Expenses:TakerFee"), + amount: amount.abs(), + value: String::from("BTC") + }); + transaction.add_comment(record[2].to_string()); + trade_transactions.push(transaction); + } + + if details.contains("Multisig deposit") { + let mut transaction = Transaction::new(date_time, format!("Multisig Deposit")); + transaction.add_input(Input{ + source: String::from("Assets:Trading"), + amount: amount.abs(), + value: String::from("BTC") + }); + transaction.add_output(Output{ + destination: String::from("Expenses:TradingDeposit"), + amount: amount.abs(), + value: String::from("BTC") + }); + transaction.add_comment(record[2].to_string()); + trade_transactions.push(transaction); + } + + if details.contains("Multisig payout") { + let mut transaction = Transaction::new(date_time, format!("Multisig Payout")); + transaction.add_input(Input{ + source: String::from("Expenses:TradingDeposit"), + amount: amount.abs(), + value: String::from("BTC") + }); + transaction.add_output(Output{ + destination: String::from("Assets:Trading"), + amount: amount.abs(), + value: String::from("BTC") + }); + transaction.add_comment(record[2].to_string()); + trade_transactions.push(transaction); + } + + if details.contains("Withdrawn from wallet") { + let mut transaction = Transaction::new(date_time, format!("Widthdrawal from Bisq Trading Wallet")); + transaction.add_input(Input{ + source: String::from("Assets:Trading"), + amount: amount.abs(), + value: String::from("BTC") + }); + transaction.add_output(Output{ + destination: String::from("Assets:General"), + amount: ((amount.abs() * 100.0).round() / 100.0), + value: String::from("BTC") + }); + transaction.add_output(Output{ + destination: String::from("Expenses:TxFee"), + amount: ((amount.abs() - ((amount.abs() * 100.0).round() / 100.0)) * 100000000.0).round() / 100000000.0, + value: String::from("BTC") + }); + transaction.add_comment(record[2].to_string()); + trade_transactions.push(transaction); + } + + + if details.contains("Received funds") { + let mut transaction = Transaction::new(date_time, format!("Deposit into Bisq Trading Wallet")); + transaction.add_input(Input{ + source: String::from("Assets:General"), + amount: amount.abs(), + value: String::from("BTC") + }); + transaction.add_output(Output{ + destination: String::from("Assets:Trading"), + amount: amount.abs(), + value: String::from("BTC") + }); + transaction.add_comment(record[2].to_string()); + trade_transactions.push(transaction); + } + + } + return Ok(trade_transactions); +} + + +fn parse_bisq_trades(account:&String) -> Result, Box>{ + // Build the CSV reader and iterate over each record. + let file = File::open(String::from(account) + "/bisq/tradeHistory.csv")?; + let mut rdr = csv::Reader::from_reader(file); + let mut trade_transactions: Vec= vec!(); + for result in rdr.records() { + // The iterator yields Result, so we check the + // error here.. + let record = result?; + let id = &record[0]; + let date = &record[1]; + let date_time = NaiveDateTime::parse_from_str(date,"%b %e, %Y %I:%M:%S %p")?; + let bought = &record[4]; + let sold = &record[5]; + + let mut transaction = Transaction::new(date_time, format!("Bisq Trade {}", id)); + transaction.add_input(Input{ + source: String::from("Assets:Trading"), + amount: sold.replace(" ZEC","").parse().unwrap(), + value: String::from("ZEC") + }); + transaction.add_output(Output{ + destination: String::from("Expenses:TradingDeposit"), + amount: bought.parse().unwrap(), + value: String::from("BTC") + }); + trade_transactions.push(transaction); + + } + return Ok(trade_transactions); +} + + +fn parse_donations(account:&String) -> Result, Box>{ + // Build the CSV reader and iterate over each record. + let file = File::open(String::from(account) + "/donations.csv")?; + let mut rdr = csv::Reader::from_reader(file); + let mut trade_transactions: Vec= vec!(); + for result in rdr.records() { + // The iterator yields Result, so we check the + // error here.. + let record = result?; + let date = &record[0]; + let date_time = NaiveDateTime::parse_from_str(date,"%b %e, %Y %I:%M:%S %p")?; + let details = &record[1]; + let amount = &record[2]; + let denomination = &record[3]; + let account = &record[4]; + + + let mut transaction = Transaction::new(date_time, format!("{}", details)); + transaction.add_input(Input{ + source: String::from("Income:Donations"), + amount: amount.parse().unwrap(), + value: String::from(denomination), + }); + transaction.add_output(Output{ + destination: String::from(account), + amount: amount.parse().unwrap(), + value: String::from(denomination), + }); + trade_transactions.push(transaction); + + } + return Ok(trade_transactions); +} + +fn main() { + + let args: Vec = env::args().collect(); + let mut ledger = account::Account::new(); + + let donations = parse_donations(args[1].borrow()).unwrap(); + for transaction in donations.iter() { + ledger.add_transaction(transaction.clone()); + } + + let trade_transactions = parse_bisq_trades(args[1].borrow()).unwrap(); + for transaction in trade_transactions.iter() { + ledger.add_transaction(transaction.clone()); + } + + let bisq_transactions = parse_bisq_transactions(args[1].borrow()).unwrap(); + for transaction in bisq_transactions.iter() { + ledger.add_transaction(transaction.clone()); + } + + ledger.transactions.sort_by_key(|transaction|transaction.date); + ledger.print(); +} diff --git a/src/transaction.rs b/src/transaction.rs new file mode 100644 index 0000000..bc3a5cf --- /dev/null +++ b/src/transaction.rs @@ -0,0 +1,87 @@ +use chrono::{NaiveDateTime, Datelike}; + +#[derive(Clone)] +pub struct Input { + pub source: String, + pub amount:f64, + pub value: String +} + +#[derive(Clone)] +pub struct Output { + pub destination: String, + pub amount:f64, + pub value: String +} + +impl Output { + pub fn clone(&self) -> Self { + Output { + destination: self.destination.clone(), + amount: self.amount, + value: self.value.clone() + } + } +} + +pub struct Transaction { + pub date: NaiveDateTime, + description: String, + inputs: Vec, + outputs:Vec, + comments:Vec +} + +impl Transaction { + pub fn new(date:NaiveDateTime, description:String) -> Transaction { + Transaction { + date, + description, + inputs:vec!(), + outputs:vec!(), + comments:vec!() + } + } + + pub fn add_input(&mut self, input: Input) { + self.inputs.push(input); + } + + pub fn add_output(&mut self, output: Output) { + self.outputs.push(output); + } + + pub fn add_comment(&mut self, comment: String) { + self.comments.push(comment); + } + + pub fn clone(&self) -> Self { + Transaction { + date: self.date, + description: self.description.clone(), + inputs: self.inputs.to_vec(), + outputs: self.outputs.to_vec(), + comments: self.comments.to_vec(), + } + } + + + pub fn print(&self) { + for comment in self.comments.iter() { + println!(";{}", comment); + } + println!("{}/{}/{} {} * {}", self.date.date().year(),self.date.date().month(),self.date.date().day(), self.date.time(), self.description); + for input in self.inputs.iter() { + println!("\t {} \t\t -{} {}", input.source, input.amount, input.value); + } + for output in self.outputs.iter() { + println!("\t {} \t\t {} {}", output.destination, output.amount, output.value); + } + println!(""); + println!(""); + } + + +} + +