currency 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/COPYING.txt +339 -0
  2. data/LICENSE.txt +62 -0
  3. data/Manifest.txt +37 -14
  4. data/README.txt +8 -0
  5. data/Rakefile +42 -8
  6. data/Releases.txt +26 -0
  7. data/TODO.txt +1 -0
  8. data/examples/ex1.rb +3 -3
  9. data/examples/xe1.rb +3 -2
  10. data/lib/currency.rb +71 -9
  11. data/lib/currency/active_record.rb +138 -21
  12. data/lib/currency/core_extensions.rb +7 -5
  13. data/lib/currency/currency.rb +94 -177
  14. data/lib/currency/{currency_factory.rb → currency/factory.rb} +46 -25
  15. data/lib/currency/currency_version.rb +3 -3
  16. data/lib/currency/exception.rb +14 -14
  17. data/lib/currency/exchange.rb +14 -12
  18. data/lib/currency/exchange/rate.rb +159 -28
  19. data/lib/currency/exchange/rate/deriver.rb +146 -0
  20. data/lib/currency/exchange/rate/source.rb +84 -0
  21. data/lib/currency/exchange/rate/source/base.rb +156 -0
  22. data/lib/currency/exchange/rate/source/failover.rb +57 -0
  23. data/lib/currency/exchange/rate/source/historical.rb +79 -0
  24. data/lib/currency/exchange/rate/source/historical/rate.rb +181 -0
  25. data/lib/currency/exchange/rate/source/historical/writer.rb +203 -0
  26. data/lib/currency/exchange/rate/source/new_york_fed.rb +91 -0
  27. data/lib/currency/exchange/rate/source/provider.rb +105 -0
  28. data/lib/currency/exchange/rate/source/test.rb +50 -0
  29. data/lib/currency/exchange/rate/source/the_financials.rb +190 -0
  30. data/lib/currency/exchange/rate/source/timed_cache.rb +144 -0
  31. data/lib/currency/exchange/rate/source/xe.rb +166 -0
  32. data/lib/currency/exchange/time_quantitizer.rb +111 -0
  33. data/lib/currency/formatter.rb +159 -0
  34. data/lib/currency/macro.rb +321 -0
  35. data/lib/currency/money.rb +90 -64
  36. data/lib/currency/money_helper.rb +6 -5
  37. data/lib/currency/parser.rb +153 -0
  38. data/test/ar_column_test.rb +6 -3
  39. data/test/ar_simple_test.rb +5 -2
  40. data/test/ar_test_base.rb +39 -33
  41. data/test/ar_test_core.rb +64 -0
  42. data/test/formatter_test.rb +81 -0
  43. data/test/historical_writer_test.rb +184 -0
  44. data/test/macro_test.rb +109 -0
  45. data/test/money_test.rb +72 -4
  46. data/test/new_york_fed_test.rb +57 -0
  47. data/test/parser_test.rb +60 -0
  48. data/test/test_base.rb +13 -3
  49. data/test/time_quantitizer_test.rb +136 -0
  50. data/test/xe_test.rb +29 -5
  51. metadata +41 -18
  52. data/lib/currency/exchange/base.rb +0 -84
  53. data/lib/currency/exchange/test.rb +0 -39
  54. data/lib/currency/exchange/xe.rb +0 -250
@@ -1,3 +1,5 @@
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # See LICENSE.txt for details.
1
3
 
2
4
 
3
5
  # External representation mixin
@@ -12,7 +14,7 @@ end
12
14
  # External representation mixin
13
15
  class Integer
14
16
  # Exact conversion to Money representation value.
15
- def Money_rep(currency)
17
+ def Money_rep(currency, time = nil)
16
18
  Integer(self * currency.scale)
17
19
  end
18
20
  end
@@ -21,7 +23,7 @@ end
21
23
  # External representation mixin
22
24
  class Float
23
25
  # Inexact conversion to Money representation value.
24
- def Money_rep(currency)
26
+ def Money_rep(currency, time = nil)
25
27
  Integer(self * currency.scale)
