money 6.0.1.beta2 → 6.0.1.beta3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,149 +1,153 @@
1
1
  # encoding: utf-8
2
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
3
+ class Money
4
+ class Currency
5
+ module Heuristics
6
+
7
+ # An robust and efficient algorithm for finding currencies in
8
+ # text. Using several algorithms it can find symbols, iso codes and
9
+ # even names of currencies.
10
+ # Although not recommendable, it can also attempt to find the given
11
+ # currency in an entire sentence
12
+ #
13
+ # Returns: Array (matched results)
14
+ def analyze(str)
15
+ return Analyzer.new(str, search_tree).process
16
+ end
15
17
 
16
- private
18
+ private
17
19
 
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
20
+ # Build a search tree from the currency database
21
+ def search_tree
22
+ @_search_tree ||= {
23
+ :by_symbol => currencies_by_symbol,
24
+ :by_iso_code => currencies_by_iso_code,
25
+ :by_name => currencies_by_name
26
+ }
27
+ end
26
28
 
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
29
+ def currencies_by_symbol
30
+ {}.tap do |r|
31
+ table.each do |dummy, c|
32
+ symbol = (c[:symbol]||"").downcase
33
+ symbol.chomp!('.')
34
+ (r[symbol] ||= []) << c
35
+
36
+ (c[:alternate_symbols]||[]).each do |ac|
37
+ ac = ac.downcase
38
+ ac.chomp!('.')
39
+ (r[ac] ||= []) << c
40
+ end
41
+ end
38
42
  end
39
43
  end
40
- end
41
- end
42
44
 
43
- def currencies_by_iso_code
44
- {}.tap do |r|
45
- table.each do |dummy,c|
46
- (r[c[:iso_code].downcase] ||= []) << c
45
+ def currencies_by_iso_code
46
+ {}.tap do |r|
47
+ table.each do |dummy,c|
48
+ (r[c[:iso_code].downcase] ||= []) << c
49
+ end
50
+ end
47
51
  end
48
- end
49
- end
50
52
 
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!('.')}
53
+ def currencies_by_name
54
+ {}.tap do |r|
55
+ table.each do |dummy,c|
56
+ name_parts = c[:name].downcase.split
57
+ name_parts.each {|part| part.chomp!('.')}
56
58
 
57
- # construct one branch per word
58
- root = r
59
- while name_part = name_parts.shift
60
- root = (root[name_part] ||= {})
61
- end
59
+ # construct one branch per word
60
+ root = r
61
+ while name_part = name_parts.shift
62
+ root = (root[name_part] ||= {})
63
+ end
62
64
 
63
- # the leaf is a currency
64
- (root[:value] ||= []) << c
65
+ # the leaf is a currency
66
+ (root[:value] ||= []) << c
67
+ end
68
+ end
65
69
  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
70
 
79
- def process
80
- format
81
- return [] if str.empty?
71
+ class Analyzer
72
+ attr_reader :search_tree, :words
73
+ attr_accessor :str, :currencies
82
74
 
83
- search_by_symbol
84
- search_by_iso_code
85
- search_by_name
75
+ def initialize str, search_tree
76
+ @str = (str||'').dup
77
+ @search_tree = search_tree
78
+ @currencies = []
79
+ end
86
80
 
87
- prepare_reply
88
- end
81
+ def process
82
+ format
83
+ return [] if str.empty?
89
84
 
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
85
+ search_by_symbol
86
+ search_by_iso_code
87
+ search_by_name
98
88
 
99
- def search_by_symbol
100
- words.each do |word|
101
- if found = search_tree[:by_symbol][word]
102
- currencies.concat(found)
89
+ prepare_reply
103
90
  end
104
- end
105
- end
106
91
 
107
- def search_by_iso_code
108
- words.each do |word|
109
- if found = search_tree[:by_iso_code][word]
110
- currencies.concat(found)
92
+ def format
93
+ str.gsub!(/[\r\n\t]/,'')
94
+ str.gsub!(/[0-9][\.,:0-9]*[0-9]/,'')
95
+ str.gsub!(/[0-9]/, '')
96
+ str.downcase!
97
+ @words = str.split
98
+ @words.each {|word| word.chomp!('.'); word.chomp!(',') }
111
99
  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
100
 
121
- search_words = words.dup
122
-
123
- while search_words.length > 0
124
- root = search_tree[:by_name]
101
+ def search_by_symbol
102
+ words.each do |word|
103
+ if found = search_tree[:by_symbol][word]
104
+ currencies.concat(found)
105
+ end
106
+ end
107
+ end
125
108
 
