money 5.1.0.beta1 → 5.1.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.
@@ -4,6 +4,7 @@
4
4
  "iso_code": "EEK",
5
5
  "name": "Estonian Kroon",
6
6
  "symbol": "KR",
7
+ "alternate_symbols": [],
7
8
  "subunit": "Sent",
8
9
  "subunit_to_unit": 100,
9
10
  "symbol_first": false,
@@ -17,6 +18,7 @@
17
18
  "iso_code": "GHS",
18
19
  "name": "Ghanaian Cedi",
19
20
  "symbol": "₵",
21
+ "alternate_symbols": ["GH₵"],
20
22
  "subunit": "Pesewa",
21
23
  "subunit_to_unit": 100,
22
24
  "symbol_first": true,
@@ -30,6 +32,7 @@
30
32
  "iso_code": "TMM",
31
33
  "name": "Turkmenistani Manat",
32
34
  "symbol": "m",
35
+ "alternate_symbols": [],
33
36
  "subunit": "Tennesi",
34
37
  "subunit_to_unit": 100,
35
38
  "symbol_first": false,
@@ -43,6 +46,7 @@
43
46
  "iso_code": "JPY",
44
47
  "name": "Japanese Yen",
45
48
  "symbol": "¥",
49
+ "alternate_symbols": ["円", "圓"],
46
50
  "subunit": "Sen",
47
51
  "subunit_to_unit": 100,
48
52
  "symbol_first": true,
@@ -56,6 +60,7 @@
56
60
  "iso_code": "ZWD",
57
61
  "name": "Zimbabwean Dollar",
58
62
  "symbol": "$",
63
+ "alternate_symbols": ["Z$"],
59
64
  "subunit": "Cent",
60
65
  "subunit_to_unit": 100,
61
66
  "symbol_first": true,
@@ -69,6 +74,7 @@
69
74
  "iso_code": "ZWL",
70
75
  "name": "Zimbabwean Dollar",
71
76
  "symbol": "$",
77
+ "alternate_symbols": ["Z$"],
72
78
  "subunit": "Cent",
73
79
  "subunit_to_unit": 100,
74
80
  "symbol_first": true,
@@ -82,6 +88,7 @@
82
88
  "iso_code": "ZWN",
83
89
  "name": "Zimbabwean Dollar",
84
90
  "symbol": "$",
91
+ "alternate_symbols": ["Z$"],
85
92
  "subunit": "Cent",
86
93
  "subunit_to_unit": 100,
87
94
  "symbol_first": true,
@@ -95,6 +102,7 @@
95
102
  "iso_code": "ZWR",
96
103
  "name": "Zimbabwean Dollar",
97
104
  "symbol": "$",
105
+ "alternate_symbols": ["Z$"],
98
106
  "subunit": "Cent",
99
107
  "subunit_to_unit": 100,
100
108
  "symbol_first": true,
@@ -1,7 +1,6 @@
1
1
  require "bigdecimal"
2
2
  require "bigdecimal/util"
3
3
  require "i18n" rescue LoadError
4
- require "money/currency_loader"
5
4
  require "money/currency"
6
5
  require "money/money"
7
6
  require "money/core_extensions"
@@ -7,7 +7,12 @@ class Money
7
7
  # Represents a specific currency unit.
8
8
  class Currency
9
9
  include Comparable
10
- extend CurrencyLoader
10
+
11
+ require "money/currency/loader"
12
+ extend Loader
13
+
14
+ require "money/currency/heuristics"
15
+ extend Heuristics
11
16
 
12
17
  # Thrown when an unknown currency is requested.
13
18
  class UnknownCurrency < StandardError; 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
@@ -1,7 +1,7 @@
1
- module CurrencyLoader
1
+ module Money::Currency::Loader
2
2
  extend self
3
3
 
4
- DATA_PATH = File.expand_path("../../../config", __FILE__)
4
+ DATA_PATH = File.expand_path("../../../../config", __FILE__)
5
5
 
6
6
  # Loads and returns the currencies stored in JSON files in the config directory.
7
7
  #
@@ -42,7 +42,7 @@ class Money
42
42
  # from the stated currency string
43
43
  c = if Money.assume_from_symbol && i =~ /^(\$|€|£)/
44
44
  case i
45
- when /^$/ then "USD"
45
+ when /^\$/ then "USD"
46
46
  when /^€/ then "EUR"
