acvwilson-currency 0.5.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.
- data/COPYING.txt +339 -0
- data/ChangeLog +8 -0
- data/LICENSE.txt +65 -0
- data/Manifest.txt +58 -0
- data/README.txt +51 -0
- data/Releases.txt +155 -0
- data/TODO.txt +9 -0
- data/currency.gemspec +18 -0
- data/examples/ex1.rb +13 -0
- data/examples/xe1.rb +20 -0
- data/lib/currency.rb +143 -0
- data/lib/currency/active_record.rb +265 -0
- data/lib/currency/config.rb +91 -0
- data/lib/currency/core_extensions.rb +83 -0
- data/lib/currency/currency.rb +175 -0
- data/lib/currency/currency/factory.rb +121 -0
- data/lib/currency/currency_version.rb +6 -0
- data/lib/currency/exception.rb +119 -0
- data/lib/currency/exchange.rb +48 -0
- data/lib/currency/exchange/rate.rb +214 -0
- data/lib/currency/exchange/rate/deriver.rb +157 -0
- data/lib/currency/exchange/rate/source.rb +89 -0
- data/lib/currency/exchange/rate/source/base.rb +166 -0
- data/lib/currency/exchange/rate/source/failover.rb +63 -0
- data/lib/currency/exchange/rate/source/federal_reserve.rb +160 -0
- data/lib/currency/exchange/rate/source/historical.rb +79 -0
- data/lib/currency/exchange/rate/source/historical/rate.rb +184 -0
- data/lib/currency/exchange/rate/source/historical/rate_loader.rb +186 -0
- data/lib/currency/exchange/rate/source/historical/writer.rb +220 -0
- data/lib/currency/exchange/rate/source/new_york_fed.rb +127 -0
- data/lib/currency/exchange/rate/source/provider.rb +120 -0
- data/lib/currency/exchange/rate/source/test.rb +50 -0
- data/lib/currency/exchange/rate/source/the_financials.rb +191 -0
- data/lib/currency/exchange/rate/source/timed_cache.rb +198 -0
- data/lib/currency/exchange/rate/source/xe.rb +165 -0
- data/lib/currency/exchange/time_quantitizer.rb +111 -0
- data/lib/currency/formatter.rb +310 -0
- data/lib/currency/macro.rb +321 -0
- data/lib/currency/money.rb +298 -0
- data/lib/currency/money_helper.rb +13 -0
- data/lib/currency/parser.rb +193 -0
- data/spec/ar_column_spec.rb +76 -0
- data/spec/ar_core_spec.rb +68 -0
- data/spec/ar_simple_spec.rb +23 -0
- data/spec/config_spec.rb +29 -0
- data/spec/federal_reserve_spec.rb +75 -0
- data/spec/formatter_spec.rb +72 -0
- data/spec/historical_writer_spec.rb +187 -0
- data/spec/macro_spec.rb +109 -0
- data/spec/money_spec.rb +355 -0
- data/spec/new_york_fed_spec.rb +73 -0
- data/spec/parser_spec.rb +105 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/time_quantitizer_spec.rb +115 -0
- data/spec/timed_cache_spec.rb +95 -0
- data/spec/xe_spec.rb +50 -0
- metadata +117 -0
@@ -0,0 +1,91 @@
|
|
1
|
+
# Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
|
2
|
+
# See LICENSE.txt for details.
|
3
|
+
|
4
|
+
# The Currency::Config class is responsible for
|
5
|
+
# maintaining global configuration for the Currency package.
|
6
|
+
#
|
7
|
+
# TO DO:
|
8
|
+
#
|
9
|
+
# Migrate all class variable configurations to this object.
|
10
|
+
class Currency::Config
|
11
|
+
@@default = nil
|
12
|
+
|
13
|
+
# Returns the default Currency::Config object.
|
14
|
+
#
|
15
|
+
# If one is not specfied an instance is
|
16
|
+
# created. This is a global, not thread-local.
|
17
|
+
def self.default
|
18
|
+
@@default ||=
|
19
|
+
self.new
|
20
|
+
end
|
21
|
+
|
22
|
+
# Sets the default Currency::Config object.
|
23
|
+
def self.default=(x)
|
24
|
+
@@default = x
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns the current Currency::Config object used during
|
28
|
+
# in the current thread.
|
29
|
+
#
|
30
|
+
# If #current= has not been called and #default= has not been called,
|
31
|
+
# then UndefinedExchange is raised.
|
32
|
+
def self.current
|
33
|
+
Thread.current[:Currency__Config] ||=
|
34
|
+
self.default ||
|
35
|
+
(raise ::Currency::Exception::UndefinedConfig, "Currency::Config.default not defined")
|
36
|
+
end
|
37
|
+
|
38
|
+
# Sets the current Currency::Config object used
|
39
|
+
# in the current thread.
|
40
|
+
def self.current=(x)
|
41
|
+
Thread.current[:Currency__Config] = x
|
42
|
+
end
|
43
|
+
|
44
|
+
# Clones the current configuration and makes it current
|
45
|
+
# during the execution of a block. After block completes,
|
46
|
+
# the previous configuration is restored.
|
47
|
+
#
|
48
|
+
# Currency::Config.configure do | c |
|
49
|
+
# c.float_ref_filter = Proc.new { | x | x.round }
|
50
|
+
# "123.448".money.rep == 12345
|
51
|
+
# end
|
52
|
+
def self.configure(&blk)
|
53
|
+
c_prev = current
|
54
|
+
c_new = self.current = current.clone
|
55
|
+
result = nil
|
56
|
+
begin
|
57
|
+
result = yield c_new
|
58
|
+
ensure
|
59
|
+
self.current = c_prev
|
60
|
+
end
|
61
|
+
result
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
@@identity = Proc.new { |x| x } # :nodoc:
|
66
|
+
|
67
|
+
# Returns the current Float conversion filter.
|
68
|
+
# Can be used to set rounding or truncation policies when converting
|
69
|
+
# Float values to Money values.
|
70
|
+
# Defaults to an identity function.
|
71
|
+
# See Float#Money_rep.
|
72
|
+
def float_ref_filter
|
73
|
+
@float_ref_filter ||=
|
74
|
+
@@identity
|
75
|
+
end
|
76
|
+
|
77
|
+
# Sets the current Float conversion filter.
|
78
|
+
def float_ref_filter=(x)
|
79
|
+
@float_ref_filter = x
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
# Defines the table name for Historical::Rate records.
|
84
|
+
# Defaults to 'currency_historical_rates'.
|
85
|
+
attr_accessor :historical_table_name
|
86
|
+
def historical_table_name
|
87
|
+
@historical_table_name ||= 'currency_historical_rates'
|
88
|
+
end
|
89
|
+
|
90
|
+
end # module
|
91
|
+
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
|
2
|
+
# See LICENSE.txt for details.
|
3
|
+
|
4
|
+
|
5
|
+
|
6
|
+
class Object
|
7
|
+
# Exact conversion to Money representation value.
|
8
|
+
def money(*opts)
|
9
|
+
Currency::Money(self, *opts)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
class Integer
|
16
|
+
# Exact conversion to Money representation value.
|
17
|
+
def Money_rep(currency, time = nil)
|
18
|
+
Integer(self * currency.scale)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# module Asa
|
23
|
+
# module Rounding
|
24
|
+
# def self.included(base) #:nodoc:
|
25
|
+
# puts "included by #{base.inspect}"
|
26
|
+
# base.class_eval do
|
27
|
+
# alias_method :round_without_precision, :round
|
28
|
+
# alias_method :round, :round_with_precision
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# # Rounds the float with the specified precision.
|
33
|
+
# #
|
34
|
+
# # x = 1.337
|
35
|
+
# # x.round # => 1
|
36
|
+
# # x.round(1) # => 1.3
|
37
|
+
# # x.round(2) # => 1.34
|
38
|
+
# def round_with_precision(precision = nil)
|
39
|
+
# precision.nil? ? round_without_precision : (self * (10 ** precision)).round / (10 ** precision).to_f
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# class Float
|
46
|
+
# include Asa::Rounding
|
47
|
+
# # Inexact conversion to Money representation value.
|
48
|
+
# def Money_rep(currency, time = nil)
|
49
|
+
# Integer(Currency::Config.current.float_ref_filter.call(self * currency.scale))
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
|
53
|
+
class Float
|
54
|
+
# Inexact conversion to Money representation value.
|
55
|
+
def Money_rep(currency, time = nil)
|
56
|
+
Integer(Currency::Config.current.float_ref_filter.call(self * currency.scale))
|
57
|
+
end
|
58
|
+
|
59
|
+
# def round_with_awesome_precision(precision = nil)
|
60
|
+
# # puts "self: #{self.inspect}"
|
61
|
+
# # puts "precision: #{precision.inspect}"
|
62
|
+
# # puts "round_without_precision: #{round_without_precision.inspect}"
|
63
|
+
# # puts "self * (10 ** precision): #{(self * (10 ** precision)).inspect}"
|
64
|
+
# # puts "(self * (10 ** precision)).round_without_precision: #{((self * (10 ** precision)).round_without_precision).inspect}"
|
65
|
+
# # self.to_s.to_f.round_without_precision
|
66
|
+
# precision.nil? ? round_without_awesome_precision : (self * (10 ** precision)).round_without_awesome_precision / (10 ** precision).to_f
|
67
|
+
# end
|
68
|
+
# alias_method :round_without_awesome_precision, :round
|
69
|
+
# alias_method :round, :round_with_awesome_precision
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
class String
|
76
|
+
# Exact conversion to Money representation value.
|
77
|
+
def Money_rep(currency, time = nil)
|
78
|
+
x = currency.parse(self, :currency => currency, :time => time)
|
79
|
+
x = x.rep if x.respond_to?(:rep)
|
80
|
+
x
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
@@ -0,0 +1,175 @@
|
|
1
|
+
# Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
|
2
|
+
# See LICENSE.txt for details.
|
3
|
+
|
4
|
+
|
5
|
+
# Represents a currency.
|
6
|
+
#
|
7
|
+
# Currency objects are created on-demand by Currency::Currency::Factory.
|
8
|
+
#
|
9
|
+
# See Currency.get method.
|
10
|
+
#
|
11
|
+
class Currency::Currency
|
12
|
+
# Returns the ISO three-letter currency code as a symbol.
|
13
|
+
# e.g. :USD, :CAD, etc.
|
14
|
+
attr_reader :code
|
15
|
+
|
16
|
+
# The Currency's scale factor.
|
17
|
+
# e.g: the :USD scale factor is 100.
|
18
|
+
attr_reader :scale
|
19
|
+
|
20
|
+
# The Currency's scale factor.
|
21
|
+
# e.g: the :USD scale factor is 2, where 10 ^ 2 == 100.
|
22
|
+
attr_reader :scale_exp
|
23
|
+
|
24
|
+
# Used by Formatter.
|
25
|
+
attr_reader :format_right
|
26
|
+
|
27
|
+
# Used by Formatter.
|
28
|
+
attr_reader :format_left
|
29
|
+
|
30
|
+
# The Currency's symbol.
|
31
|
+
# e.g: USD symbol is '$'
|
32
|
+
attr_accessor :symbol
|
33
|
+
|
34
|
+
# The Currency's symbol as HTML.
|
35
|
+
# e.g: EUR symbol is '€' (:html € :) or '€' (:html € :)
|
36
|
+
attr_accessor :symbol_html
|
37
|
+
|
38
|
+
# The default Formatter.
|
39
|
+
attr_accessor :formatter
|
40
|
+
|
41
|
+
# The default parser.
|
42
|
+
attr_accessor :parser
|
43
|
+
|
44
|
+
|
45
|
+
# Create a new currency.
|
46
|
+
# This should only be called from Currency::Currency::Factory.
|
47
|
+
def initialize(code, symbol = nil, scale = 1000000)
|
48
|
+
self.code = code
|
49
|
+
self.symbol = symbol
|
50
|
+
self.scale = scale
|
51
|
+
|
52
|
+
@formatter =
|
53
|
+
@parser =
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
# Returns the Currency object from the default Currency::Currency::Factory
|
59
|
+
# by its three-letter uppercase Symbol, such as :USD, or :CAD.
|
60
|
+
def self.get(code)
|
61
|
+
# $stderr.puts "#{self}.get(#{code.inspect})"
|
62
|
+
return nil unless code
|
63
|
+
return code if code.kind_of?(::Currency::Currency)
|
64
|
+
Factory.default.get_by_code(code)
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
# Internal method for converting currency codes to internal
|
69
|
+
# Symbol format.
|
70
|
+
def self.cast_code(x)
|
71
|
+
x = x.upcase.intern if x.kind_of?(String)
|
72
|
+
raise ::Currency::Exception::InvalidCurrencyCode, x unless x.kind_of?(Symbol)
|
73
|
+
raise ::Currency::Exception::InvalidCurrencyCode, x unless x.to_s.length == 3
|
74
|
+
x
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
# Returns the hash of the Currency's code.
|
79
|
+
def hash
|
80
|
+
@code.hash
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
# Returns true if the Currency's are equal.
|
85
|
+
def eql?(x)
|
86
|
+
self.class == x.class && @code == x.code
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
# Returns true if the Currency's are equal.
|
91
|
+
def ==(x)
|
92
|
+
self.class == x.class && @code == x.code
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
# Clients should never call this directly.
|
97
|
+
def code=(x)
|
98
|
+
x = self.class.cast_code(x) unless x.nil?
|
99
|
+
@code = x
|
100
|
+
#$stderr.puts "#{self}.code = #{@code}"; x
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
# Clients should never call this directly.
|
105
|
+
def scale=(x)
|
106
|
+
@scale = x
|
107
|
+
return x if x.nil?
|
108
|
+
@scale_exp = Integer(Math.log10(@scale));
|
109
|
+
@format_right = - @scale_exp
|
110
|
+
@format_left = @format_right - 1
|
111
|
+
x
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
# Parse a Money string in this Currency.
|
116
|
+
#
|
117
|
+
# See Currency::Parser#parse.
|
118
|
+
#
|
119
|
+
def parse(str, *opt)
|
120
|
+
parser_or_default.parse(str, *opt)
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
def parser_or_default
|
125
|
+
(@parser || ::Currency::Parser.default)
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
# Formats the Money value as a string using the current Formatter.
|
130
|
+
# See Currency::Formatter#format.
|
131
|
+
def format(m, *opt)
|
132
|
+
formatter_or_default.format(m, *opt)
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
def formatter_or_default
|
137
|
+
(@formatter || ::Currency::Formatter.default)
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
# Returns the Currency code as a String.
|
142
|
+
def to_s
|
143
|
+
@code.to_s
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
# Returns the default Factory's currency.
|
148
|
+
def self.default
|
149
|
+
Factory.default.currency
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
# Sets the default Factory's currency.
|
154
|
+
def self.default=(x)
|
155
|
+
x = self.get(x) unless x.kind_of?(self)
|
156
|
+
Factory.default.currency = x
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
# If selector is [A-Z][A-Z][A-Z], load the currency via Factory.default.
|
161
|
+
#
|
162
|
+
# Currency::Currency.USD
|
163
|
+
# => #<Currency::Currency:0xb7d0917c @formatter=nil, @scale_exp=2, @scale=100, @symbol="$", @format_left=-3, @code=:USD, @parser=nil, @format_right=-2>
|
164
|
+
#
|
165
|
+
def self.method_missing(sel, *args, &blk)
|
166
|
+
if args.size == 0 && (! block_given?) && /^[A-Z][A-Z][A-Z]$/.match(sel.to_s)
|
167
|
+
Factory.default.get_by_code(sel)
|
168
|
+
else
|
169
|
+
super
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
end # class
|
174
|
+
|
175
|
+
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
|
2
|
+
# See LICENSE.txt for details.
|
3
|
+
|
4
|
+
|
5
|
+
# Responsible for creating Currency::Currency objects on-demand.
|
6
|
+
class Currency::Currency::Factory
|
7
|
+
@@default = nil
|
8
|
+
|
9
|
+
# Returns the default Currency::Factory.
|
10
|
+
def self.default
|
11
|
+
@@default ||= self.new
|
12
|
+
end
|
13
|
+
# Sets the default Currency::Factory.
|
14
|
+
def self.default=(x)
|
15
|
+
@@default = x
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def initialize(*opts)
|
20
|
+
@currency_by_code = { }
|
21
|
+
@currency_by_symbol = { }
|
22
|
+
@currency = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
# Lookup Currency by code.
|
27
|
+
def get_by_code(x)
|
28
|
+
x = ::Currency::Currency.cast_code(x)
|
29
|
+
# $stderr.puts "get_by_code(#{x})"
|
30
|
+
@currency_by_code[x] ||= install(load(::Currency::Currency.new(x)))
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
# Lookup Currency by symbol.
|
35
|
+
def get_by_symbol(symbol)
|
36
|
+
@currency_by_symbol[symbol] ||= install(load(::Currency::Currency.new(nil, symbol)))
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
# This method initializes a Currency object as
|
41
|
+
# requested from #get_by_code or #get_by_symbol.
|
42
|
+
#
|
43
|
+
# This method must initialize:
|
44
|
+
#
|
45
|
+
# currency.code
|
46
|
+
# currency.scale
|
47
|
+
#
|
48
|
+
# Optionally:
|
49
|
+
#
|
50
|
+
# currency.symbol
|
51
|
+
# currency.symbol_html
|
52
|
+
#
|
53
|
+
# Subclasses that provide Currency metadata should override this method.
|
54
|
+
# For example, loading Currency metadata from a database or YAML file.
|
55
|
+
def load(currency)
|
56
|
+
# $stderr.puts "BEFORE: load(#{currency.code})"
|
57
|
+
|
58
|
+
# Basic
|
59
|
+
if currency.code == :USD || currency.symbol == '$'
|
60
|
+
# $stderr.puts "load('USD')"
|
61
|
+
currency.code = :USD
|
62
|
+
currency.symbol = '$'
|
63
|
+
currency.scale = 1000000
|
64
|
+
elsif currency.code == :CAD
|
65
|
+
# $stderr.puts "load('CAD')"
|
66
|
+
currency.symbol = '$'
|
67
|
+
currency.scale = 1000000
|
68
|
+
elsif currency.code == :EUR
|
69
|
+
# $stderr.puts "load('CAD')"
|
70
|
+
currency.symbol = nil
|
71
|
+
currency.symbol_html = '€'
|
72
|
+
currency.scale = 1000000
|
73
|
+
else
|
74
|
+
currency.symbol = nil
|
75
|
+
currency.scale = 1000000
|
76
|
+
end
|
77
|
+
|
78
|
+
# $stderr.puts "AFTER: load(#{currency.inspect})"
|
79
|
+
|
80
|
+
currency
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
# Installs a new Currency for #get_by_symbol and #get_by_code.
|
85
|
+
def install(currency)
|
86
|
+
raise ::Currency::Exception::UnknownCurrency unless currency
|
87
|
+
@currency_by_symbol[currency.symbol] ||= currency unless currency.symbol.nil?
|
88
|
+
@currency_by_code[currency.code] = currency
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
# Returns the default Currency.
|
93
|
+
# Defaults to self.get_by_code(:USD).
|
94
|
+
def currency
|
95
|
+
@currency ||= self.get_by_code(:USD)
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
# Sets the default Currency.
|
100
|
+
def currency=(x)
|
101
|
+
@currency = x
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
# If selector is [A-Z][A-Z][A-Z], load the currency.
|
106
|
+
#
|
107
|
+
# factory.USD
|
108
|
+
# => #<Currency::Currency:0xb7d0917c @formatter=nil, @scale_exp=2, @scale=100, @symbol="$", @format_left=-3, @code=:USD, @parser=nil, @format_right=-2>
|
109
|
+
#
|
110
|
+
def method_missing(sel, *args, &blk)
|
111
|
+
if args.size == 0 && (! block_given?) && /^[A-Z][A-Z][A-Z]$/.match(sel.to_s)
|
112
|
+
self.get_by_code(sel)
|
113
|
+
else
|
114
|
+
super
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end # class
|
119
|
+
|
120
|
+
|
121
|
+
|