forex 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8c17d5904df7323d11c5d76a221f0f184c7f1361
4
- data.tar.gz: ce15542872d6db044a109c58aaea2c2d53ff4538
3
+ metadata.gz: 0805a3b1ecebdfe37fb068a85dc133dd9e500007
4
+ data.tar.gz: 735001cf921eeeda36f4c1235adce750aff8000b
5
5
  SHA512:
6
- metadata.gz: 1f041c9670792ec5a29135c96f00bc3ef8791c563d451820ac797b7c5253e8af04b1c45804022c15f9442984da55f4715faaaaaedb1e4bb6d64712e2f333f2af
7
- data.tar.gz: 1fbc4be31fc08d22072c54a1ed0edf4a73b3f956a3091a4972569ec149feab9e966ddf99db31ec131971090e824d209a33a6e4f0aabb5372a640dccff5784748
6
+ metadata.gz: 5cca23e919fe8b9745119b06430c644e7c33d4c5867bd9e844db861984f9a8d405661a0d3a3c2f2d560a19917a88198ac025e5e8b7e23c30fd6ae12c0cd2b23f
7
+ data.tar.gz: 5e7980edf74dd90a048f833ee74c1c3c280c26bcb223f6137fe45333aa1e6d83df87e5e8324e5c8b99daa6485c9f2b61d307282b05983b429fa72e26e35b3570
@@ -1,4 +1,10 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 2.0.0
4
- script: bundle exec cucumber && bundle exec rspec
4
+ cache: bundler
5
+ branches:
6
+ only:
7
+ - master
8
+ script:
9
+ - bundle exec cucumber -f Fivemat
10
+ - bundle exec rspec -f Fivemat
data/README.md CHANGED
@@ -1,7 +1,12 @@
1
1
  # Forex
2
2
 