126
- search_words.each do |word|
127
- if root = root[word]
128
- if root[:value]
129
- currencies.concat(root[:value])
109
+ def search_by_iso_code
110
+ words.each do |word|
111
+ if found = search_tree[:by_iso_code][word]
112
+ currencies.concat(found)
130
113
  end
131
- else
132
- break
133
114
  end
134
115
  end
135
116
 
136
- search_words.delete_at(0)
137
- end
138
- end
117
+ def search_by_name
118
+ # remember, the search tree by name is a construct of branches and leaf!
119
+ # We need to try every combination of words within the sentence, so we
120
+ # end up with a x^2 equation, which should be fine as most names are either
121
+ # one or two words, and this is multiplied with the words of given sentence
122
+
123
+ search_words = words.dup
124
+
125
+ while search_words.length > 0
126
+ root = search_tree[:by_name]
127
+
128
+ search_words.each do |word|
129
+ if root = root[word]
130
+ if root[:value]
131
+ currencies.concat(root[:value])
132
+ end
133
+ else
134
+ break
135
+ end
136
+ end
139
137
 
140
- def prepare_reply
141
- codes = currencies.map do |currency|
142
- currency[:iso_code]
138
+ search_words.delete_at(0)
139
+ end
140
+ end
141
+
142
+ def prepare_reply
143
+ codes = currencies.map do |currency|
144
+ currency[:iso_code]
145
+ end
146
+ codes.uniq!
147
+ codes.sort!
148
+ codes
149
+ end
143
150
  end
144
- codes.uniq!
145
- codes.sort!
146
- codes
147
151
  end
148
152
  end
149
153
  end
@@ -1,22 +1,24 @@
1
- module Money::Currency::Loader
2
- extend self
1
+ class Money
2
+ class Currency
3
+ module Loader
4
+ DATA_PATH = File.expand_path("../../../../config", __FILE__)
3
5
 
4
- DATA_PATH = File.expand_path("../../../../config", __FILE__)
6
+ # Loads and returns the currencies stored in JSON files in the config directory.
7
+ #
8
+ # @return [Hash]
9
+ def load_currencies
10
+ currencies = parse_currency_file("currency_iso.json")
11
+ currencies.merge! parse_currency_file("currency_non_iso.json")
12
+ currencies.merge! parse_currency_file("currency_backwards_compatible.json")
13
+ end
5
14
 
6
- # Loads and returns the currencies stored in JSON files in the config directory.
7
- #
8
- # @return [Hash]
9
- def load_currencies
10
- currencies = parse_currency_file("currency_iso.json")
11
- currencies.merge! parse_currency_file("currency_non_iso.json")
12
- currencies.merge! parse_currency_file("currency_backwards_compatible.json")
13
- end
14
-
15
- private
15
+ private
16
16
 
17
- def parse_currency_file(filename)
18
- json = File.read("#{DATA_PATH}/#{filename}")
19
- json.force_encoding(::Encoding::UTF_8) if defined?(::Encoding)
20
- JSON.parse(json, :symbolize_names => true)
17
+ def parse_currency_file(filename)
18
+ json = File.read("#{DATA_PATH}/#{filename}")
19
+ json.force_encoding(::Encoding::UTF_8) if defined?(::Encoding)
20
+ JSON.parse(json, :symbolize_names => true)
21
+ end
22
+ end
21
23
  end
22
24
  end
@@ -78,56 +78,36 @@ class Money
78
78
  end
79
79
  private :as_d
80
80
 
81
- # The currency the money is in.
82
- #
83
- # @return [Currency]
84
- attr_reader :currency
85
-
86
- # The +Money::Bank+ based object used to perform currency exchanges with.
87
- #
88
- # @return [Money::Bank::*]
89
- attr_reader :bank
81
+ # @attr_reader [Currency] currency The currency the money is in.
82
+ # @attr_reader [Money::Bank::*] bank The +Money::Bank+ based object used to
83
+ # perform currency exchanges with.
84
+ attr_reader :currency, :bank
90
85
 
91
86
  # Class Methods
92
87
  class << self
