Skip to content

Commit

Permalink
Merge pull request #13 from sebinsua/feat/cool-thingz
Browse files Browse the repository at this point in the history
Restyling the commands
  • Loading branch information
sebinsua committed Dec 20, 2015
2 parents 75b8445 + b52a280 commit e8ddcec
Show file tree
Hide file tree
Showing 3 changed files with 237 additions and 110 deletions.
40 changes: 28 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ The purpose of this command line tool is to provide a human-interface for your b

It uses [Teller](http://teller.io) behind-the-scenes to interact with your UK bank, so you will need to have an account there.

**:point_up_2: Heads up!** The interface is in flux while I try to make it human-like without becoming too redundant.
**:point_up_2: Heads up!** (1) This is my first [Rust](https://www.rust-lang.org/) project, (2) the interface is in flux while I try to make it human-like without ending up redundant, and (3) I'm [putting off writing tests](https://github.com/sebinsua/teller-cli/issues/1) until I am happy with the interface.

## Usage

Expand All @@ -15,7 +15,7 @@ It uses [Teller](http://teller.io) behind-the-scenes to interact with your UK ba

*e.g.*

![Instructions](http://i.imgur.com/lIytXnY.png)
![Instructions](http://i.imgur.com/XJ5Vsfa.png)

## Why?

Expand All @@ -30,7 +30,7 @@ It uses [Teller](http://teller.io) behind-the-scenes to interact with your UK ba
```sh
#!/bin/sh

CURRENT_BALANCE=`teller show balance current --only-numbers`
CURRENT_BALANCE=`teller show balance current --hide-currency`
MIN_BALANCE=1000.00

if (( $(bc <<< "$CURRENT_BALANCE < $MIN_BALANCE") ))
Expand All @@ -45,25 +45,41 @@ fi

```
> teller list transactions current | grep "NANNA'S"
27 2015-11-12 NANNA'S -2.70 GBP 4836 11NOV15 C , NANNA'S , LONDON GB
60 2015-10-28 NANNA'S -2.40 GBP 4836 27OCT15 C , NANNA'S , LONDON GB
68 2015-10-26 NANNA'S -5.40 GBP 4836 25OCT15 C , NANNA'S , LONDON GB
101 2015-10-09 NANNA'S -2.70 GBP 4836 08OCT15 C , NANNA'S , LONDON GB
203 2015-07-17 NANNA'S -4.60 GBP 2520 16JUL15 C , NANNA'S , LONDON GB
206 2015-07-16 NANNA'S -9.90 GBP 2520 15JUL15 C , NANNA'S , LONDON GB
208 2015-07-16 NANNA'S -9.30 GBP 2520 14JUL15 C , NANNA'S , LONDON GB
209 2015-07-16 NANNA'S -0.10 GBP 2520 15JUL15 C , NANNA'S , LONDON GB
27 2015-11-12 NANNA'S -2.70
60 2015-10-28 NANNA'S -2.40
68 2015-10-26 NANNA'S -5.40
101 2015-10-09 NANNA'S -2.70
203 2015-07-17 NANNA'S -4.60
206 2015-07-16 NANNA'S -9.90
208 2015-07-16 NANNA'S -9.30
209 2015-07-16 NANNA'S -0.10
```

Hopefully Teller will add support for querying transactions soon.

#### Am I saving money with a chart :chart_with_upwards_trend: with [`spark`](https://github.com/holman/spark)

```
> teller list balances business --interval=monthly --only-numbers | spark
> teller list balances business --interval=monthly --timeframe=year --output=spark | spark
▁▁▁▂▃▂▃▄▄▅▆█
```

## Installation

### From release

```
TODO: write a command to download cli from Github releases :)
```

### From source

First `git clone` and then:

```
> cargo build --release && cp ./target/release/teller /usr/local/bin && chmod +x /usr/local/bin/teller
```

## FAQ

#### Compiling gives `openssl/hmac.h` not found error
Expand Down
141 changes: 91 additions & 50 deletions src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use config::Config;
use hyper::{Client, Url};
use hyper::header::{Authorization, Bearer};
use rustc_serialize::json;
use chrono::{Date, DateTime, NaiveDate, UTC, Datelike};
use chrono::{Date, DateTime, UTC};
use chrono::duration::Duration;
use itertools::Itertools;

Expand All @@ -17,13 +17,13 @@ use self::error::TellerClientError;
#[derive(Debug)]
pub enum Interval {
Monthly,
None
}

#[derive(Debug)]
pub enum Timeframe {
Year,
None
SixMonths,
ThreeMonths,
}

pub type ApiServiceResult<T> = Result<T, TellerClientError>;
Expand Down Expand Up @@ -63,6 +63,21 @@ pub struct Transaction {
pub amount: String,
}

#[derive(Debug)]
pub struct Balances {
pub historical_amounts: Vec<(String, String)>,
pub currency: String,
}

impl Balances {
pub fn new(historical_amounts: Vec<(String, String)>, currency: String) -> Balances {
Balances {
historical_amounts: historical_amounts,
currency: currency,
}
}
}

fn get_auth_header(auth_token: &String) -> Authorization<Bearer> {
Authorization(
Bearer {
Expand Down Expand Up @@ -156,70 +171,96 @@ fn parse_utc_date_from_transaction(t: &Transaction) -> Date<UTC> {
}

pub fn get_transactions(config: &Config, account_id: &str, timeframe: &Timeframe) -> ApiServiceResult<Vec<Transaction>> {
let mut all_transactions = vec![];
let page_through_transactions = |from| -> ApiServiceResult<Vec<Transaction>> {
let mut all_transactions = vec![];

let mut fetching = true;
let mut page = 1;
let count = 250;
while fetching {
let mut transactions = try!(raw_transactions(config, &account_id, count, page));
match transactions.last() {
None => {
// If there are no transactions left, do not fetch forever...
fetching = false
},
Some(past_transaction) => {
let past_transaction_date = parse_utc_date_from_transaction(&past_transaction);
if past_transaction_date < from {
fetching = false;
}
},
};

all_transactions.append(&mut transactions);
page = page + 1;
}

all_transactions = all_transactions.into_iter().filter(|t| {
let transaction_date = parse_utc_date_from_transaction(&t);
transaction_date > from
}).collect();

all_transactions.reverse();
Ok(all_transactions)
};

match *timeframe {
Timeframe::None => Ok(all_transactions),
Timeframe::Year => {
Timeframe::ThreeMonths => {
let to = UTC::today();
let from = to - Duration::weeks(52); // close enough... 😅

let mut fetching = true;
let mut page = 1;
let count = 250;
while fetching {
let mut transactions = try!(raw_transactions(config, &account_id, count, page));
match transactions.last() {
None => (),
Some(past_transaction) => {
let past_transaction_date = parse_utc_date_from_transaction(&past_transaction);
if past_transaction_date < from {
fetching = false;
}
},
};

all_transactions.append(&mut transactions);
page = page + 1;
}
let from = to - Duration::days(91); // close enough... 😅

page_through_transactions(from)
},
Timeframe::SixMonths => {
let to = UTC::today();
let from = to - Duration::days(183);

let all_transactions = all_transactions.into_iter().filter(|t| {
let transaction_date = parse_utc_date_from_transaction(&t);
transaction_date > from
}).collect();
page_through_transactions(from)
},
Timeframe::Year => {
let to = UTC::today();
let from = to - Duration::days(365);

Ok(all_transactions)
page_through_transactions(from)
},
}
}

pub fn get_balances(config: &Config, account_id: &str, interval: &Interval, timeframe: &Timeframe) -> ApiServiceResult<Vec<Money>> {
let mut balances: Vec<Money> = vec![];
pub fn get_balances(config: &Config, account_id: &str, interval: &Interval, timeframe: &Timeframe) -> ApiServiceResult<Balances> {
let transactions: Vec<Transaction> = get_transactions(&config, &account_id, &timeframe).unwrap_or(vec![]);

let month_year_total_transactions: Vec<(String, f32)> = transactions.into_iter().group_by(|t| {
let mut month_year_total_transactions: Vec<(String, i64)> = transactions.into_iter().group_by(|t| {
let transaction_date = parse_utc_date_from_transaction(&t);
let group_name = format!("{}-{}", transaction_date.month(), transaction_date.year());
group_name
match *interval {
Interval::Monthly => {
let group_name = transaction_date.format("%m-%Y").to_string();
group_name
}
}
}).map(|myt| {
let group_name = myt.0;
let amount = myt.1.into_iter().map(|t| {
f32::from_str(&t.amount).unwrap()
}).fold(0f32, |sum, v| sum + v);
(myt.0, amount)
let v = (f64::from_str(&t.amount).unwrap() * 100f64).round() as i64;
v
}).fold(0i64, |sum, v| sum + v);
(group_name, amount)
}).collect();
month_year_total_transactions.reverse();

let current_balance = try!(match get_account(&config, &account_id) {
Ok(ref account) => {
Ok((account.balance.to_owned(), account.currency.to_owned()))
},
Err(e) => Err(e),
});
balances.push(current_balance.to_owned());
let mut last_balance = f32::from_str(&current_balance.0).unwrap();
let account = try!(get_account(&config, &account_id));
let current_balance = (f64::from_str(&account.balance).unwrap() * 100f64).round() as i64;
let currency = account.currency;

let mut historical_amounts: Vec<(String, String)> = vec![];
historical_amounts.push(("current".to_string(), format!("{:.2}", current_balance as f64 / 100f64)));

let mut last_balance = current_balance;
for mytt in month_year_total_transactions {
last_balance = last_balance - mytt.1;
balances.push((last_balance.to_string(), current_balance.1.to_owned()));
historical_amounts.push((mytt.0.to_string(), format!("{:.2}", last_balance as f64 / 100f64)));
}
historical_amounts.reverse();

balances.reverse();
Ok(balances)
Ok(Balances::new(historical_amounts, currency))
}
Loading

0 comments on commit e8ddcec

Please sign in to comment.