money-joshm1 5.1.2

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.
@@ -0,0 +1,355 @@
1
+ # encoding: utf-8
2
+
3
+ require 'json'
4
+
5
+ class Money
6
+
7
+ # Represents a specific currency unit.
8
+ class Currency
9
+ include Comparable
10
+
11
+ require "money/currency/loader"
12
+ extend Loader
13
+
14
+ require "money/currency/heuristics"
15
+ extend Heuristics
16
+
17
+ # Thrown when an unknown currency is requested.
18
+ class UnknownCurrency < StandardError; end
19
+
20
+ class << self
21
+
22
+ # Lookup a currency with given +id+ an returns a +Currency+ instance on
23
+ # success, +nil+ otherwise.
24
+ #
25
+ # @param [String, Symbol, #to_s] id Used to look into +table+ and
26
+ # retrieve the applicable attributes.
27
+ #
28
+ # @return [Money::Currency]
29
+ #
30
+ # @example
31
+ # Money::Currency.find(:eur) #=> #<Money::Currency id: eur ...>
32
+ # Money::Currency.find(:foo) #=> nil
33
+ def find(id)
34
+ id = id.to_s.downcase.to_sym
35
+ new(id) if self.table[id]
36
+ end
37
+
38
+ # Lookup a currency with given +num+ as an ISO 4217 numeric and returns an
39
+ # +Currency+ instance on success, +nil+ otherwise.
40
+ #
41
+ # @param [#to_s] num used to look into +table+ in +iso_numeric+ and find
42
+ # the right currency id.
43
+ #
44
+ # @return [Money::Currency]
45
+ #
46
+ # @example
47
+ # Money::Currency.find_by_iso_numeric(978) #=> #<Money::Currency id: eur ...>
48
+ # Money::Currency.find_by_iso_numeric('001') #=> nil
49
+ def find_by_iso_numeric(num)
50
+ num = num.to_s
51
+ id, _ = self.table.find{|key, currency| currency[:iso_numeric] == num}
52
+ new(id) if self.table[id]
53
+ end
54
+
55
+ # Wraps the object in a +Currency+ unless it's already a +Currency+
56
+ # object.
57
+ #
58
+ # @param [Object] object The object to attempt and wrap as a +Currency+
59
+ # object.
60
+ #
61
+ # @return [Money::Currency]
62
+ #
63
+ # @example
64
+ # c1 = Money::Currency.new(:usd)
65
+ # Money::Currency.wrap(nil) #=> nil
66
+ # Money::Currency.wrap(c1) #=> #<Money::Currency id: usd ...>
67
+ # Money::Currency.wrap("usd") #=> #<Money::Currency id: usd ...>
68
+ def wrap(object)
69
+ if object.nil?
70
+ nil
71
+ elsif object.is_a?(Currency)
72
+ object
73
+ else
74
+ Currency.new(object)
75
+ end
76
+ end
77
+
78
+ # List of known currencies.
79
+ #
80
+ # == monetary unit
81
+ # The standard unit of value of a currency, as the dollar in the United States or the peso in Mexico.
82
+ # http://www.answers.com/topic/monetary-unit
83
+ # == fractional monetary unit, subunit
84
+ # A monetary unit that is valued at a fraction (usually one hundredth) of the basic monetary unit
85
+ # http://www.answers.com/topic/fractional-monetary-unit-subunit
86
+ #
87
+ # See http://en.wikipedia.org/wiki/List_of_circulating_currencies and
88
+ # http://search.cpan.org/~tnguyen/Locale-Currency-Format-1.28/Format.pm
89
+ def table
90
+ @table ||= load_currencies
91
+ end
92
+
93
+ # List the currencies imported and registered
94
+ # @return [Array]
95
+ #
96
+ # @example
97
+ # Money::Currency.iso_codes()
98
+ # [#<Currency ..USD>, 'CAD', 'EUR']...
99
+ def all
100
+ table.keys.map {|curr| Currency.new(curr)}.sort_by(&:priority)
101
+ end
102
+
103
+ # We need a string-based validator before creating an unbounded number of symbols.
104
+ # http://www.randomhacks.net/articles/2007/01/20/13-ways-of-looking-at-a-ruby-symbol#11
105
+ # https://github.com/RubyMoney/money/issues/132
106
+ def stringified_keys
107
+ @stringified_keys ||= stringify_keys
108
+ end
109
+
110
+ def register(curr)
111
+ key = curr[:iso_code].downcase.to_sym
112
+ @table[key] = curr
113
+ @stringified_keys = stringify_keys
114
+ end
115
+
116
+ private
117
+
118
+ def stringify_keys
119
+ table.keys.map{|k| k.to_s.downcase}
120
+ end
121
+ end
122
+
123
+ # The symbol used to identify the currency, usually the lowercase
124
+ # +iso_code+ attribute.
125
+ #
126
+ # @return [Symbol]
127
+ attr_reader :id
128
+
129
+ # A numerical value you can use to sort/group the currency list.
130
+ #
131
+ # @return [Integer]
132
+ attr_reader :priority
133
+
134
+ # The international 3-letter code as defined by the ISO 4217 standard.
135
+ #
136
+ # @return [String]
137
+ attr_reader :iso_code
138
+ #
139
+ # The international 3-numeric code as defined by the ISO 4217 standard.
140
+ #
141
+ # @return [String]
142
+ attr_reader :iso_numeric
143
+
144
+ # The currency name.
145
+ #
146
+ # @return [String]
147
+ attr_reader :name
148
+
149
+ # The currency symbol (UTF-8 encoded).
150
+ #
151
+ # @return [String]
152
+ attr_reader :symbol
153
+
154
+ # The html entity for the currency symbol
155
+ #
156
+ # @return [String]
157
+ attr_reader :html_entity
158
+
159
+ # The name of the fractional monetary unit.
160
+ #
161
+ # @return [String]
162
+ attr_reader :subunit
163
+
164
+ # The proportion between the unit and the subunit
165
+ #
166
+ # @return [Integer]
167
+ attr_reader :subunit_to_unit
168
+
169
+ # The decimal mark, or character used to separate the whole unit from the subunit.
170
+ #
171
+ # @return [String]
172
+ attr_reader :decimal_mark
173
+ alias :separator :decimal_mark
174
+
175
+ # The character used to separate thousands grouping of the whole unit.
176
+ #
177
+ # @return [String]
178
+ attr_reader :thousands_separator
179
+ alias :delimiter :thousands_separator
180
+
181
+ # Should the currency symbol precede the amount, or should it come after?
182
+ #
183
+ # @return [Boolean]
184
+ attr_reader :symbol_first
185
+
186
+ # Create a new +Currency+ object.
187
+ #
188
+ # @param [String, Symbol, #to_s] id Used to look into +table+ and retrieve
189
+ # the applicable attributes.
190
+ #
191
+ # @return [Money::Currency]
192
+ #
193
+ # @example
194
+ # Money::Currency.new(:usd) #=> #<Money::Currency id: usd ...>
195
+ def initialize(id)
196
+ id = id.to_s.downcase
197
+ raise(UnknownCurrency, "Unknown currency `#{id}'") unless self.class.stringified_keys.include?(id)
198
+
199
+ @id = id.to_sym
200
+ data = self.class.table[@id]
201
+ data.each_pair do |key, value|
202
+ instance_variable_set(:"@#{key}", value)
203
+ end
204
+ end
205
+
206
+ # Compares +self+ with +other_currency+ against the value of +priority+
207
+ # attribute.
208
+ #
209
+ # @param [Money::Currency] other_currency The currency to compare to.
210
+ #
211
+ # @return [-1,0,1] -1 if less than, 0 is equal to, 1 if greater than
212
+ #
213
+ # @example
214
+ # c1 = Money::Currency.new(:usd)
215
+ # c2 = Money::Currency.new(:jpy)
216
+ # c1 <=> c2 #=> 1
217
+ # c2 <=> c1 #=> -1
218
+ # c1 <=> c1 #=> 0
219
+ def <=>(other_currency)
220
+ self.priority <=> other_currency.priority
221
+ end
222
+
223
+ # Compares +self+ with +other_currency+ and returns +true+ if the are the
224
+ # same or if their +id+ attributes match.
225
+ #
226
+ # @param [Money::Currency] other_currency The currency to compare to.
227
+ #
228
+ # @return [Boolean]
229
+ #
230
+ # @example
231
+ # c1 = Money::Currency.new(:usd)
232
+ # c2 = Money::Currency.new(:jpy)
233
+ # c1 == c1 #=> true
234
+ # c1 == c2 #=> false
235
+ def ==(other_currency)
236
+ self.equal?(other_currency) ||
237
+ self.id.to_s.downcase == (other_currency.is_a?(Currency) ? other_currency.id.to_s.downcase : other_currency.to_s.downcase)
238
+ end
239
+
240
+ # Compares +self+ with +other_currency+ and returns +true+ if the are the
241
+ # same or if their +id+ attributes match.
242
+ #
243
+ # @param [Money::Currency] other_currency The currency to compare to.
244
+ #
245
+ # @return [Boolean]
246
+ #
247
+ # @example
248
+ # c1 = Money::Currency.new(:usd)
249
+ # c2 = Money::Currency.new(:jpy)
250
+ # c1.eql? c1 #=> true
251
+ # c1.eql? c2 #=> false
252
+ def eql?(other_currency)
253
+ self == other_currency
254
+ end
255
+
256
+ # Returns a Fixnum hash value based on the +id+ attribute in order to use
257
+ # functions like & (intersection), group_by, etc.
258
+ #
259
+ # @return [Fixnum]
260
+ #
261
+ # @example
262
+ # Money::Currency.new(:usd).hash #=> 428936
263
+ def hash
264
+ id.hash
265
+ end
266
+
267
+ # Returns a human readable representation.
268
+ #
269
+ # @return [String]
270
+ #
271
+ # @example
272
+ # Money::Currency.new(:usd) #=> #<Currency id: usd ...>
273
+ def inspect
274
+ "#<#{self.class.name} id: #{id}, priority: #{priority}, symbol_first: #{symbol_first}, thousands_separator: #{thousands_separator}, html_entity: #{html_entity}, decimal_mark: #{decimal_mark}, name: #{name}, symbol: #{symbol}, subunit_to_unit: #{subunit_to_unit}, exponent: #{exponent}, iso_code: #{iso_code}, iso_numeric: #{iso_numeric}, subunit: #{subunit}>"
275
+ end
276
+
277
+ # Returns a string representation corresponding to the upcase +id+
278
+ # attribute.
279
+ #
280
+ # -–
281
+ # DEV: id.to_s.upcase corresponds to iso_code but don't use ISO_CODE for consistency.
282
+ #
283
+ # @return [String]
284
+ #
285
+ # @example
286
+ # Money::Currency.new(:usd).to_s #=> "USD"
287
+ # Money::Currency.new(:eur).to_s #=> "EUR"
288
+ def to_s
289
+ id.to_s.upcase
290
+ end
291
+
292
+ # Conversation to +self+.
293
+ #
294
+ # @return [self]
295
+ def to_currency
296
+ self
297
+ end
298
+
299
+
300
+ # Returns currency symbol or iso code for currencies with no symbol.
301
+ #
302
+ # @return [String]
303
+ def code
304
+ symbol || iso_code
305
+ end
306
+
307
+ def symbol_first?
308
+ !!@symbol_first
309
+ end
310
+
311
+ # Returns the number of digits after the decimal separator.
312
+ #
313
+ # @return [Float]
314
+ def exponent
315
+ Math.log10(@subunit_to_unit)
316
+ end
317
+
318
+ # Cache decimal places for subunit_to_unit values. Common ones pre-cached.
319
+ def self.decimal_places_cache
320
+ @decimal_places_cache ||= {
321
+ 1 => 0,
322
+ 10 => 1,
323
+ 100 => 2,
324
+ 1000 => 3
325
+ }
326
+ end
327
+
328
+ # The number of decimal places needed.
329
+ #
330
+ # @return [Integer]
331
+ def decimal_places
332
+ cache = self.class.decimal_places_cache
333
+ places = cache[subunit_to_unit]
334
+ unless places
335
+ places = calculate_decimal_places(subunit_to_unit)
336
+ cache[subunit_to_unit] = places
337
+ end
338
+ places
339
+ end
340
+
341
+ # If we need to figure out how many decimal places we need we
342
+ # use repeated integer division.
343
+ def calculate_decimal_places(num)
344
+ return 0 if num == 1
345
+ i = 1
346
+ while num >= 10
347
+ num /= 10
348
+ i += 1 if num >= 10
349
+ end
350
+ i
351
+ end
352
+ private :calculate_decimal_places
353
+
354
+ end
355
+ end
@@ -0,0 +1,149 @@
1
+ # encoding: utf-8
2
+
3
+ module Money::Currency::Heuristics
4
+
5
+ # An robust and efficient algorithm for finding currencies in
6
+ # text. Using several algorithms it can find symbols, iso codes and
7
+ # even names of currencies.
8
+ # Although not recommendable, it can also attempt to find the given
9
+ # currency in an entire sentence
10
+ #
11
+ # Returns: Array (matched results)
12
+ def analyze(str)
13
+ return Analyzer.new(str, search_tree).process
14
+ end
15
+
16
+ private
17
+
18
+ # Build a search tree from the currency database
19
+ def search_tree
20
+ @_search_tree ||= {
21
+ :by_symbol => currencies_by_symbol,
22
+ :by_iso_code => currencies_by_iso_code,
23
+ :by_name => currencies_by_name
24
+ }
25
+ end
26
+
27
+ def currencies_by_symbol
28
+ {}.tap do |r|
29
+ table.each do |dummy, c|
30
+ symbol = (c[:symbol]||"").downcase
31
+ symbol.chomp!('.')
32
+ (r[symbol] ||= []) << c
33
+
34
+ (c[:alternate_symbols]||[]).each do |ac|
35
+ ac = ac.downcase
36
+ ac.chomp!('.')
37
+ (r[ac] ||= []) << c
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ def currencies_by_iso_code
44
+ {}.tap do |r|
45
+ table.each do |dummy,c|
46
+ (r[c[:iso_code].downcase] ||= []) << c
47
+ end
48
+ end
49
+ end
50
+
51
+ def currencies_by_name
52
+ {}.tap do |r|
53
+ table.each do |dummy,c|
54
+ name_parts = c[:name].downcase.split
55
+ name_parts.each {|part| part.chomp!('.')}
56
+
57
+ # construct one branch per word
58
+ root = r
59
+ while name_part = name_parts.shift
60
+ root = (root[name_part] ||= {})
61
+ end
62
+
63
+ # the leaf is a currency
64
+ (root[:value] ||= []) << c
65
+ end
66
+ end
67
+ end
68
+
69
+ class Analyzer
70
+ attr_reader :search_tree, :words
71
+ attr_accessor :str, :currencies
72
+
73
+ def initialize str, search_tree
74
+ @str = (str||'').dup
75
+ @search_tree = search_tree
76
+ @currencies = []
77
+ end
78
+
79
+ def process
80
+ format
81
+ return [] if str.empty?
82
+
83
+ search_by_symbol
84
+ search_by_iso_code
85
+ search_by_name
86
+
87
+ prepare_reply
88
+ end
89
+
90
+ def format
91
+ str.gsub!(/[\r\n\t]/,'')
92
+ str.gsub!(/[0-9][\.,:0-9]*[0-9]/,'')
93
+ str.gsub!(/[0-9]/, '')
94
+ str.downcase!
95
+ @words = str.split
96
+ @words.each {|word| word.chomp!('.'); word.chomp!(',') }
97
+ end
98
+
99
+ def search_by_symbol
100
+ words.each do |word|
101
+ if found = search_tree[:by_symbol][word]
102
+ currencies.concat(found)
103
+ end
104
+ end
105
+ end
106
+
107
+ def search_by_iso_code
108
+ words.each do |word|
109
+ if found = search_tree[:by_iso_code][word]
110
+ currencies.concat(found)
111
+ end
112
+ end
113
+ end
114
+
115
+ def search_by_name
116
+ # remember, the search tree by name is a construct of branches and leaf!
117
+ # We need to try every combination of words within the sentence, so we
118
+ # end up with a x^2 equation, which should be fine as most names are either
119
+ # one or two words, and this is multiplied with the words of given sentence
120
+
121
+ search_words = words.dup
122
+
123
+ while search_words.length > 0
124
+ root = search_tree[:by_name]
125
+
126
+ search_words.each do |word|
127
+ if root = root[word]
128
+ if root[:value]
129
+ currencies.concat(root[:value])
130
+ end
131
+ else
132
+ break
133
+ end
134
+ end
135
+
136
+ search_words.delete_at(0)
137
+ end
138
+ end
139
+
140
+ def prepare_reply
141
+ codes = currencies.map do |currency|
142
+ currency[:iso_code]
143
+ end
144
+ codes.uniq!
145
+ codes.sort!
146
+ codes
147
+ end
148
+ end
149
+ end