Skip to content

Commit

Permalink
Read settings from config
Browse files Browse the repository at this point in the history
Instead of hard-coding the sample files, read settings from config.
  • Loading branch information
DavidCain committed Jul 10, 2019
1 parent ffae03a commit 80efa4b
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 19 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/config.toml

/target/
**/*.rs.bk
*.gnucash.*LCK
Expand Down
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ version = "0.1.0"
authors = ["David Cain <[email protected]>"]

[dependencies]
quick-xml = "0.13.1"
rust_decimal = ">= 0.11.2"
rusqlite = "0.16.0"
chrono = "0.4.6"
csv = "1"
num = "0.2.0"
quick-xml = "0.13.1"
rusqlite = "0.16.0"
rust_decimal = ">= 0.11.2"
serde = "1"
serde_derive = "1"
num = "0.2.0"
toml = "0.5"
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ Contribute the following amounts:

### Sample GnuCash accounting records

`example.sqlite3` and `example.gnucash` are included sample files that may be
opened with [GnuCash 3][gnucash]. The transactions, security prices, and
In `example/` are two (identical) sample files in XML and sqlite3 format. Each
may be opened with [GnuCash 3][gnucash]. The transactions, security prices, and
account balances contained within are the basis of rebalancing logic:

![GnuCash user interface for included sample files][img-gnucash-interface]
Expand Down
File renamed without changes.
File renamed without changes.
6 changes: 6 additions & 0 deletions example_config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[user]
birthday = '1972-07-12'

[gnucash]
path_to_book = 'my_books.gnucash'
file_format = 'sqlite3'
102 changes: 102 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
extern crate chrono;
extern crate serde_derive;

use serde_derive::Deserialize;

use self::chrono::NaiveDate;
use std::fs;

#[derive(Deserialize)]
struct User {
birthday: String, // YYYY-MM-DD
}

impl User {
fn birthday(&self) -> NaiveDate {
NaiveDate::parse_from_str(&self.birthday, "%Y-%m-%d").unwrap()
}
}

#[derive(Deserialize)]
pub struct GnuCash {
pub path_to_book: String,
pub file_format: String,
}

#[derive(Deserialize)]
pub struct Config {
user: User,
pub gnucash: GnuCash,
}

impl Config {
/// Return default settings for use with the sample data
pub fn default() -> Config {
Config {
user: User {
birthday: String::from("1985-01-01"),
},
gnucash: GnuCash {
path_to_book: String::from("example.sqlite3"),
file_format: String::from("sqlite3"),
},
}
}

pub fn user_birthday(&self) -> NaiveDate {
self.user.birthday()
}

/// Return a Config from file, or default settings if not present
///
/// See `example_config.toml` for a sample configuration:
///
/// ```toml
/// [user]
/// birthday = '1971-06-14'
///
/// [gnucash]
/// path_to_book = '/path/to/database.gnucash'
/// file_format = 'sqlite3'
/// ```
pub fn from_file(path: &str) -> Config {
let config_toml = match fs::read_to_string(path) {
Ok(file) => file,
Err(_) => {
// Silently fall back to the default
return Config::default();
}
};

toml::from_str(&config_toml).unwrap()
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_parses_birthday() {
let user = User {
birthday: String::from("1962-12-31"),
};
assert_eq!(user.birthday(), NaiveDate::from_ymd(1962, 12, 31));
}

#[test]
fn test_parse_from_toml() {
let conf = Config::from_file("example_config.toml");
assert_eq!(conf.user_birthday(), NaiveDate::from_ymd(1972, 7, 12));
assert_eq!(&conf.gnucash.path_to_book, "my_books.gnucash");
assert_eq!(&conf.gnucash.file_format, "sqlite3");
}

#[test]
fn test_fallback_to_default_settings() {
let conf = Config::from_file("/tmp/definitely_does_not_exist.toml");
assert_eq!(&conf.user.birthday, "1985-01-01");
assert_eq!(&conf.gnucash.path_to_book, "example.sqlite3");
assert_eq!(&conf.gnucash.file_format, "sqlite3");
}
}
12 changes: 12 additions & 0 deletions src/gnucash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use std::io::BufReader;
use std::str::FromStr;

use assets;
use config::Config;
use rebalance::{AssetAllocation, Portfolio};

static GNUCASH_DT_FORMAT: &str = "%Y-%m-%d %H:%M:%S %z";
Expand Down Expand Up @@ -671,6 +672,17 @@ impl Book {
}
}

pub fn from_config(conf: &Config) -> Book {
let path = &conf.gnucash.path_to_book;
if conf.gnucash.file_format == "sqlite3" {
return Book::from_sqlite_file(path);
} else if conf.gnucash.file_format == "xml" {
return Book::from_xml_file(path);
} else {
panic!("Other file formats not supported at this time");
}
}

pub fn from_sqlite_file(filename: &str) -> Book {
let conn = Connection::open(filename).expect("Could not open file");
Book::from_sqlite(&conn)
Expand Down
29 changes: 16 additions & 13 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ use std::io;
mod allocation;
mod assets;
mod compounding;
mod config;
mod gnucash;
mod rebalance;
mod stats;

use config::Config;
use gnucash::Book;

fn get_contribution() -> Decimal {
Expand Down Expand Up @@ -67,12 +69,11 @@ fn summarize_retirement_prospects(birthday: NaiveDate, portfolio_total: Decimal,
}

fn main() {
let sqlite_file = "example.sqlite3";
let book = Book::from_sqlite_file(sqlite_file);
//let book = Book::from_xml_file("example.gnucash");
let conf = Config::from_file("config.toml");
let book = Book::from_config(&conf);

// Identify our ideal allocations (percentages by asset class, summing to 100%)
let birthday = NaiveDate::from_ymd(1985, 1, 1);
let birthday = conf.user_birthday();
let bond_allocation = allocation::bond_allocation(birthday, 120);
let ideal_allocations = allocation::core_four(bond_allocation);

Expand All @@ -84,15 +85,17 @@ fn main() {

summarize_retirement_prospects(birthday, portfolio.current_value(), 0.07);

let sql_stats = stats::Stats::new(sqlite_file);
let after_tax = sql_stats.after_tax_income().unwrap();
let charity = sql_stats.charitable_giving().unwrap();
println!("After-tax income: ${:.0}", after_tax);
println!(
"Charitable giving: ${:.0} ({:.0}% of after-tax income)",
charity,
(charity / after_tax) * Decimal::from(100)
);
if conf.gnucash.file_format == "sqlite3" {
let sql_stats = stats::Stats::new(&conf.gnucash.path_to_book);
let after_tax = sql_stats.after_tax_income().unwrap();
let charity = sql_stats.charitable_giving().unwrap();
println!("After-tax income: ${:.0}", after_tax);
println!(
"Charitable giving: ${:.0} ({:.0}% of after-tax income)",
charity,
(charity / after_tax) * Decimal::from(100)
);
}

let contribution = get_contribution();

Expand Down

0 comments on commit 80efa4b

Please sign in to comment.