93
- # Each Money object is associated to a bank object, which is responsible
94
- # for currency exchange. This property allows you to specify the default
95
- # bank object. The default value for this property is an instance of
96
- # +Bank::VariableExchange.+ It allows one to specify custom exchange rates.
97
- #
98
- # @return [Money::Bank::*]
99
- attr_accessor :default_bank
100
-
101
- # The default currency, which is used when +Money.new+ is called without an
102
- # explicit currency argument. The default value is Currency.new("USD"). The
103
- # value must be a valid +Money::Currency+ instance.
104
- #
105
- # @return [Money::Currency]
106
- attr_accessor :default_currency
107
-
108
- # Use this to disable i18n even if it's used by other objects in your app.
109
- #
110
- # @return [true,false]
111
- attr_accessor :use_i18n
112
-
113
- # Use this to enable the ability to assume the currency from a passed symbol
114
- #
115
- # @return [true,false]
116
- attr_accessor :assume_from_symbol
117
-
118
- # Use this to enable infinite precision cents
119
- #
120
- # @return [true,false]
121
- attr_accessor :infinite_precision
122
-
123
- # Use this to specify the rounding mode
88
+ # @attr_accessor [Money::Bank::*] default_bank Each Money object is
89
+ # associated to a bank object, which is responsible for currency exchange.
90
+ # This property allows you to specify the default bank object. The default
91
+ # value for this property is an instance of +Bank::VariableExchange.+ It
92
+ # allows one to specify custom exchange rates.
93
+ # @attr_accessor [Money::Currency] default_currency The default currency,
94
+ # which is used when +Money.new+ is called without an explicit currency
95
+ # argument. The default value is Currency.new("USD"). The value must be a
96
+ # valid +Money::Currency+ instance.
97
+ # @attr_accessor [true, false] use_i18n Use this to disable i18n even if
98
+ # it's used by other objects in your app.
99
+ # @attr_accessor [true, false] assume_from_symbol Use this to enable the
100
+ # ability to assume the currency from a passed symbol
101
+ # @attr_accessor [true, false] infinite_precision Use this to enable
102
+ # infinite precision cents
103
+ # @attr_accessor [Integer] conversion_precision Use this to specify
104
+ # precision for converting Rational to BigDecimal
105
+ attr_accessor :default_bank, :default_currency, :use_i18n,
106
+ :assume_from_symbol, :infinite_precision, :conversion_precision
107
+
108
+ # @attr_writer rounding_mode Use this to specify the rounding mode
124
109
  attr_writer :rounding_mode
125
110
 
126
- # Use this to specify precision for converting Rational to BigDecimal
127
- #
128
- # @return [Integer]
129
- attr_accessor :conversion_precision
130
-
131
111
  # Create a new money object with value 0.
132
112
  #
133
113
  # @param [Currency, String, Symbol] currency The currency to use.
@@ -185,10 +165,12 @@ class Money
185
165
  # Money.new(1200) * BigDecimal.new('0.029')
186
166
  # end
187
167
  def self.rounding_mode(mode=nil)
188
- return Thread.current[:money_rounding_mode] || @rounding_mode if mode.nil?
189
-
190
- Thread.current[:money_rounding_mode] = mode
191
- yield
168
+ if mode.nil?
169
+ Thread.current[:money_rounding_mode] || @rounding_mode
170
+ else
171
+ Thread.current[:money_rounding_mode] = mode
172
+ yield
173
+ end
192
174
  ensure
193
175
  Thread.current[:money_rounding_mode] = nil
194
176
  end
@@ -258,10 +240,9 @@ class Money
258
240
  # @see Money.new
259
241
  #
260
242
  def self.new_with_amount(amount, currency = Money.default_currency, bank = Money.default_bank)
261
- money = from_numeric(amount, currency)
262
- # Hack! You can't change a bank
263
- money.instance_variable_set("@bank", bank)
264
- money
243
+ from_numeric(amount, currency).tap do |money|
244
+ money.instance_variable_set("@bank", bank) # Hack! You can't change a bank
245
+ end
265
246
  end
266
247
 
267
248
  # Synonym of #new_with_amount
@@ -310,17 +291,14 @@ class Money
310
291
  #
311
292
  # @see Money.new_with_dollars
312
293
  #
313
- def initialize(fractional, currency = Money.default_currency, bank = Money.default_bank)
314
- if (fractional.is_a? Money)
315
- money = fractional
316
- @fractional = money.fractional
317
- @currency = money.currency
318
- @bank = money.bank
319
- else
320
- @fractional = as_d(fractional)
321
- @currency = Currency.wrap(currency)
322
- @bank = bank
323
- end
294
+ def initialize(obj, currency = Money.default_currency, bank = Money.default_bank)
295
+ @fractional = obj.fractional
296
+ @currency = obj.currency
297
+ @bank = obj.bank
298
+ rescue NoMethodError
299
+ @fractional = as_d(obj)
300
+ @currency = Currency.wrap(currency)
301
+ @bank = bank
324
302
  end