3
+ [![Code Climate](https://codeclimate.com/github/mcmorgan/forex.png)](https://codeclimate.com/github/mcmorgan/forex)
4
+ [![Build Status](https://api.travis-ci.org/mcmorgan/forex.png)](https://travis-ci.org/mcmorgan/forex)
5
+ [![Dependency Status](https://gemnasium.com/mcmorgan/forex.png)](https://gemnasium.com/mcmorgan/forex)
6
+ [![Gem Version](https://badge.fury.io/rb/forex.png)](http://rubygems.org/gems/forex)
7
+
3
8
  Provides a simple DSL for managing Foreign Exchange rates (forex) for various
4
- traders.
9
+ traders (Inspired by [CurrrencyJA](https://github.com/kenrick/currencyja)).
5
10
 
6
11
  ## Installation
7
12
 
@@ -30,7 +35,7 @@ Forex::Trader.define "NCB" do |t|
30
35
  t.name = "National Commercial Bank"
31
36
  t.endpoint = "http://www.jncb.com/rates/foreignexchangerates"
32
37
 
33
- t.rates_parser = Proc.new do |doc| # doc is a nokogiri document
38
+ t.rates_parser = ->(doc) do # doc is a nokogiri document
34
39
  # process the doc and return rates hash in the following format
35
40
 
36
41
  # {
@@ -46,18 +51,46 @@ end
46
51
 
47
52
  # Rates may be fetched for a specific trader
48
53
  Forex::Trader.all['BNS'].fetch
54
+ ```
49
55
 
50
- # Or all traders and yielded to the block given
51
- Forex::Trader.fetch_all do |trader|
52
- # Save or do some other processing on the rates ``trader.rates`` hash.
53
- end
56
+ ### TabularRates
57
+
58
+ `TabularRates` allows you to simplify the process of parsing a table of rates.
59
+ Simply find the table and pass it along with options of what values are in which
60
+ columns (zero indexed).
61
+
62
+ For example:
63
+
64
+ ```ruby
65
+ #...
66
+
67
+ translations = {
68
+ 'US$' => 'USD',
69
+ 'TT$' => 'TTD',
70
+ }
71
+
72
+ t.rates_parser = ->(doc) do # doc is a nokogiri document
73
+
74
+ options = {
75
+ currency_code: 1,
76
+ sell_cash: 2,
77
+ buy_cash: 4,
78
+ buy_draft: 3,
79
+ }
80
+
81
+ table = doc.css(".rates table").first
82
+
83
+ # translations is optional
84
+ Forex::TabularRates.new(table, options).parse_rates(translations)
85
+ end
86
+
87
+ #...
54
88
  ```
55
89
 
56
- ## Code Status
90
+ ## TODO
57
91
 
58
- * [![Code Climate](https://codeclimate.com/github/mcmorgan/forex.png)](https://codeclimate.com/github/mcmorgan/forex)
59
- * [![Build Status](https://api.travis-ci.org/mcmorgan/forex.png)](https://travis-ci.org/mcmorgan/forex)
60
- * [![Dependency Status](https://gemnasium.com/mcmorgan/forex.png)](https://gemnasium.com/mcmorgan/forex)
92
+ * Make it possible to have the same trader in multiple countries
93
+ * Add other traders in Jamaica
61
94
 
62
95
  ## Contributing
63
96
 
@@ -7,7 +7,7 @@ When(/^the column options exist for the tabular rates parser:$/) do |options_tab
7
7
  end
8
8
 
9
9
  Then(/^parsing the table should return the following rates:$/) do |table|
10
- ensure_rates_are_equal_to table
10
+ assert_rates_are_equal_to table
11
11
  end
12
12
 
13
13
  Then(/^parsing the table should raise an exception with the message:$/) do |table|
@@ -15,20 +15,49 @@ Then(/^parsing the table should raise an exception with the message:$/) do |tabl
15
15
  expect { parse_rates }.to raise_error(message)
16
16
  end
17
17
 
18
- def parse_rates
19
- Forex::TabularRates.new(@table, @options).parse_rates
18
+ When(/^the currency translations:$/) do |table|
19
+ @translations = table.raw.each_with_object({}) do |translation, h|
20
+ key, value = translation
21
+ h[key] = value
22
+ end
20
23
  end
21
24
 
22
- def ensure_rates_are_equal_to(table)
23
- formatted_rates = table.hashes.each_with_object({}) do |table_hash, currencies|
24
- table_hash.symbolize_keys!
25
+ module KnowsTabularRates
26
+ include Forex
27
+
28
+ def translations
29
+ @translations || {}
30
+ end
25
31
 
26
- currencies[table_hash.delete(:currency_code)] =
27
- table_hash.each_with_object({}) do |currency_rate, rates|
28
- currency, rate = *currency_rate
29
- rates[currency] = rate.to_f == 0 ? nil : rate.to_f
30
- end
32
+ def parse_rates
33
+ TabularRates.new(@table, @options).parse_rates(translations)
31
34
  end
32
35
 
33
- parse_rates.should == formatted_rates
34
- end
36
+ def assert_rates_are_equal_to(table)
37
+ table = table.dup # because we modify it below
38
+
39
+ convert_rates! table
40
+
41
+ formatted_rates = table.hashes.each_with_object({}) do |table_hash, currencies|
42
+ table_hash.symbolize_keys!
43
+
44
+ currencies[table_hash.delete(:currency_code)] =
45
+ table_hash.each_with_object({}) do |currency_rate, rates|
46
+ currency, rate = *currency_rate
47
+ rates[currency] = rate
48
+ end
49
+ end
50
+
51
+ parse_rates.should == formatted_rates
52
+ end
53
+
54
+ def convert_rates!(table)
55
+ zero_to_nil = ->(value) { value == 0 ? nil : value }
56
+
57
+ TabularRates::COLUMN_LABELS.each do |column_label|
58
+ table.map_column!(column_label) { |value| zero_to_nil.(value.to_f) }
59
+ end
60
+ end
61
+ end
62
+
63
+ World(KnowsTabularRates)
@@ -1 +1,2 @@
1
1
  require 'forex'
2
+ require 'pry'
@@ -9,12 +9,12 @@ Feature: Tabular Rates
9
9
  Given the tabular rates table:
10
10
  """
11
11
  <table>
12
- <tr><td> </td> <td>Cash </td> <td>Cheque</td> <td>Cash & Cheque</td></tr>
13
- <tr><td> </td> <td>BUY </td> <td>BUY </td> <td>SELL </td> </tr>
14
- <tr><td>USD</td> <td>101.30</td> <td>103.30</td> <td>105.00</td> </tr>
15
- <tr><td>GBP</td> <td>166.20</td> <td>169.63</td> <td>173.30</td> </tr>
16
- <tr><td>CAD</td> <td> 94.51</td> <td> 96.92</td> <td> 99.72</td> </tr>
17
- <tr><td>EUR</td> <td> </td> <td>137.42</td> <td>143.16</td> </tr>
12
+ <tr><td> </td> <td>Cash </td> <td>Cheque</td> <td>Cash & Cheque</td></tr>
13
+ <tr><td> </td> <td>BUY </td> <td>BUY </td> <td>SELL </td> </tr>
14
+ <tr><td>US$ </td> <td>101.30</td> <td>103.30</td> <td>105.00</td> </tr>
15
+ <tr><td>GBP </td> <td>166.20</td> <td>169.63</td> <td>173.30</td> </tr>
16
+ <tr><td>CAD </td> <td> 94.51</td> <td> 96.92</td> <td> 99.72</td> </tr>
17
+ <tr><td>EURO</td> <td> </td> <td>137.42</td> <td>143.16</td> </tr>
18
18
  </table>
19
19
  """
20
20
 
@@ -22,6 +22,9 @@ Feature: Tabular Rates
22
22
  When the column options exist for the tabular rates parser:
23
23
  | currency_code | buy_cash | buy_draft | sell_cash | sell_draft |
24
24
  | 0 | 1 | 2 | 3 | 3 |
25
+ And the currency translations:
26
+ | US$ | USD |
27
+ | EURO | EUR |
25
28
  Then parsing the table should return the following rates:
26
29
  | currency_code | buy_cash | buy_draft | sell_cash | sell_draft |
27
30
  | USD | 101.30 | 103.30 | 105.00 | 105.00 |
@@ -26,6 +26,8 @@ Gem::Specification.new do |spec|
26
26
  spec.add_development_dependency "cucumber"
27
27
  spec.add_development_dependency "vcr"
28
28
  spec.add_development_dependency "webmock"
29
+ spec.add_development_dependency "pry"
30
+ spec.add_development_dependency "fivemat"
29
31
 
30
32
  spec.add_development_dependency "bundler", "~> 1.3"
31
33
  spec.add_development_dependency "rake"
@@ -7,4 +7,5 @@ require "forex/tabular_rates"
7
7
  require "forex/trader"
8
8
 
9
9
  # Traders are automatically loaded
10
- Dir[File.dirname(__FILE__) + '/forex/traders/**/*.rb'].each { |t| require t }
10
+ traders_path = File.join(File.dirname(__FILE__), 'forex', 'traders', '**', '*.rb')
11
+ Dir[traders_path].each { |file| require file }
@@ -4,31 +4,34 @@ module Forex
4
4
 
5
5
  attr_accessor :table, :options
6
6
 
7
+ COLUMN_LABELS = [
8
+ :buy_cash,
9
+ :buy_draft,
10
+ :sell_cash,
11
+ :sell_draft
12
+ ]
13
+
7
14
  DEFAULT_OPTIONS = {
8
15
  currency_code: 0,
9
- buy_cash: 1,
10
- buy_draft: 2,
11
- sell_cash: 3,
12
- sell_draft: 4,
13
- }
16
+ }.merge(Hash[(COLUMN_LABELS).zip(1..COLUMN_LABELS.size)])
14
17
 
15
18
  def initialize(table, options = DEFAULT_OPTIONS)
16
19
  @table = table
17
20
  @options = options.symbolize_keys
18
21
  end
19
22
 
20
- def parse_rates
23
+ def parse_rates(translations = {})
21
24
  currency = options.delete(:currency_code) || 0
22
25
 
23
26
  table.css('tr').each_with_object({}) do |tr, currencies|
24
27
  cells = tr.css('td')
25
28
  next if cells.empty?
26
29
 
27
- currency_code = CurrencyCode.new(cells[currency.to_i].content)
30
+ currency_code = CurrencyCode.new(cells[currency.to_i].content, translations)
28
31
 
29
32
  next if currencies.has_key?(currency_code.to_s) || currency_code.invalid?
30
33
 
31
- currencies[currency_code.to_s] = column_labels.each_with_object({}) do |column_label, rates|
34
+ currencies[currency_code.to_s] = COLUMN_LABELS.each_with_object({}) do |column_label, rates|
32
35
  next unless rate_column = options[column_label]
33
36
 
34
37
  rate_node = cells[rate_column.to_i]
@@ -39,35 +42,28 @@ module Forex
39
42
  end
40
43
  end
41
44
 
42
- def column_labels
43
- [:buy_cash, :buy_draft, :sell_cash, :sell_draft]
44
- end
45
-
46
45
  end
47
46
 
48
- class Currency
49
-
50
- def initialize(string)
51
- @string = string
52
- end
47
+ class Currency < Struct.new(:string)
53
48
 
54
49
  # converts the currency to it's storage representation
55
50
  def value
56
- value = @string.strip.to_f
57
- value == 0.0 ? nil : value
51
+ converted == 0.0 ? nil : converted
58
52
  end
59
53
 
60
- end
54
+ private
61
55
 
62
- class CurrencyCode
63
- def initialize(string)
64
- @string = string
56
+ def converted
57
+ @converted ||= string.strip.to_f
65
58
  end
66
59
 
60
+ end
61
+
62
+ class CurrencyCode < Struct.new(:string, :translations)
63
+
67
64
  # TODO validate the currency codes via http://www.xe.com/iso4217.php
68
65
  def valid?
69
- @string = to_s # hack
70
- !@string.blank? && @string.length == 3
66
+ !translated.blank? && translated.length == 3
71
67
  end
72
68
 
73
69
  def invalid?
@@ -75,14 +71,17 @@ module Forex
75
71
  end
76
72
 
77
73
  def to_s
78
- @string.strip.
79
- # Replace currency symbols with letter equivalent
80
- # TODO go crazy and add the rest http://www.xe.com/symbols.php
81
- gsub('$', 'D').
74
+ translated
75
+ end
76
+
77
+ private
82
78
 
83
- # Remove all non word charactes ([^A-Za-z0-9_])
84
- gsub(/\W/,'')
79
+ def translated
80
+ @translated ||= translations[converted] || converted
81
+ end
85
82
 
83
+ def converted
84
+ @converted ||= string.strip
86
85
  end
87
86
  end
88
87
  end
@@ -3,7 +3,7 @@ Forex::Trader.define "BNS" do |t|
3
3
  t.name = "Bank of Nova Scotia"
4
4
  t.endpoint = "http://www4.scotiabank.com/cgi-bin/ratesTool/depdisplay.cgi?pid=56"
5
5
 
6
- t.rates_parser = Proc.new do |doc| # doc is a nokogiri document
6
+ t.rates_parser = ->(doc) do # doc is a nokogiri document
7
7
 
8
8
  options = {
9
9
  currency_code: 0,
@@ -0,0 +1,24 @@
1
+ Forex::Trader.define "COK" do |t|
2
+ t.base_currency = "JMD"
3
+ t.name = "City Of Kingston Credit Union"
4
+ t.endpoint = "http://www.cokcu.com/otherservices/cambio/"
5
+
6
+ t.rates_parser = ->(doc) do # doc is a nokogiri document
7
+
8
+ options = {
9
+ currency_code: 0,
10
+ buy_cash: 3,
11
+ buy_draft: 2,
12
+ sell_cash: 1,
13
+ }
14
+
15
+ table = doc.css(".table-bordered").first
16
+
17
+ translations = {
18
+ 'CI' => 'KYD',
19
+ 'Euro' => 'EUR',
20
+ }
21
+
22
+ Forex::TabularRates.new(table, options).parse_rates(translations)
23
+ end
24
+ end
@@ -3,7 +3,7 @@ Forex::Trader.define "FGB" do |t|
3
3
  t.name = "First Global Bank"
4
4
  t.endpoint = "http://www.firstglobal-bank.com/"
5
5
 
6
- t.rates_parser = Proc.new do |doc| # doc is a nokogiri document
6
+ t.rates_parser = ->(doc) do # doc is a nokogiri document
7
7
  options = {
8
8
  currency_code: 0,
9
9
  buy_cash: 1,
@@ -3,7 +3,7 @@ Forex::Trader.define "FXTRADERS" do |t|
3
3
  t.name = "FX Traders"
4
4
  t.endpoint = "http://www.fxtrader.gkmsonline.com/rates"
5
5
 
6
- t.rates_parser = Proc.new do |doc| # doc is a nokogiri document
6
+ t.rates_parser = ->(doc) do # doc is a nokogiri document
7
7
 
8
8
  content_for = ->(type, n, part = nil) do
9
9
  doc.css(
@@ -11,10 +11,15 @@ Forex::Trader.define "FXTRADERS" do |t|
11
11
  ).first.content
12
12
  end
13
13
 
14
+ translations = {
15
+ 'EURO' => 'EUR',
16
+ }
17
+
14
18
  (1..5).each_with_object({}) do |n, currencies|
15
- country_code = content_for.(:currency, n)
19
+ currency_code = content_for.(:currency, n)
20
+ currency_code = translations[currency_code] || currency_code
16
21
 
17
- currencies[country_code] = {
22
+ currencies[currency_code] = {
18
23
  buy_cash: content_for.(:buying, n).to_f,
19
24
  buy_draft: content_for.(:buying, n, :b).to_f,
20
25
  sell_cash: content_for.(:selling, n).to_f,
@@ -3,7 +3,7 @@ Forex::Trader.define "JMMB" do |t|
3
3
  t.name = "Jamaica Money Market Brokers"
4
4
  t.endpoint = "http://www.jmmb.com/full_rates.php"
5
5
 
6
- t.rates_parser = Proc.new do |doc| # doc is a nokogiri document
6
+ t.rates_parser = ->(doc) do # doc is a nokogiri document
7
7
 
8
8
  options = {
9
9
  currency_code: 0,
@@ -13,12 +13,17 @@ Forex::Trader.define "JMMB" do |t|
13
13
  sell_draft: 4
14
14
  }
15
15
 
16
+ translations = {
17
+ 'US$' => 'USD',
18
+ 'TT$' => 'TTD',
19
+ }
20
+
16
21
  table =
17
22
  doc.search("[text()*='FX Trading Rates']").first. # Section with rates
18
23
  ancestors('table').first. # Root table for section
19
24
  css("table").first # Rates table
20
25
 
21
- Forex::TabularRates.new(table, options).parse_rates
26
+ Forex::TabularRates.new(table, options).parse_rates(translations)
22
27
  end
23
28
  end
24
29
 
@@ -3,10 +3,9 @@ Forex::Trader.define "JNBS" do |t|
3
3
  t.name = "Jamaica National Building Society"
4
4
  t.endpoint = "http://www.jnbs.com/fx-rates-2"
5
5
 
6
- t.rates_parser = Proc.new do |doc| # doc is a nokogiri document
6
+ t.rates_parser = ->(doc) do # doc is a nokogiri document
7
7
 
8
8
  options = {
9
- currency_code: 0,
10
9
  sell_cash: 4,
11
10
  buy_cash: 2,
12
11
  buy_draft: 1,