26
28
  end
27
29
  end
@@ -30,9 +32,9 @@ end
30
32
  # External representation mixin
31
33
  class String
32
34
  # Exact conversion to Money representation value.
33
- def Money_rep(currency)
34
- x = currency.parse(self, :currency => currency)
35
- x.rep if x.kind_of?(Currency::Money)
35
+ def Money_rep(currency, time = nil)
36
+ x = currency.parse(self, :currency => currency, :time => time)
37
+ x = x.rep if x.respond_to?(:rep)
36
38
  x
37
39
  end
38
40
  end
@@ -1,73 +1,103 @@
1
- # -*- ruby -*-
2
- #
3
- # = Currency::Currency
4
- #
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # See LICENSE.txt for details.
3
+
4
+
5
5
  # Represents a currency.
6
6
  #
7
+ # Currency objects are created on-demand by Currency::Currency::Factory.
7
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 '&#8364;' (:html &#8364; :) or '&euro;' (:html &euro; :)
36
+ attr_accessor :symbol_html
37
+
38
+ # The default Formatter.
39
+ attr_accessor :formatter
40
+
41
+ # The default parser.
42
+ attr_accessor :parser
8
43
 
9
- module Currency
10
- #include Currency::Exceptions
11
44
 
12
- class Currency
13
45
  # Create a new currency.
14
- # This should only be called from Currency::CurrencyFactory.
46
+ # This should only be called from Currency::Currency::Factory.
15
47
  def initialize(code, symbol = nil, scale = 100)
16
48
  self.code = code
17
49
  self.symbol = symbol
18
50
  self.scale = scale
19
51
  end
20
52
 
21
- # Returns the Currency object from the default CurrencyFactory
22
- # by its 3-letter uppercase Symbol name, such as :USD, or :CAD.
53
+
54
+ # Returns the Currency object from the default Currency::Currency::Factory
55
+ # by its three-letter uppercase Symbol, such as :USD, or :CAD.
23
56
  def self.get(code)
24
- CurrencyFactory.default.get_by_code(code)
57
+ # $stderr.puts "#{self}.get(#{code.inspect})"
58
+ return nil unless code
59
+ return code if code.kind_of?(::Currency::Currency)
60
+ Factory.default.get_by_code(code)
25
61
  end
26
62
 
63
+
27
64
  # Internal method for converting currency codes to internal
28
65
  # Symbol format.
29
66
  def self.cast_code(x)
30
- x = x.upcase.intern if x.kind_of?(String)
31
- raise Exception::InvalidCurrencyCode.new(x) unless x.kind_of?(Symbol)
32
- raise Exception::InvalidCurrencyCode.new(x) unless x.to_s.length == 3
33
- x
67
+ x = x.upcase.intern if x.kind_of?(String)
68
+ raise ::Currency::Exception::InvalidCurrencyCode.new(x) unless x.kind_of?(Symbol)
69
+ raise ::Currency::Exception::InvalidCurrencyCode.new(x) unless x.to_s.length == 3
70
+ x
34
71
  end
35
72
 
73
+
36
74
  # Returns the hash of the Currency's code.
37
75
  def hash
38
76
  @code.hash
39
77
  end
40
78
 
79
+
41
80
  # Returns true if the Currency's are equal.
42
81
  def eql?(x)
43
82
  self.class == x.class && @code == x.code
44
83
  end
45
84
 
85
+
46
86
  # Returns true if the Currency's are equal.
47
87
  def ==(x)
48
88
  self.class == x.class && @code == x.code
49
89
  end
50
90
 
51
- # Get the Currency's code.
52
- def code
53
- @code
54
- end
55
91
 
56
-
57
- # Client should never call this directly.
92
+ # Clients should never call this directly.
58
93
  def code=(x)
59
94
  x = self.class.cast_code(x) unless x.nil?
60
95
  @code = x
61
96
  #$stderr.puts "#{self}.code = #{@code}"; x
62
97
  end
63
98
 
64
- # Get the Currency's scale factor.
65
- # E.g: the :USD scale factor is 100.
66
- def scale
67
- @scale
68
- end
69
99
 
70
- # Client should never call this directly.
100
+ # Clients should never call this directly.
71
101
  def scale=(x)
72
102
  @scale = x
73
103
  return x if x.nil?
@@ -77,178 +107,65 @@ module Currency
77
107
  x
78
108
  end
79
109
 
80
- # Get the Currency's scale factor.
81
- # E.g: the :USD scale factor is 2, where 10 ^ 2 == 100.
82
- def scale_exp
83
- @scale_exp
84
- end
85
-
86
- # Get the Currency's symbol.
87
- # E.g. :USD, :CAD, etc.
88
- def symbol
89
- @symbol
90
- end
91
110
 
92
- # Client should never call this directly.
93
- def symbol=(x)
94
- @symbol = x
95
- end
96
-
97
- # Parse a Money string.
98
- # Options:
99
- # :currency => Currency object.
100
- # Look for a matching currency code at the beginning or end of the string.
101
- # If the currency does not match IncompatibleCurrency is raised.
111
+ # Parse a Money string in this Currency.
112
+ #
113
+ # See Currency::Parser#parse.
102
114
  #
103
115
  def parse(str, *opt)
104
- x = str
105
- opt = Hash[*opt]
106
-
107
- md = nil # match data
108
-
109
- # $stderr.puts "'#{x}'.Money_rep(#{self})"
110
-
111
- # Handle currency code at front of string.
112
- if (md = /^([A-Z][A-Z][A-Z])\s*(.*)$/.match(x)) != nil
113
- curr = CurrencyFactory.default.get_by_code(md[1])
114
- x = md[2]
115
- if curr != self
116
- if opt[:currency] && opt[:currency] != curr
117
- raise Exception::IncompatibleCurrency.new("#{str} #{opt[:currency].code}")
118
- end
119
- return Money.new(x, curr);
120
- end
121
- # Handle currency code at end of string.
122
- elsif (md = /^(.*)\s*([A-Z][A-Z][A-Z])$/.match(x)) != nil
123
- curr = CurrencyFactory.default.get_by_code(md[2])
124
- x = md[1]
125
- if curr != self
126
- if opt[:currency] && opt[:currency] != curr
127
- raise Exception::IncompatibleCurrency.new("#{str} #{opt[:currency].code}")
128
- end
129
- return Money.new(x, curr);
130
- end
131
- end
132
-
133
- # Remove placeholders and symbol.
134
- x = x.gsub(/[, ]/, '')
135
- x = x.sub(@symbol, '') if @symbol
136
-
137
- # Match: whole Currency value.
138
- if x =~ /^[-+]?(\d+)\.?$/
139
- # $stderr.puts "'#{self}'.parse(#{str}) => EXACT"
140
- x.to_i.Money_rep(self)
141
-
142
- # Match: fractional Currency value.
143
- elsif (md = /^([-+]?)(\d*)\.(\d+)$/.match(x)) != nil
144
- sign = md[1]
145
- whole = md[2]
146
- part = md[3]
147
-
148
- # $stderr.puts "'#{self}'.parse(#{str}) => DECIMAL (#{sign} #{whole} . #{part})"
149
-
150
- if part.length != self.scale
151
-
152
- # Pad decimal places with additional '0'
153
- while part.length < self.scale_exp
154
- part << '0'
155
- end
156
-
157
- # Truncate to Currency's decimal size.
158
- part = part[0..(self.scale_exp - 1)]
159
-
160
- # $stderr.puts " => INEXACT DECIMAL '#{whole}'"
161
- end
162
-
163
- # Put the string back together:
164
- # #{sign}#{whole}#{part}
165
- whole = sign + whole + part
166
- # $stderr.puts " => REP = #{whole}"
167
-
168
- x = whole.to_i
169
-
170
- x = Money.new_rep(x, opt[:currency])
171
- else
172
- # $stderr.puts "'#{self}'.parse(#{str}) => ??? '#{x}'"
173
- #x.to_f.Money_rep(self)
174
- raise Exception::InvalidMoneyString.new("#{str} #{self}")
175
- end
116
+ parser_or_default.parse(str, *opt)
176
117
  end
177
118
 
178
- # Format a Money object.
179
- def format(m, *opt)
180
- opt = opt.flatten
181
119
 
182
- # Get scaled integer representation for this Currency.
183
- x = m.Money_rep(self)
184
-
185
- # Remove sign.
186
- x = - x if ( neg = x < 0 )
187
-
188
- # Convert to String.
189
- x = x.to_s
190
-
191
- # Keep prefixing "0" until filled to scale.
192
- while ( x.length <= @scale_exp )
193
- x = "0" + x
194
- end
120
+ def parser_or_default
121
+ (@parser || ::Currency::Parser.default)
122
+ end
195
123
 
196
- # Insert decimal place
197
- whole = x[0..@format_left]
198
- decimal = x[@format_right..-1]
199
-
200
- # Do commas
201
- x = whole
202
- unless opt.include?(:no_thousands)
203
- x.reverse!
204
- x.gsub!(/(\d\d\d)/) {|y| y + ','}
205
- x.sub!(/,$/,'')
206
- x.reverse!
207
- end
208
124
 
209
- x << '.' + decimal unless opt.include?(:no_cents) || opt.include?(:no_decimal)
125
+ # Formats the Money value as a string using the current Formatter.
126
+ # See Currency::Formatter#format.
127
+ def format(m, *opt)
128
+ formatter_or_default.format(m, *opt)
129
+ end
210
130
 
211
- # Put sign back.
212
- x = "-" + x if neg
213
131
 
214
- # Add symbol?
215
- x = (@symbol || '') + x unless opt.include?(:no_symbol)
132
+ def formatter_or_default
133
+ (@formatter || ::Currency::Formatter.default)
134
+ end
216
135
 
217
- # Suffix currency code.
218
- if opt.include?(:with_currency)
219
- x << ' '
220
- x << '<span class="currency">' if opt.include?(:html)
221
- x << @code.to_s
222
- x << '</span>' if opt.include?(:html)
223
- end
224
136
 
225
- x
137
+ # Returns the Currency code as a String.
138
+ def to_s
139
+ @code.to_s
226
140
  end
227
141
 
228
- #def to_s
229
- # @code.to_s
230
- #end
231
142
 
232
- # Returns the default CurrencyFactory's currency.
143
+ # Returns the default Factory's currency.
233
144
  def self.default
234
- CurrencyFactory.default.currency
145
+ Factory.default.currency
235
146
  end
236
- # Sets the default CurrencyFactory's currency.
147
+
148
+
149
+ # Sets the default Factory's currency.
237
150
  def self.default=(x)
238
- CurrencyFactory.default.currency = x
151
+ x = self.get(x) unless x.kind_of?(self)
152
+ Factory.default.currency = x
239
153
  end
240
154
 
241
- # Returns the USD Currency.
242
- def self.USD
243
- CurrencyFactory.default.USD
244
- end
245
155
 
246
- # Returns the CAD Currency.
247
- def self.CAD
248
- CurrencyFactory.default.CAD
156
+ # If selector is [A-Z][A-Z][A-Z], load the currency via Factory.default.
157
+ #
158
+ # Currency::Currency.USD
159
+ # => #<Currency::Currency:0xb7d0917c @formatter=nil, @scale_exp=2, @scale=100, @symbol="$", @format_left=-3, @code=:USD, @parser=nil, @format_right=-2>
160
+ #
161
+ def self.method_missing(sel, *args, &blk)
162
+ if args.size == 0 && (! block_given?) && /^[A-Z][A-Z][A-Z]$/.match(sel.to_s)
163
+ Factory.default.get_by_code(sel)
164
+ else
165
+ super
166
+ end
249
167
  end
250
168
 
251
- end # class
169
+ end # class
252
170
 
253
- end # module
254
171
 
@@ -1,44 +1,54 @@
1
- module Currency
2
- class CurrencyFactory
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
3
7
  @@default = nil
4
8
 
5
- # Returns the default CurrencyFactory.
9
+ # Returns the default Currency::Factory.
6
10
  def self.default
7
- @@default ||= CurrencyFactory.new
11
+ @@default ||= self.new
8
12
  end
9
- # Sets the default CurrencyFactory.
13
+ # Sets the default Currency::Factory.
10
14
  def self.default=(x)
11
15
  @@default = x
12
16
  end
13
17
 
18
+
14
19
  def initialize(*opts)
15
20
  @currency_by_code = { }
16
21
  @currency_by_symbol = { }
17
22
  @currency = nil
18
- @USD = nil
19
- @CAD = nil
20
23
  end
21
24
 
25
+
22
26
  # Lookup Currency by code.
23
27
  def get_by_code(x)
24
- x = Currency.cast_code(x)
28
+ x = ::Currency::Currency.cast_code(x)
25
29
  # $stderr.puts "get_by_code(#{x})"
26
- @currency_by_code[x] ||= install(load(Currency.new(x)))
30
+ @currency_by_code[x] ||= install(load(::Currency::Currency.new(x)))
27
31
  end
28
32
 
33
+
29
34
  # Lookup Currency by symbol.
30
35
  def get_by_symbol(symbol)
31
- @currency_by_symbol[symbol] ||= install(load(Currency.new(nil, symbol)))
36
+ @currency_by_symbol[symbol] ||= install(load(::Currency::Currency.new(nil, symbol)))
32
37
  end
33
38
 
39
+
34
40
  # This method initializes a Currency object as
35
41
  # requested from #get_by_code or #get_by_symbol.
36
42
  #
37
43
  # This method must initialize:
38
44
  #
39
45
  # currency.code
40
- # currency.symbol
41
46
  # currency.scale
47
+ #
48
+ # Optionally:
49
+ #
50
+ # currency.symbol
51
+ # currency.symbol_html
42
52
  #
43
53
  # Subclasses that provide Currency metadata should override this method.
44
54
  # For example, loading Currency metadata from a database or YAML file.
@@ -55,6 +65,11 @@ module Currency
55
65
  # $stderr.puts "load('CAD')"
56
66
  currency.symbol = '$'
57
67
  currency.scale = 100
68
+ elsif currency.code == :EUR
69
+ # $stderr.puts "load('CAD')"
70
+ currency.symbol = nil
71
+ currency.symbol_html = '&#8364;'
72
+ currency.scale = 100
58
73
  else
59
74
  currency.symbol = nil
60
75
  currency.scale = 100
@@ -65,36 +80,42 @@ module Currency
65
80
  currency
66
81
  end
67
82
 
83
+
68
84
  # Installs a new Currency for #get_by_symbol and #get_by_code.
69
85
  def install(currency)
70
- raise Exception::UnknownCurrency.new() unless currency
86
+ raise ::Currency::Exception::UnknownCurrency.new() unless currency
71
87
  @currency_by_symbol[currency.symbol] ||= currency unless currency.symbol.nil?
72
88
  @currency_by_code[currency.code] = currency
73
89
  end
74
90
 
75
- # Standard Currency: US Dollars, :USD.
76
- def USD
77
- @USD ||= self.get_by_code(:USD)
78
- end
79
-
80
- # Standard Currency: Canadian Dollars, :CAD.
81
- def CAD
82
- @CAD ||= self.get_by_code(:CAD)
83
- end
84
91
 
85
92
  # Returns the default Currency.
86
- # Defaults to self.USD.
93
+ # Defaults to self.get_by_code(:USD).
87
94
  def currency
88
- @currency ||= self.USD
95
+ @currency ||= self.get_by_code(:USD)
89
96
  end
90
97
 
98
+
91
99
  # Sets the default Currency.
92
100
  def currency=(x)
93
101
  @currency = x
94
102
  end
95
103
 
96
- end # class
97
- end # module
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
98
119
 
99
120
 
100
121