325
303
 
326
304
  # Assuming using a currency using dollars:
@@ -479,7 +457,7 @@ class Money
479
457
  #
480
458
  # @return [self]
481
459
  def to_money(given_currency = nil)
482
- given_currency = Currency.wrap(given_currency) if given_currency
460
+ given_currency = Currency.wrap(given_currency)
483
461
  if given_currency.nil? || self.currency == given_currency
484
462
  self
485
463
  else
@@ -551,30 +529,42 @@ class Money
551
529
  # Money.new(100, "USD").allocate([0.33, 0.33, 0.33]) #=> [Money.new(34), Money.new(33), Money.new(33)]
552
530
  #
553
531
  def allocate(splits)
554
- allocations = splits.inject(0) { |sum, n| sum + as_d(n) }
532
+ allocations = allocations_from_splits(splits)
555
533
 
556
534
  if (allocations - BigDecimal("1")) > Float::EPSILON
557
535
  raise ArgumentError, "splits add to more then 100%"
558
536
  end
559
537
 
538
+ amounts, left_over = amounts_from_splits(allocations, splits)
539
+
540
+ unless self.class.infinite_precision
541
+ left_over.to_i.times { |i| amounts[i % amounts.length] += 1 }
542
+ end
543
+
544
+ amounts.collect { |fractional| Money.new(fractional, currency) }
545
+ end
546
+
547
+ def allocations_from_splits(splits)
548
+ splits.inject(0) { |sum, n| sum + as_d(n) }
549
+ end
550
+ private :allocations_from_splits
551
+
552
+ def amounts_from_splits(allocations, splits)
560
553
  left_over = fractional
561
554
 
562
555
  amounts = splits.map do |ratio|
563
556
  if self.class.infinite_precision
564
- fraction = fractional * ratio
557
+ fractional * ratio
565
558
  else
566
- fraction = (fractional * ratio / allocations).floor
567
- left_over -= fraction
568
- fraction
559
+ (fractional * ratio / allocations).floor.tap do |frac|
560
+ left_over -= frac
561
+ end
569
562
  end
570
563
  end
571
564
 
572
- unless self.class.infinite_precision
573
- left_over.to_i.times { |i| amounts[i % amounts.length] += 1 }
574
- end
575
-
576
- amounts.collect { |fractional| Money.new(fractional, currency) }
565
+ [amounts, left_over]
577
566
  end
567
+ private :amounts_from_splits
578
568
 
579
569
  # Split money amongst parties evenly without loosing pennies.
580
570
  #
@@ -588,22 +578,29 @@ class Money
588
578
  raise ArgumentError, "need at least one party" if num < 1
589
579
 
590
580
  if self.class.infinite_precision
591
- amt = div(as_d(num))
592
- return 1.upto(num).map{amt}
581
+ split_infinite(num)
582
+ else
583
+ split_flat(num)
593
584
  end
585
+ end
594
586
 
587
+ def split_infinite(num)
588
+ amt = div(as_d(num))
589
+ 1.upto(num).map{amt}
590
+ end
591
+ private :split_infinite
592
+
593
+ def split_flat(num)
595
594
  low = Money.new(fractional / num, currency)
596
595
  high = Money.new(low.fractional + 1, currency)
597
596
 
598
597
  remainder = fractional % num
599
- result = []
600
598
 
601
- num.times do |index|
602
- result[index] = index < remainder ? high : low
599
+ Array.new(num).each_with_index.map do |_, index|
600
+ index < remainder ? high : low
603
601
  end
604
-
605
- result
606
602
  end
603
+ private :split_flat
607
604
 
608
605
  # Round the monetary amount to smallest unit of coinage.
609
606
  #
@@ -622,10 +619,9 @@ class Money
622
619
  #
623
620
  def round(rounding_mode = self.class.rounding_mode)
624
621
  if self.class.infinite_precision
625
- return Money.new(fractional.round(0, rounding_mode), self.currency)
622
+ Money.new(fractional.round(0, rounding_mode), self.currency)
626
623
  else
627
- return self
624
+ self
628
625
  end
629
626
  end
630
-
631
627
  end