47
47
  when /^£/ then "GBP"
48
48
  end
@@ -1,7 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  Gem::Specification.new do |s|
3
3
  s.name = "money"
4
- s.version = "5.1.0.beta1"
4
+ s.version = "5.1.0"
5
5
  s.platform = Gem::Platform::RUBY
6
6
  s.authors = ["Tobias Luetke", "Hongli Lai", "Jeremy McNevin",
7
7
  "Shane Emmons", "Simone Carletti"]
@@ -0,0 +1,84 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Money::Currency::Heuristics do
5
+ describe "#analyze_string" do
6
+ let(:it) { Money::Currency }
7
+
8
+ it "is not affected by blank characters and numbers" do
9
+ it.analyze('123').should == []
10
+ it.analyze('\n123 \t').should == []
11
+ end
12
+
13
+ it "returns nothing when given nothing" do
14
+ it.analyze('').should == []
15
+ it.analyze(nil).should == []
16
+ end
17
+
18
+ it "finds a currency by use of its symbol" do
19
+ it.analyze('zł').should == ['PLN']
20
+ end
21
+
22
+ it "is not affected by trailing dot" do
23
+ it.analyze('zł.').should == ['PLN']
24
+ end
25
+
26
+ it "finds match even if has numbers after" do
27
+ it.analyze('zł 123').should == ['PLN']
28
+ end
29
+
30
+ it "finds match even if has numbers before" do
31
+ it.analyze('123 zł').should == ['PLN']
32
+ end
33
+
34
+ it "find match even if symbol is next to number" do
35
+ it.analyze('300zł').should == ['PLN']
36
+ end
37
+
38
+ it "finds match even if has numbers with delimiters" do
39
+ it.analyze('zł 123,000.50').should == ['PLN']
40
+ it.analyze('zł123,000.50').should == ['PLN']
41
+ end
42
+
43
+ it "finds currencies with dots in symbols" do
44
+ it.analyze('L.E.').should == ['EGP']
45
+ end
46
+
47
+ it "finds by name" do
48
+ it.analyze('1900 bulgarian lev').should == ['BGN']
49
+ it.analyze('Swedish Krona').should == ['SEK']
50
+ end
51
+
52
+ it "Finds several currencies when several match" do
53
+ r = it.analyze('$400')
54
+ r.should include("ARS")
55
+ r.should include("USD")
56
+ r.should include("NZD")
57
+
58
+ r = it.analyze('9000 £')
59
+ r.should include("GBP")
60
+ r.should include("SHP")
61
+ r.should include("SYP")
62
+ end
63
+
64
+ it "should use alternate symbols" do
65
+ it.analyze('US$').should == ['USD']
66
+ end
67
+
68
+ it "finds a currency by use of its iso code" do
69
+ it.analyze('USD 200').should == ['USD']
70
+ end
71
+
72
+ it "finds currencies in the middle of a sentence!" do
73
+ it.analyze('It would be nice to have 10000 Albanian lek by tomorrow!').should == ['ALL']
74
+ end
75
+
76
+ it "finds several currencies in the same text!" do
77
+ it.analyze("10EUR is less than 100:- but really, I want US$1").should == ['EUR', 'SEK', 'USD']
78
+ end
79
+
80
+ it "should function with unicode characters" do
81
+ it.analyze("10 դր.").should == ["AMD"]
82
+ end
83
+ end
84
+ end
@@ -124,7 +124,7 @@ describe Money, "formatting" do
124
124
  one_thousand["EUR"].should == "€1.000,00"
125
125
 
126
126
  # Rupees
127
- one_thousand["INR"].should == "1,000.00"
127
+ one_thousand["INR"].should == "1,000.00"
128
128
  one_thousand["NPR"].should == "₨1,000.00"
129
129
  one_thousand["SCR"].should == "1,000.00 ₨"
130
130
  one_thousand["LKR"].should == "1,000.00 ₨"
@@ -223,7 +223,7 @@ describe Money, "formatting" do
223
223
  one["EUR"].should == "€1,00"
224
224
 
225
225
  # Rupees
226
- one["INR"].should == "1.00"
226
+ one["INR"].should == "1.00"
227
227
  one["NPR"].should == "₨1.00"
228
228
  one["SCR"].should == "1.00 ₨"
229
229
  one["LKR"].should == "1.00 ₨"
@@ -361,64 +361,10 @@ describe Money, "formatting" do
361
361
  end
