From 5406b4ce640c9c08def16c8777445d814a9d7d0d Mon Sep 17 00:00:00 2001 From: Markus Zoppelt Date: Sun, 18 Dec 2022 18:58:42 +0100 Subject: [PATCH] feat: support for gpg encrypted data files Closes #2. --- .gitignore | 1 + Cargo.lock | 130 ++++++++++++++++++++++++------------------------ Cargo.toml | 2 +- README.md | 10 ++++ src/main.rs | 47 ++++++++++++++--- src/position.rs | 15 +++--- 6 files changed, 122 insertions(+), 83 deletions(-) diff --git a/.gitignore b/.gitignore index ed36989..f6dc1c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target database/ positions.json +*.gpg diff --git a/Cargo.lock b/Cargo.lock index 015aca7..2e00f25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,15 +76,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] name = "cc" -version = "1.0.76" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" [[package]] name = "cfg-if" @@ -167,9 +167,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.12" +version = "0.9.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bf8df95e795db1a4aca2957ad884a2df35413b24bbeb3114422f3cc21498e8" +checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" dependencies = [ "autocfg", "cfg-if", @@ -180,18 +180,18 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.13" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422f23e724af1240ec469ea1e834d87a4b59ce2efe2c6a96256b0c47e2fd86aa" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" dependencies = [ "cfg-if", ] [[package]] name = "cxx" -version = "1.0.82" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453" +checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd" dependencies = [ "cc", "cxxbridge-flags", @@ -201,9 +201,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.82" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0" +checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0" dependencies = [ "cc", "codespan-reporting", @@ -216,15 +216,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.82" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71" +checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59" [[package]] name = "cxxbridge-macro" -version = "1.0.82" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470" +checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6" dependencies = [ "proc-macro2", "quote", @@ -487,9 +487,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59df7c4e19c950e6e0e868dcc0a300b09a9b88e9ec55bd879ca819087a77355d" +checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" dependencies = [ "http", "hyper", @@ -563,9 +563,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.5.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" +checksum = "11b0d96e660696543b251e58030cf9787df56da39dab19ad60eae7353040917e" [[package]] name = "is-terminal" @@ -581,9 +581,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "js-sys" @@ -602,24 +602,24 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.137" +version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" [[package]] name = "link-cplusplus" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" dependencies = [ "cc", ] [[package]] name = "linux-raw-sys" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "lock_api" @@ -710,9 +710,9 @@ checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "os_str_bytes" -version = "6.4.0" +version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5bf27447411e9ee3ff51186bf7a08e16c341efdde93f4d823e8844429bed7e" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" [[package]] name = "parking_lot" @@ -722,7 +722,7 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core 0.8.5", + "parking_lot_core 0.8.6", ] [[package]] @@ -732,14 +732,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.4", + "parking_lot_core 0.9.5", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ "cfg-if", "instant", @@ -751,9 +751,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" +checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" dependencies = [ "cfg-if", "libc", @@ -791,7 +791,7 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "portfolio_rs" -version = "0.1.4" +version = "0.1.5" dependencies = [ "chrono", "clap", @@ -807,18 +807,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -923,9 +923,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "scopeguard" @@ -935,9 +935,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scratch" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" [[package]] name = "sct" @@ -951,18 +951,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91" +checksum = "97fed41fc1a24994d044e6db6935e69511a1153b52c15eb42493b26fa87feba0" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a3df25b0713732468deadad63ab9da1f1fd75a48a15024b50363f128db627e" +checksum = "255abe9a125a985c05190d687b320c12f9b1f0b99445e608c21ba0782c719ad8" dependencies = [ "proc-macro2", "quote", @@ -971,9 +971,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.89" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" dependencies = [ "itoa", "ryu", @@ -1056,9 +1056,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.104" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -1076,18 +1076,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -1096,9 +1096,9 @@ dependencies = [ [[package]] name = "time" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", @@ -1142,9 +1142,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.8.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", @@ -1216,9 +1216,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-normalization" @@ -1362,9 +1362,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.5" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ "webpki", ] diff --git a/Cargo.toml b/Cargo.toml index 28d8963..ac60634 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "portfolio_rs" description = "A command line tool for managing financial investment portfolios written in Rust." readme = "README.md" -version = "0.1.4" +version = "0.1.5" edition = "2021" license = "MIT" diff --git a/README.md b/README.md index e79202c..a514b24 100644 --- a/README.md +++ b/README.md @@ -39,3 +39,13 @@ If you need help, try `portfolio_rs help [SUBCOMMAND]` for usage information. ## Demo ![demo](https://raw.githubusercontent.com/MarkusZoppelt/portfolio_rs/main/img/demo.gif) + + +## Bonus: GPG Encryption +This tool supports (gpg) encrypted json files. +Decrypted values are never written to disk. + + # you will need a valid gpg key in ~/.gnupg/ + portfolio_rs [COMMAND] data.json.gpg + +Pro Tip: Use a plugin like [vim-gnupg](https://github.com/jamessan/vim-gnupg) for editing your data file. diff --git a/src/main.rs b/src/main.rs index 9d1542e..80b0cef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,7 @@ +use std::fs::read_to_string; + use crate::portfolio::Portfolio; -use crate::position::from_file; +use crate::position::from_string; use crate::position::handle_position; use chrono::prelude::*; use clap::{arg, Command}; @@ -34,9 +36,9 @@ fn cli() -> Command { ) } -// returns a porfolio with the latest quotes from a json file -async fn create_live_portfolio(filename: &str) -> Portfolio { - let positions = from_file(filename); +// returns a porfolio with the latest quotes from json data +async fn create_live_portfolio(positions_str: String) -> Portfolio { + let positions = from_string(&positions_str); let mut portfolio = Portfolio::new(); // move tasks into the async closure passed to tokio::spawn() let tasks: Vec<_> = positions @@ -64,6 +66,19 @@ fn store_balance_in_db(portfolio: &Portfolio) { db.flush().unwrap(); } +fn open_encrpted_file(filename: String) -> String { + if filename.ends_with(".gpg") { + let output = std::process::Command::new("gpg") + .arg("-d") + .arg(filename) + .output() + .expect("failed to execute process"); + String::from_utf8(output.stdout).unwrap() + } else { + read_to_string(filename).unwrap() + } +} + #[tokio::main] async fn main() { let matches = cli().get_matches(); @@ -72,7 +87,12 @@ async fn main() { let filename = matches .get_one::("FILE") .expect("Cannot get filename"); - let portfolio = create_live_portfolio(filename).await; + let positions_str: String = if filename.ends_with(".gpg") { + open_encrpted_file(filename.to_string()) + } else { + std::fs::read_to_string(filename).unwrap() + }; + let portfolio = create_live_portfolio(positions_str).await; portfolio.print(true); store_balance_in_db(&portfolio); } @@ -81,7 +101,12 @@ async fn main() { let filename = matches .get_one::("FILE") .expect("Cannot get filename"); - let portfolio = create_live_portfolio(filename).await; + let positions_str: String = if filename.ends_with(".gpg") { + open_encrpted_file(filename.to_string()) + } else { + std::fs::read_to_string(filename).unwrap() + }; + let portfolio = create_live_portfolio(positions_str).await; portfolio.draw_pie_chart(); portfolio.print_allocation(); } @@ -90,7 +115,12 @@ async fn main() { let filename = matches .get_one::("FILE") .expect("Cannot get filename"); - let portfolio = create_live_portfolio(filename).await; + let positions_str: String = if filename.ends_with(".gpg") { + open_encrpted_file(filename.to_string()) + } else { + std::fs::read_to_string(filename).unwrap() + }; + let portfolio = create_live_portfolio(positions_str).await; let db = sled::open("database").unwrap(); // Yahoo first of the year is YYYY-01-03 @@ -153,7 +183,8 @@ mod tests { #[tokio::test] async fn test_create_live_portfolio() { - let portfolio = create_live_portfolio("example_data.json").await; + let positions_str = std::fs::read_to_string("example_data.json").unwrap(); + let portfolio = create_live_portfolio(positions_str).await; let x: Result = Ok(portfolio); assert!(x.is_ok()); } diff --git a/src/position.rs b/src/position.rs index 741a467..a3a11af 100644 --- a/src/position.rs +++ b/src/position.rs @@ -1,7 +1,5 @@ use chrono::prelude::*; use serde::Deserialize; -use std::fs::File; -use std::io::Read; use yahoo_finance_api as yahoo; #[derive(Debug, Deserialize)] @@ -46,12 +44,8 @@ impl PortfolioPosition { } } -pub fn from_file(filename: &str) -> Vec { - let mut file = File::open(filename).expect("file not found"); - let mut data = String::new(); - file.read_to_string(&mut data) - .expect("something went wrong reading the file"); - serde_json::from_str::>(&data).expect("JSON was not well-formatted") +pub fn from_string(data: &str) -> Vec { + serde_json::from_str::>(data).expect("JSON was not well-formatted") } // Get the latest price for a ticker @@ -113,6 +107,8 @@ pub async fn handle_position(position: &mut PortfolioPosition) -> PortfolioPosit #[cfg(test)] mod tests { + use std::fs; + use super::*; #[tokio::test] @@ -160,7 +156,8 @@ mod tests { #[tokio::test] async fn test_from_file() { - let positions = from_file("example_data.json"); + let positions_str = fs::read_to_string("example_data.json").unwrap(); + let positions = from_string(&positions_str); assert_eq!(positions.len(), 6); } }