exchange-rates-generator 0.0.1
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.
- data/History.txt +3 -0
- data/Manifest.txt +18 -0
- data/README.rdoc +61 -0
- data/Rakefile +26 -0
- data/bin/generate_exchange_rates +10 -0
- data/lib/exchange-rates-generator.rb +38 -0
- data/lib/exchange-rates-generator/errors.rb +21 -0
- data/lib/exchange-rates-generator/formatters/base.rb +42 -0
- data/lib/exchange-rates-generator/formatters/javascript.rb +94 -0
- data/lib/exchange-rates-generator/formatters/ruby.rb +136 -0
- data/lib/exchange-rates-generator/sources/base.rb +60 -0
- data/lib/exchange-rates-generator/sources/ecb.rb +71 -0
- data/lib/generate_exchange_rates/cli.rb +93 -0
- data/spec/exchange-rates-generator_spec.rb +11 -0
- data/spec/generate_exchange_rates_cli_spec.rb +15 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- data/tasks/rspec.rake +21 -0
- metadata +123 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
README.rdoc
|
4
|
+
Rakefile
|
5
|
+
bin/generate_exchange_rates
|
6
|
+
lib/exchange-rates-generator.rb
|
7
|
+
lib/exchange-rates-generator/errors.rb
|
8
|
+
lib/exchange-rates-generator/formatters/base.rb
|
9
|
+
lib/exchange-rates-generator/formatters/ruby.rb
|
10
|
+
lib/exchange-rates-generator/formatters/javascript.rb
|
11
|
+
lib/exchange-rates-generator/sources/base.rb
|
12
|
+
lib/exchange-rates-generator/sources/ecb.rb
|
13
|
+
lib/generate_exchange_rates/cli.rb
|
14
|
+
spec/exchange-rates-generator_spec.rb
|
15
|
+
spec/generate_exchange_rates_cli_spec.rb
|
16
|
+
spec/spec.opts
|
17
|
+
spec/spec_helper.rb
|
18
|
+
tasks/rspec.rake
|
data/README.rdoc
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
= exchange-rates-generator
|
2
|
+
|
3
|
+
* http://github.com/#{github_username}/#{project_name}
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Generates a classes (or class like things) that can translate currency values in a specific currency to a number of other currencies.
|
8
|
+
|
9
|
+
== FEATURES/PROBLEMS:
|
10
|
+
|
11
|
+
* Generates classes (or class like things) for Ruby and Javascript
|
12
|
+
* Can support addition languages through custom formatters
|
13
|
+
* Supports a currency xml feed from ECB by default, but can support custom exchange rate sources through custom Source classes.
|
14
|
+
|
15
|
+
== SYNOPSIS:
|
16
|
+
|
17
|
+
Generate a Ruby class for convert NZD amounts to other currencies, using the default ECB source.
|
18
|
+
|
19
|
+
<tt>./generate_exchange_rates -c nzd -f ruby</tt>
|
20
|
+
|
21
|
+
|
22
|
+
Generate a Ruby library for convert NZD amounts to other currencies, using the default ECB source.
|
23
|
+
|
24
|
+
<tt>./generate_exchange_rates -c usd -f javascript</tt>
|
25
|
+
|
26
|
+
== REQUIREMENTS:
|
27
|
+
|
28
|
+
* Ruby :-)
|
29
|
+
* Extlib Gem
|
30
|
+
* Money Gem
|
31
|
+
* Patron Gem
|
32
|
+
* Nokogiri Gem
|
33
|
+
|
34
|
+
== INSTALL:
|
35
|
+
|
36
|
+
* sudo gem install exchange-rates-generator
|
37
|
+
|
38
|
+
== LICENSE:
|
39
|
+
|
40
|
+
(The MIT License)
|
41
|
+
|
42
|
+
Copyright (c) 2010 Rolly
|
43
|
+
|
44
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
45
|
+
a copy of this software and associated documentation files (the
|
46
|
+
'Software'), to deal in the Software without restriction, including
|
47
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
48
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
49
|
+
permit persons to whom the Software is furnished to do so, subject to
|
50
|
+
the following conditions:
|
51
|
+
|
52
|
+
The above copyright notice and this permission notice shall be
|
53
|
+
included in all copies or substantial portions of the Software.
|
54
|
+
|
55
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
56
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
57
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
58
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
59
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
60
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
61
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'hoe', '>= 2.1.0'
|
3
|
+
require 'hoe'
|
4
|
+
require 'fileutils'
|
5
|
+
require './lib/exchange-rates-generator'
|
6
|
+
|
7
|
+
Hoe.plugin :newgem
|
8
|
+
# Hoe.plugin :website
|
9
|
+
# Hoe.plugin :cucumberfeatures
|
10
|
+
|
11
|
+
# Generate all the Rake tasks
|
12
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
13
|
+
$hoe = Hoe.spec 'exchange-rates-generator' do
|
14
|
+
self.developer 'Rolly', 'rolly@luma.co.nz'
|
15
|
+
# self.post_install_message = 'PostInstall.txt' # TODO remove if post-install message not required
|
16
|
+
self.rubyforge_name = self.name # TODO this is default value
|
17
|
+
# self.extra_deps = [['activesupport','>= 2.0.2']]
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'newgem/tasks'
|
22
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
23
|
+
|
24
|
+
# TODO - want other tests/tasks run by default? Add them to the list
|
25
|
+
# remove_task :default
|
26
|
+
# task :default => [:spec, :features]
|
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Created by Rolly on 2010-3-16.
|
4
|
+
# Copyright (c) 2010. All rights reserved.
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require File.expand_path(File.dirname(__FILE__) + "/../lib/exchange-rates-generator")
|
8
|
+
require "generate_exchange_rates/cli"
|
9
|
+
|
10
|
+
GenerateExchangeRates::CLI.execute(STDOUT, ARGV)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'patron'
|
5
|
+
require 'nokogiri'
|
6
|
+
require 'extlib'
|
7
|
+
require 'logger'
|
8
|
+
|
9
|
+
module ExchangeRatesGenerator
|
10
|
+
VERSION = '0.0.1'
|
11
|
+
|
12
|
+
def self.log_to=(l)
|
13
|
+
@log_to = l
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.log_to
|
17
|
+
@log_to ||= STDOUT
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.logger=(logger)
|
21
|
+
@logger = logger
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.logger
|
25
|
+
@logger ||= begin
|
26
|
+
log = Logger.new(log_to)
|
27
|
+
log.level = Logger::INFO
|
28
|
+
log
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
require 'exchange-rates-generator/formatters/base'
|
34
|
+
require 'exchange-rates-generator/formatters/ruby'
|
35
|
+
require 'exchange-rates-generator/formatters/javascript'
|
36
|
+
|
37
|
+
require 'exchange-rates-generator/sources/base'
|
38
|
+
require 'exchange-rates-generator/sources/ecb'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ExchangeRatesGenerator
|
2
|
+
module Errors
|
3
|
+
class ExchangeRatesError < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
class NotFoundError < ExchangeRatesError
|
7
|
+
end
|
8
|
+
|
9
|
+
class UnknownError < ExchangeRatesError
|
10
|
+
end
|
11
|
+
|
12
|
+
class MalformedSourceClass < ExchangeRatesError
|
13
|
+
end
|
14
|
+
|
15
|
+
class CurrencyNotAvailable < ExchangeRatesError
|
16
|
+
end
|
17
|
+
|
18
|
+
class MalformedFormatterClass < ExchangeRatesError
|
19
|
+
end
|
20
|
+
end # module Errors
|
21
|
+
end # module ExchangeRatesGenerator
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module ExchangeRatesGenerator
|
2
|
+
module Formatters
|
3
|
+
def self.get(formatter_name)
|
4
|
+
Object.full_const_get([self.to_s, Extlib::Inflection.classify(formatter_name.to_s)].join("::"))
|
5
|
+
end
|
6
|
+
|
7
|
+
class Base
|
8
|
+
attr_reader :currency, :rates
|
9
|
+
|
10
|
+
def initialize(currency, rates)
|
11
|
+
@currency = currency.to_s.upcase.to_sym
|
12
|
+
@rates = rates
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
header + body + footer
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
# Returns all Formatters
|
21
|
+
#
|
22
|
+
# @return [Array]
|
23
|
+
# Array containing all Formatters
|
24
|
+
#
|
25
|
+
def formatters
|
26
|
+
@formatters ||= []
|
27
|
+
end
|
28
|
+
|
29
|
+
# Records all new Formatters
|
30
|
+
def inherited(formatter)
|
31
|
+
self.formatters << formatter
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def formatter_classname
|
38
|
+
@formatter_classname ||= Extlib::Inflection.classify(@currency.to_s)
|
39
|
+
end
|
40
|
+
end # class Base
|
41
|
+
end # module Formatters
|
42
|
+
end # module ExchangeRatesGenerator
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module ExchangeRatesGenerator
|
2
|
+
module Formatters
|
3
|
+
class Javascript < Base
|
4
|
+
def initialize(currency, rates)
|
5
|
+
super(currency, rates)
|
6
|
+
end
|
7
|
+
|
8
|
+
def default_extension
|
9
|
+
self.class.default_extension
|
10
|
+
end
|
11
|
+
|
12
|
+
def description
|
13
|
+
self.class.description
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def default_extension
|
18
|
+
:js
|
19
|
+
end
|
20
|
+
|
21
|
+
def description
|
22
|
+
"Javascript Formatter"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def formatter_classname
|
29
|
+
@formatter_classname ||= Extlib::Inflection.underscore(@currency.to_s)
|
30
|
+
end
|
31
|
+
|
32
|
+
def header
|
33
|
+
<<-EOS
|
34
|
+
//
|
35
|
+
// THIS FILE IS AUTOMATICALLY GENERATED USING THE exchange-rates-generator RUBY GEM DO NOT EDIT IT.
|
36
|
+
//
|
37
|
+
|
38
|
+
if ( typeof(exchange_rates) === 'undefined' ) {
|
39
|
+
var exchange_rates = {};
|
40
|
+
}
|
41
|
+
|
42
|
+
// This Library provides exchange rate conversion from #{@currency.to_s} to various other currencies. The list of
|
43
|
+
// currencies that this Library can convert to can be retrieve using the supported_currencies method.
|
44
|
+
//
|
45
|
+
// Generated using the exchange-rates-generator Ruby Gem.
|
46
|
+
exchange_rates.#{formatter_classname} = function() {
|
47
|
+
EOS
|
48
|
+
end
|
49
|
+
|
50
|
+
def body
|
51
|
+
<<-EOS
|
52
|
+
return {
|
53
|
+
base_currency : function() {
|
54
|
+
return '#{@currency.to_s}';
|
55
|
+
},
|
56
|
+
|
57
|
+
supported_currencies : function() {
|
58
|
+
return [
|
59
|
+
#{rates.keys.collect {|c| " \"#{c}\"" }.join(",\n") }
|
60
|
+
];
|
61
|
+
},
|
62
|
+
|
63
|
+
get : function(target_currency) {
|
64
|
+
return this.rates()[target_currency.toUpperCase()];
|
65
|
+
},
|
66
|
+
|
67
|
+
convert : function(amount, target_currency) {
|
68
|
+
var rate = this.get(target_currency);
|
69
|
+
if ( !rate ) {
|
70
|
+
throw "This exchange rate converter can not convert to " + target_currency;
|
71
|
+
}
|
72
|
+
|
73
|
+
return rate * parseFloat(amount, 10);
|
74
|
+
},
|
75
|
+
|
76
|
+
rates : function() {
|
77
|
+
return {
|
78
|
+
"#{@currency.to_s}" : 1.0,
|
79
|
+
#{rates.to_a.collect { |rate| " \"#{rate[0].to_s}\" : #{rate[1].to_s}" }.join(",\n")}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
};
|
84
|
+
EOS
|
85
|
+
end
|
86
|
+
|
87
|
+
def footer
|
88
|
+
<<-EOS
|
89
|
+
}();
|
90
|
+
EOS
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end # module Formatters
|
94
|
+
end # module ExchangeRatesGenerator
|
@@ -0,0 +1,136 @@
|
|
1
|
+
module ExchangeRatesGenerator
|
2
|
+
module Formatters
|
3
|
+
class Ruby < Base
|
4
|
+
def initialize(currency, rates)
|
5
|
+
super(currency, rates)
|
6
|
+
end
|
7
|
+
|
8
|
+
def default_extension
|
9
|
+
self.class.default_extension
|
10
|
+
end
|
11
|
+
|
12
|
+
def description
|
13
|
+
self.class.description
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def default_extension
|
18
|
+
:rb
|
19
|
+
end
|
20
|
+
|
21
|
+
def description
|
22
|
+
"Ruby Formatter"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def header
|
29
|
+
<<-EOS
|
30
|
+
#
|
31
|
+
# THIS FILE IS AUTOMATICALLY GENERATED USING THE exchange-rates-generator RUBY GEM DO NOT EDIT IT.
|
32
|
+
#
|
33
|
+
require 'money'
|
34
|
+
|
35
|
+
module ExchangeRates
|
36
|
+
class CurrencyNotAvailable < StandardError
|
37
|
+
end
|
38
|
+
|
39
|
+
# This Class provides exchange rate conversion from #{@currency.to_s} to various other currencies. The list of
|
40
|
+
# currencies that this Class can convert to can be retrieve using the supported_currencies method.
|
41
|
+
#
|
42
|
+
# This Class also supports integration with the Money Gem, for usage see the use_as_default! method.
|
43
|
+
#
|
44
|
+
# Generated using the exchange-rates-generator Ruby Gem.
|
45
|
+
class #{@currency.to_s}
|
46
|
+
class << self
|
47
|
+
def base_currency
|
48
|
+
:#{@currency.to_s}
|
49
|
+
end
|
50
|
+
|
51
|
+
def supported_currencies
|
52
|
+
rates.keys
|
53
|
+
end
|
54
|
+
|
55
|
+
# Wires this currency up to use with the Money Gem. It sets it as the default
|
56
|
+
# currency.
|
57
|
+
#
|
58
|
+
# @example
|
59
|
+
# #{@currency.to_s}.use_as_default!
|
60
|
+
# Money.us_dollar(100).exchange_to("CAD") => Money.ca_dollar(124)
|
61
|
+
#
|
62
|
+
def use_as_default!
|
63
|
+
Money.default_currency = base_currency
|
64
|
+
Money.bank = self
|
65
|
+
Money
|
66
|
+
end
|
67
|
+
|
68
|
+
EOS
|
69
|
+
end
|
70
|
+
|
71
|
+
def body
|
72
|
+
<<-EOS
|
73
|
+
# Retrieves an exchange rate.
|
74
|
+
#
|
75
|
+
# @param [String, #to_s] The target currency that we want the exchange rate for.
|
76
|
+
# @return [Float] The exchange rate
|
77
|
+
def get(target_currency)
|
78
|
+
rates[target_currency.to_s.upcase.to_sym]
|
79
|
+
end
|
80
|
+
|
81
|
+
# Convert +amount+ from base_currency to +currency+.
|
82
|
+
#
|
83
|
+
# @param [Float, #to_f] Amount to convert.
|
84
|
+
# @param [String, #to_s] The currency we want to convert to.
|
85
|
+
# @return [Float] The +amount+ converted to +currency+.
|
86
|
+
def convert(amount, currency)
|
87
|
+
rate = get(currency) or raise CurrencyNotAvailable, "Can't find required exchange rate"
|
88
|
+
rate * amount.to_f
|
89
|
+
end
|
90
|
+
|
91
|
+
# Retrieves an exchange rate, this is here to support the Money Gem.
|
92
|
+
#
|
93
|
+
# @param [String, #to_s] The source currency that we want the exchange rate for. For this class from should always match base_currency.
|
94
|
+
# @param [String, #to_s] The target currency that we want the exchange rate for.
|
95
|
+
# @return [Float] The exchange rate
|
96
|
+
def get_rate(from, to)
|
97
|
+
unless from.to_s.upcase.to_sym == :#{@currency.to_s}
|
98
|
+
raise CurrencyNotAvailable, "This exchange rate converter can only convert from #{@currency.to_s}"
|
99
|
+
end
|
100
|
+
|
101
|
+
get(to)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Convert +money+ to +currency+.
|
105
|
+
#
|
106
|
+
# @param [Money] The amount to convert.
|
107
|
+
# @param [String] The currency to convert to.
|
108
|
+
# @return [Money] The +amount+ converted to +currency+.
|
109
|
+
def exchange(money, currency)
|
110
|
+
rate = get_rate(money.currency, currency.to_s) or raise CurrencyNotAvailable, "Can't find required exchange rate"
|
111
|
+
|
112
|
+
Money.new((money.cents * rate).floor, currency.to_s, money.precision)
|
113
|
+
end
|
114
|
+
|
115
|
+
# The exchange rates relative to base_currency.
|
116
|
+
#
|
117
|
+
# @return [Hash] The exchange rates relative to base_currency.
|
118
|
+
def rates
|
119
|
+
{
|
120
|
+
:#{@currency.to_s} => 1.0,
|
121
|
+
#{rates.to_a.collect { |rate| "\s\s:#{rate[0].to_s} => #{rate[1].to_s}" }.join(",\n ")}
|
122
|
+
}
|
123
|
+
end
|
124
|
+
EOS
|
125
|
+
end
|
126
|
+
|
127
|
+
def footer
|
128
|
+
<<-EOS
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end # module ExchangeRates
|
132
|
+
EOS
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end # module Formatters
|
136
|
+
end # module ExchangeRatesGenerator
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module ExchangeRatesGenerator
|
2
|
+
module Sources
|
3
|
+
def self.get(source_name)
|
4
|
+
Object.full_const_get([self.to_s, Extlib::Inflection.classify(source_name.to_s)].join("::"))
|
5
|
+
end
|
6
|
+
|
7
|
+
class Base
|
8
|
+
class << self
|
9
|
+
# Returns all Sources
|
10
|
+
#
|
11
|
+
# @return [Array]
|
12
|
+
# Array containing all Sources
|
13
|
+
#
|
14
|
+
def sources
|
15
|
+
@sources ||= []
|
16
|
+
end
|
17
|
+
|
18
|
+
# Records all new Sources
|
19
|
+
def inherited(source)
|
20
|
+
self.sources << source
|
21
|
+
end
|
22
|
+
|
23
|
+
# Retrieve a Hash of currency codes and exchange rates, they rates will be relative to +currency_code+
|
24
|
+
#
|
25
|
+
# @param [String, #to_s] The currency code that the exchange rates will be relative to.
|
26
|
+
# @return [Hash] The exchange rates, as a Hash of currency codes and exchange rates.
|
27
|
+
def rates_for(currency_code)
|
28
|
+
# Retrieve the actual rates from the external source
|
29
|
+
rates = all_rates
|
30
|
+
|
31
|
+
adjusted_currency = currency_code.to_s.upcase.to_sym
|
32
|
+
unless rates.include?(adjusted_currency)
|
33
|
+
raise Errors::CurrencyNotAvailable, "#{adjusted_currency.to_s} was not available in this Source (#{self.to_s}), please use a different Source"
|
34
|
+
end
|
35
|
+
|
36
|
+
adjusted_rates = {}
|
37
|
+
|
38
|
+
# Add the currency we are converting to...
|
39
|
+
adjusted_rates[adjusted_currency] = 1.0
|
40
|
+
|
41
|
+
# Work out the exchange from our desired currency to our base currency. So if our base was EUR and we wanted USD this would
|
42
|
+
# be how many Euros are in one US dollar.
|
43
|
+
adjustment_factor = 1.0 / rates[adjusted_currency]
|
44
|
+
adjusted_rates[base_currency] = adjustment_factor
|
45
|
+
|
46
|
+
# Now remove it, since we've already done it.
|
47
|
+
rates.delete(base_currency)
|
48
|
+
|
49
|
+
# Now convert the rest
|
50
|
+
rates.each do |currency, rate|
|
51
|
+
adjusted_rates[currency] = rate * adjustment_factor
|
52
|
+
end
|
53
|
+
|
54
|
+
adjusted_rates
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end # module Sources
|
60
|
+
end # module ExchangeRatesGenerator
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module ExchangeRatesGenerator
|
2
|
+
module Sources
|
3
|
+
# ECB: European Central Bank home page
|
4
|
+
class Ecb < Base
|
5
|
+
class << self
|
6
|
+
# Retrieves the currency code (as an uppercase symbol) that represents this Source's Base currency.
|
7
|
+
#
|
8
|
+
# @return [Symbol] The currency code of this Source's base currency
|
9
|
+
def base_currency
|
10
|
+
:EUR
|
11
|
+
end
|
12
|
+
|
13
|
+
# Retrieves a human readable description of the Source.
|
14
|
+
#
|
15
|
+
# @return [String] The description.
|
16
|
+
def description
|
17
|
+
"European Central Bank currency feed"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Retrieves all the currency rates from the external source, this is a Hash of currency codes and exchange
|
21
|
+
# rates. The currency codes will be upcased, Symbols (e.g. :USD, :NZD). The exchange rates will be with
|
22
|
+
# respect to the base_currency.
|
23
|
+
#
|
24
|
+
# @return [Hash] The rates as a Hash of currency codes => exchange rates.
|
25
|
+
def all_rates
|
26
|
+
session = Patron::Session.new
|
27
|
+
session.timeout = 30000 # 10 secs
|
28
|
+
session.connect_timeout = 2000 # 2 secs
|
29
|
+
|
30
|
+
ExchangeRatesGenerator.logger.info "Retrieving exchange rates from #{source}..."
|
31
|
+
|
32
|
+
http_response = session.get(source)
|
33
|
+
|
34
|
+
if ['404', '405', '406', '410'].include?(http_response.status)
|
35
|
+
# Not Found, auth failure, etc. Some form of it wasn't there.
|
36
|
+
raise Errors::NotFoundError, "The exchange rate source file seems to be unavailable"
|
37
|
+
elsif !['1', '2'].include?(http_response.status.to_s[0..0])
|
38
|
+
# Other non-specific failures.
|
39
|
+
raise Errors::UnexpectedError, "Error while making a request to #{source}\nResponse: #{http_response.inspect}"
|
40
|
+
end
|
41
|
+
|
42
|
+
convert_to_exchange_rate_hash(http_response.body)
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
# Parse the XML response from the service
|
48
|
+
def convert_to_exchange_rate_hash(response_body)
|
49
|
+
doc = Nokogiri::HTML.parse(response_body)
|
50
|
+
|
51
|
+
hashed_rates = {}
|
52
|
+
|
53
|
+
# Normalise and Hash the result
|
54
|
+
doc.css('envelope>cube>cube>cube').each do |rate|
|
55
|
+
currency = rate[:currency].upcase.to_sym
|
56
|
+
rate = rate[:rate].to_f
|
57
|
+
hashed_rates[currency] = rate
|
58
|
+
end
|
59
|
+
|
60
|
+
hashed_rates
|
61
|
+
end
|
62
|
+
|
63
|
+
def source
|
64
|
+
'http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml'
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end # module Sources
|
71
|
+
end # module ExchangeRatesGenerator
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module GenerateExchangeRates
|
4
|
+
class CLI
|
5
|
+
def self.execute(stdout, arguments=[])
|
6
|
+
|
7
|
+
# NOTE: the option -p/--path= is given as an example, and should be replaced in your application.
|
8
|
+
|
9
|
+
options = {
|
10
|
+
:path => nil,
|
11
|
+
:source => 'ecb',
|
12
|
+
:format => 'ruby'
|
13
|
+
}
|
14
|
+
mandatory_options = %w( currency )
|
15
|
+
|
16
|
+
parser = OptionParser.new do |opts|
|
17
|
+
opts.banner = <<-BANNER.gsub(/^ /,'')
|
18
|
+
This application is wonderful because it can generate helpful classes that can do currency exchange from a single target currency to numerous others.
|
19
|
+
|
20
|
+
Usage: #{File.basename($0)} [options]
|
21
|
+
|
22
|
+
Options are:
|
23
|
+
BANNER
|
24
|
+
opts.separator ""
|
25
|
+
opts.on("-v", "--version", String,
|
26
|
+
"Displays the current version number of this Gem") { |arg| stdout.puts "#{File.basename($0)} #{ExchangeRatesGenerator::VERSION}"; exit }
|
27
|
+
opts.on("-c", "--currency CURRENCY", String,
|
28
|
+
"This is the base currency you wish to convert from.") { |arg| options[:currency] = arg }
|
29
|
+
opts.on("-p", "--path PATH", String,
|
30
|
+
"The is the default output path for the exchange rate converter.",
|
31
|
+
"Default: ~/exchange_rates.[format extension]") { |arg| options[:path] = arg }
|
32
|
+
opts.on("-s", "--source SOURCE", String,
|
33
|
+
"This is the name of the desired Source to retrieve the currency data from.",
|
34
|
+
"To get a list of all available Sources using the -l or --list flags.",
|
35
|
+
"Default: ecb") { |arg| options[:source] = arg.upcase }
|
36
|
+
opts.on("-f", "--format Format", String,
|
37
|
+
"This is the name of the desired output format of the exchange rate converter, the choice of format depends on the programming language you intend to use the converter from.",
|
38
|
+
"To get a list of all available formats using the -l or --list flags.",
|
39
|
+
"Default: ruby") { |arg| options[:format] = arg.upcase }
|
40
|
+
|
41
|
+
opts.on("-l", "--list", "List all available formats and sources") do
|
42
|
+
formatters = ExchangeRatesGenerator::Formatters::Base.formatters.collect {|f| "\s\s#{Extlib::Inflection.demodulize(f.to_s)}: #{f.description} (*.#{f.default_extension.to_s})" }.join("\n")
|
43
|
+
sources = ExchangeRatesGenerator::Sources::Base.sources.collect {|s| "\s\s#{Extlib::Inflection.demodulize(s.to_s)}: #{s.description}" }.join("\n")
|
44
|
+
|
45
|
+
stdout.puts <<-EOS
|
46
|
+
Available Formats:
|
47
|
+
#{formatters}
|
48
|
+
|
49
|
+
Available Sources:
|
50
|
+
#{sources}
|
51
|
+
EOS
|
52
|
+
|
53
|
+
exit
|
54
|
+
end
|
55
|
+
opts.on("-h", "--help",
|
56
|
+
"Show this help message.") { stdout.puts opts; exit }
|
57
|
+
opts.parse!(arguments)
|
58
|
+
|
59
|
+
if mandatory_options && mandatory_options.find { |option| options[option.to_sym].nil? }
|
60
|
+
stdout.puts opts; exit
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
formatter = ::ExchangeRatesGenerator::Formatters.get(options[:format].to_sym)
|
65
|
+
unless formatter
|
66
|
+
stdout.puts "Sorry, I couldn't find a formatter for the format '#{options[:format]}', use the -l or --list flag to see the complete list of available formats."
|
67
|
+
exit
|
68
|
+
end
|
69
|
+
|
70
|
+
source = ::ExchangeRatesGenerator::Sources.get(options[:source].to_sym)
|
71
|
+
unless source
|
72
|
+
stdout.puts "Sorry, I couldn't find the source '#{options[:source]}', use the -l or --list flag to see the complete list of available sources."
|
73
|
+
exit
|
74
|
+
end
|
75
|
+
|
76
|
+
path = options[:path]
|
77
|
+
path ||= "exchange_rates.#{formatter.default_extension.to_s}"
|
78
|
+
|
79
|
+
begin
|
80
|
+
File.open(path, 'w') do |file|
|
81
|
+
file.write formatter.new(options[:currency], source.rates_for(options[:currency]))
|
82
|
+
end
|
83
|
+
|
84
|
+
# TODO: Catch some exceptions?
|
85
|
+
rescue ::ExchangeRatesGenerator::Errors::ExchangeRatesError => e
|
86
|
+
stdout.puts e.message
|
87
|
+
exit
|
88
|
+
end
|
89
|
+
|
90
|
+
stdout.puts "Exchange rate converted successfully. Find it at: #{path}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'generate_exchange_rates/cli'
|
3
|
+
|
4
|
+
describe GenerateExchangeRates::CLI, "execute" do
|
5
|
+
before(:each) do
|
6
|
+
@stdout_io = StringIO.new
|
7
|
+
GenerateExchangeRates::CLI.execute(@stdout_io, [])
|
8
|
+
@stdout_io.rewind
|
9
|
+
@stdout = @stdout_io.read
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should print default output" do
|
13
|
+
@stdout.should =~ /To update this executable/
|
14
|
+
end
|
15
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/spec/spec_helper.rb
ADDED
data/tasks/rspec.rake
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
begin
|
2
|
+
require 'spec'
|
3
|
+
rescue LoadError
|
4
|
+
require 'rubygems' unless ENV['NO_RUBYGEMS']
|
5
|
+
require 'spec'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'spec/rake/spectask'
|
9
|
+
rescue LoadError
|
10
|
+
puts <<-EOS
|
11
|
+
To use rspec for testing you must install rspec gem:
|
12
|
+
gem install rspec
|
13
|
+
EOS
|
14
|
+
exit(0)
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Run the specs under spec/models"
|
18
|
+
Spec::Rake::SpecTask.new do |t|
|
19
|
+
t.spec_opts = ['--options', "spec/spec.opts"]
|
20
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: exchange-rates-generator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Rolly
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-03-16 00:00:00 +13:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rubyforge
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 2
|
29
|
+
- 0
|
30
|
+
- 4
|
31
|
+
version: 2.0.4
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: gemcutter
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
- 5
|
44
|
+
- 0
|
45
|
+
version: 0.5.0
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: hoe
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
segments:
|
56
|
+
- 2
|
57
|
+
- 5
|
58
|
+
- 0
|
59
|
+
version: 2.5.0
|
60
|
+
type: :development
|
61
|
+
version_requirements: *id003
|
62
|
+
description: Generates a classes (or class like things) that can translate currency values in a specific currency to a number of other currencies.
|
63
|
+
email:
|
64
|
+
- rolly@luma.co.nz
|
65
|
+
executables:
|
66
|
+
- generate_exchange_rates
|
67
|
+
extensions: []
|
68
|
+
|
69
|
+
extra_rdoc_files:
|
70
|
+
- History.txt
|
71
|
+
- Manifest.txt
|
72
|
+
files:
|
73
|
+
- History.txt
|
74
|
+
- Manifest.txt
|
75
|
+
- README.rdoc
|
76
|
+
- Rakefile
|
77
|
+
- bin/generate_exchange_rates
|
78
|
+
- lib/exchange-rates-generator.rb
|
79
|
+
- lib/exchange-rates-generator/errors.rb
|
80
|
+
- lib/exchange-rates-generator/formatters/base.rb
|
81
|
+
- lib/exchange-rates-generator/formatters/ruby.rb
|
82
|
+
- lib/exchange-rates-generator/formatters/javascript.rb
|
83
|
+
- lib/exchange-rates-generator/sources/base.rb
|
84
|
+
- lib/exchange-rates-generator/sources/ecb.rb
|
85
|
+
- lib/generate_exchange_rates/cli.rb
|
86
|
+
- spec/exchange-rates-generator_spec.rb
|
87
|
+
- spec/generate_exchange_rates_cli_spec.rb
|
88
|
+
- spec/spec.opts
|
89
|
+
- spec/spec_helper.rb
|
90
|
+
- tasks/rspec.rake
|
91
|
+
has_rdoc: true
|
92
|
+
homepage: http://github.com/#{github_username}/#{project_name}
|
93
|
+
licenses: []
|
94
|
+
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options:
|
97
|
+
- --main
|
98
|
+
- README.rdoc
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
segments:
|
106
|
+
- 0
|
107
|
+
version: "0"
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
segments:
|
113
|
+
- 0
|
114
|
+
version: "0"
|
115
|
+
requirements: []
|
116
|
+
|
117
|
+
rubyforge_project: exchange-rates-generator
|
118
|
+
rubygems_version: 1.3.6
|
119
|
+
signing_key:
|
120
|
+
specification_version: 3
|
121
|
+
summary: Generates a classes (or class like things) that can translate currency values in a specific currency to a number of other currencies.
|
122
|
+
test_files: []
|
123
|
+
|