currency 0.3.3 → 0.4.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.
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