forex 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,