362
362
  end
363
363
 
364
- it "brute forces :subunit_to_unit = 1" do
365
- ("0".."9").each do |amt|
366
- amt.to_money("VUV").format(:symbol => false).should == amt
367
- end
368
- ("-1".."-9").each do |amt|
369
- amt.to_money("VUV").format(:symbol => false).should == amt
370
- end
371
- "1000".to_money("VUV").format(:symbol => false).should == "1,000"
372
- "-1000".to_money("VUV").format(:symbol => false).should == "-1,000"
373
- end
374
-
375
- it "brute forces :subunit_to_unit = 5" do
376
- ("0.0".."9.4").each do |amt|
377
- next if amt[-1].to_i > 4
378
- amt.to_money("MGA").format(:symbol => false).should == amt
379
- end
380
- ("-0.1".."-9.4").each do |amt|
381
- next if amt[-1].to_i > 4
382
- amt.to_money("MGA").format(:symbol => false).should == amt
383
- end
384
- "1000.0".to_money("MGA").format(:symbol => false).should == "1,000.0"
385
- "-1000.0".to_money("MGA").format(:symbol => false).should == "-1,000.0"
386
- end
387
-
388
- it "brute forces :subunit_to_unit = 10" do
389
- ("0.0".."9.9").each do |amt|
390
- amt.to_money("VND").format(:symbol => false).should == amt.to_s.gsub(/\./, ",")
391
- end
392
- ("-0.1".."-9.9").each do |amt|
393
- amt.to_money("VND").format(:symbol => false).should == amt.to_s.gsub(/\./, ",")
394
- end
395
- "1000.0".to_money("VND").format(:symbol => false).should == "1.000,0"
396
- "-1000.0".to_money("VND").format(:symbol => false).should == "-1.000,0"
364
+ it "maintains floating point precision" do
365
+ "0.01".to_money("USD").format(:symbol => false).should == "0.01"
397
366
  end
398
367
 
399
- it "brute forces :subunit_to_unit = 100" do
400
- %w{0.00 0.01 1.00 1.01 5.67 9.99}.each do |amt|
401
- amt.to_money("USD").format(:symbol => false).should == amt
402
- end
403
- %w{-0.01 -1.00 -1.01 -5.67 -9.99}.each do |amt|
404
- amt.to_money("USD").format(:symbol => false).should == amt
405
- end
406
- "1000.00".to_money("USD").format(:symbol => false).should == "1,000.00"
407
- "-1000.00".to_money("USD").format(:symbol => false).should == "-1,000.00"
408
- end
409
-
410
- it "brute forces :subunit_to_unit = 1000" do
411
- %w{0.000 0.001 1.000 1.001 5.678 9.999}.each do |amt|
412
- amt.to_money("IQD").format(:symbol => false).should == amt
413
- end
414
-
415
- %w{-0.001 -1.000 -1.001 -5.678 -9.999}.each do |amt|
416
- amt.to_money("IQD").format(:symbol => false).should == amt
417
- end
418
-
419
- "1000.000".to_money("IQD").format(:symbol => false).should == "1,000.000"
420
- "-1000.000".to_money("IQD").format(:symbol => false).should == "-1,000.000"
421
- end
422
368
  end
423
369
 
424
370
  context "custom currencies with 4 decimal places" do
@@ -27,8 +27,10 @@ describe Money, "parsing" do
27
27
  before do
28
28
  Money.assume_from_symbol = true
29
29
  end
30
- it "parses formatted inputs with the currency passed as a symbol" do
31
- Money.parse("$5.95").should == Money.new(595, 'USD')
30
+ it "parses formatted inputs with the currency passed as a symbol" do
31
+ with_default_currency("EUR") do
32
+ Money.parse("$5.95").should == Money.new(595, 'USD')
33
+ end
32
34
  Money.parse("€5.95").should == Money.new(595, 'EUR')
33
35
  Money.parse(" €5.95 ").should == Money.new(595, 'EUR')
34
36
  Money.parse("£9.99").should == Money.new(999, 'GBP')
@@ -1,8 +1,11 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__)
1
2
  require 'rspec'
2
3
  require 'money'
4
+ require 'support/default_currency_helper'
3
5
 
4
6
  RSpec.configure do |c|
5
7
  c.order = "rand"
8
+ c.include DefaultCurrencyHelper
6
9
  end
7
10
 
8
11
  def silence_warnings