money2 7.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,150 @@
1
+ # encoding: utf-8
2
+ require 'money/unaccent'
3
+
4
+ class Money
5
+ class Currency
6
+ # Using this module requires `sixarm_ruby_unaccent` gem.
7
+ # Add it to your Gemfile manually.
8
+ module Heuristics
9
+ module_function
10
+
11
+ # An robust and efficient algorithm for finding currencies in
12
+ # text. Using several algorithms it can find symbols, iso codes and
13
+ # even names of currencies.
14
+ # Although not recommendable, it can also attempt to find the given
15
+ # currency in an entire sentence
16
+ #
17
+ # Returns: Array (matched results)
18
+ def analyze(str, klass)
19
+ Analyzer.new(str, SearchTree.cache[klass]).process
20
+ end
21
+
22
+ class SearchTree
23
+ class << self
24
+ def cache
25
+ @cache ||= Hash.new { |h, k| h[k] = new(k) }
26
+ end
27
+ end
28
+
29
+ attr_reader :currency_class
30
+
31
+ def initialize(currency_class)
32
+ @currency_class = currency_class
33
+ end
34
+
35
+ def table
36
+ currency_class.table
37
+ end
38
+
39
+ def by_symbol
40
+ @by_symbol ||= table.each_with_object({}) do |(_, c), r|
41
+ symbol = (c[:symbol]||"").downcase
42
+ symbol.chomp!('.')
43
+ (r[symbol] ||= []) << c
44
+
45
+ (c[:alternate_symbols]||[]).each do |ac|
46
+ ac = ac.downcase
47
+ ac.chomp!('.')
48
+ (r[ac] ||= []) << c
49
+ end
50
+ end
51
+ end
52
+
53
+ def by_code
54
+ @by_code ||= table.each_with_object({}) do |(k, c), r|
55
+ (r[k.downcase] ||= []) << c
56
+ end
57
+ end
58
+
59
+ def by_name
60
+ @by_name ||= table.each_with_object({}) do |(_, c), r|
61
+ name_parts = c[:name].unaccent.downcase.split
62
+ name_parts.each {|part| part.chomp!('.')}
63
+
64
+ # construct one branch per word
65
+ root = r
66
+ while name_part = name_parts.shift
67
+ root = (root[name_part] ||= {})
68
+ end
69
+
70
+ # the leaf is a currency
71
+ (root[:value] ||= []) << c
72
+ end
73
+ end
74
+ end
75
+
76
+ class Analyzer
77
+ attr_reader :search_tree, :words
78
+ attr_accessor :str, :currencies
79
+
80
+ def initialize(str, search_tree)
81
+ @str = (str || '').dup
82
+ @search_tree = search_tree
83
+ end
84
+
85
+ def process
86
+ format
87
+ return [] if str.empty?
88
+
89
+ @currencies = []
90
+ search_by_symbol
91
+ search_by_code
92
+ search_by_name
93
+
94
+ currencies.map { |x| x[:code] }.tap(&:uniq!).tap(&:sort!)
95
+ end
96
+
97
+ def format
98
+ str.gsub!(/[\r\n\t]/,'')
99
+ str.gsub!(/[0-9][\.,:0-9]*[0-9]/,'')
100
+ str.gsub!(/[0-9]/, '')
101
+ str.downcase!
102
+ @words = str.unaccent.split
103
+ @words.each {|word| word.chomp!('.'); word.chomp!(',') }
104
+ end
105
+
106
+ def search_by_symbol
107
+ words.each do |word|
108
+ if found = search_tree.by_symbol[word]
109
+ currencies.concat(found)
110
+ end
111
+ end
112
+ end
113
+
114
+ def search_by_code
115
+ words.each do |word|
116
+ if found = search_tree.by_code[word]
117
+ currencies.concat(found)
118
+ end
119
+ end
120
+ end
121
+
122
+ def search_by_name
123
+ # remember, the search tree by name is a construct of branches and leaf!
124
+ # We need to try every combination of words within the sentence, so we
125
+ # end up with a x^2 equation, which should be fine as most names are either
126
+ # one or two words, and this is multiplied with the words of given sentence
127
+
128
+ search_words = words.dup
129
+
130
+ while search_words.length > 0
131
+ root = search_tree.by_name
132
+
133
+ search_words.each do |word|
134
+ if root = root[word]
135
+ if root[:value]
136
+ currencies.concat(root[:value])
137
+ end
138
+ else
139
+ break
140
+ end
141
+ end
142
+
143
+ search_words.delete_at(0)
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+
@@ -0,0 +1,29 @@
1
+ class Money
2
+ class Currency
3
+ module Loader
4
+ DATA_PATH = File.expand_path("../../../../config", __FILE__).freeze
5
+ FILES = %w(
6
+ currency_iso.json
7
+ currency_non_iso.json
8
+ currency_backwards_compatible.json
9
+ )
10
+
11
+ extend self
12
+
13
+ # Loads and returns the currencies stored in JSON files in the config directory.
14
+ #
15
+ # @return [Hash]
16
+ def load_all
17
+ FILES.inject({}) { |acc, x| acc.merge!(load(x)) }
18
+ end
19
+
20
+ def load(filename)
21
+ json = File.read("#{DATA_PATH}/#{filename}")
22
+ json.force_encoding(::Encoding::UTF_8) if defined?(::Encoding)
23
+ JSON.parse(json, symbolize_names: true).each_with_object({}) do |x, acc|
24
+ acc[x[:code]] = x
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,139 @@
1
+ class Money
2
+ module CurrencyMethods
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ # Creates a new Money object of the given value, using the Canadian
9
+ # dollar currency.
10
+ #
11
+ # @param [Integer] cents The cents value.
12
+ #
13
+ # @return [Money]
14
+ #
15
+ # @example
16
+ # n = Money.ca_dollar(100)
17
+ # n.cents #=> 100
18
+ # n.currency #=> #<Money::Currency id: cad>
19
+ def ca_dollar(cents)
20
+ new(cents, "CAD")
21
+ end
22
+ alias_method :cad, :ca_dollar
23
+
24
+
25
+ # Creates a new Money object of the given value, using the American dollar
26
+ # currency.
27
+ #
28
+ # @param [Integer] cents The cents value.
29
+ #
30
+ # @return [Money]
31
+ #
32
+ # @example
33
+ # n = Money.us_dollar(100)
34
+ # n.cents #=> 100
35
+ # n.currency #=> #<Money::Currency id: usd>
36
+ def us_dollar(cents)
37
+ new(cents, "USD")
38
+ end
39
+ alias_method :usd, :us_dollar
40
+
41
+
42
+ # Creates a new Money object of the given value, using the Euro currency.
43
+ #
44
+ # @param [Integer] cents The cents value.
45
+ #
46
+ # @return [Money]
47
+ #
48
+ # @example
49
+ # n = Money.euro(100)
50
+ # n.cents #=> 100
51
+ # n.currency #=> #<Money::Currency id: eur>
52
+ def euro(cents)
53
+ new(cents, "EUR")
54
+ end
55
+ alias_method :eur, :euro
56
+
57
+
58
+ # Creates a new Money object of the given value, in British pounds.
59
+ #
60
+ # @param [Integer] pence The pence value.
61
+ #
62
+ # @return [Money]
63
+ #
64
+ # @example
65
+ # n = Money.pound_sterling(100)
66
+ # n.fractional #=> 100
67
+ # n.currency #=> #<Money::Currency id: gbp>
68
+ def pound_sterling(pence)
69
+ new(pence, "GBP")
70
+ end
71
+ alias_method :gbp, :pound_sterling
72
+ end
73
+
74
+ # Assuming using a currency using dollars:
75
+ # Returns the value of the money in dollars,
76
+ # instead of in the fractional unit cents.
77
+ #
78
+ # Synonym of #amount
79
+ #
80
+ # @return [BigDecimal]
81
+ #
82
+ # @example
83
+ # Money.new(1_00, "USD").dollars # => BigDecimal.new("1.00")
84
+ #
85
+ # @see #amount
86
+ # @see #to_d
87
+ # @see #cents
88
+ #
89
+ def dollars
90
+ amount
91
+ end
92
+
93
+ # Convenience method for fractional part of the amount. Synonym of #fractional
94
+ #
95
+ # @return [Integer] when infinite_precision is false
96
+ # @return [BigDecimal] when infinite_precision is true
97
+ #
98
+ # @see infinite_precision
99
+ def cents
100
+ fractional
101
+ end
102
+
103
+ # Receive a money object with the same amount as the current Money object
104
+ # in United States dollar.
105
+ #
106
+ # @return [Money]
107
+ #
108
+ # @example
109
+ # n = Money.new(100, "CAD").as_us_dollar
110
+ # n.currency #=> #<Money::Currency id: usd>
111
+ def as_us_dollar
112
+ exchange_to("USD")
113
+ end
114
+
115
+ # Receive a money object with the same amount as the current Money object
116
+ # in Canadian dollar.
117
+ #
118
+ # @return [Money]
119
+ #
120
+ # @example
121
+ # n = Money.new(100, "USD").as_ca_dollar
122
+ # n.currency #=> #<Money::Currency id: cad>
123
+ def as_ca_dollar
124
+ exchange_to("CAD")
125
+ end
126
+
127
+ # Receive a money object with the same amount as the current Money object
128
+ # in euro.
129
+ #
130
+ # @return [Money]
131
+ #
132
+ # @example
133
+ # n = Money.new(100, "USD").as_euro
134
+ # n.currency #=> #<Money::Currency id: eur>
135
+ def as_euro
136
+ exchange_to("EUR")
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,404 @@
1
+ # encoding: UTF-8
2
+ class Money
3
+ class Formatter
4
+ extend ClassAttribute
5
+
6
+ # @!attribute [rw] use_i18n
7
+ # @return [Boolean] Use this to disable i18n even if it's used by other
8
+ # objects in your app.
9
+ class_attribute :use_i18n
10
+ self.use_i18n = true
11
+
12
+ # @!attribute default_rules
13
+ # @return [Hash] Use this to define a default hash of rules for every time
14
+ # +Money#format+ is called. Rules provided on method call will be
15
+ # merged with the default ones. To overwrite a rule, just provide the
16
+ # intended value while calling +format+.
17
+ #
18
+ # @see +#format+ for more details.
19
+ #
20
+ # @example
21
+ # Money.formatter.default_rules = { :display_free => true }
22
+ # Money.new(0, "USD").format # => "free"
23
+ # Money.new(0, "USD").format(:display_free => false) # => "$0.00"
24
+ class_attribute :default_rules
25
+ self.default_rules = {}
26
+
27
+ class << self
28
+ def format(*args)
29
+ new(*args).format
30
+ end
31
+
32
+ def decimal_str(money, decimal_places = money.currency.decimal_places)
33
+ str = money.to_d.to_s('F')
34
+ units, fractional = str.split('.')
35
+ if decimal_places == 0 && fractional == '0'
36
+ units
37
+ else
38
+ pad = decimal_places - fractional.size
39
+ str << '0' * pad if pad > 0
40
+ str
41
+ end
42
+ end
43
+
44
+ # Splits string into chunks fullfilling the rightmost first:
45
+ #
46
+ # rsplit_str_by('12345') # => ['12', '345']
47
+ def rsplit_str_by(str, count)
48
+ size = str.size
49
+ i = size % count
50
+ parts = i > 0 ? [str.slice(0, i)] : []
51
+ while i < size
52
+ parts << str.slice(i, count)
53
+ i += count
54
+ end
55
+ parts
56
+ end
57
+ end
58
+
59
+ attr_reader :money, :currency, :rules
60
+
61
+ def initialize(money, **rules)
62
+ @money = money
63
+ @currency = money.currency
64
+ @rules = rules
65
+ end
66
+
67
+ # Creates a formatted price string according to several rules.
68
+ #
69
+ # @param [Hash] rules The options used to format the string.
70
+ #
71
+ # @return [String]
72
+ #
73
+ # @option rules [Boolean, String] :display_free (false) Whether a zero
74
+ # amount of money should be formatted of "free" or as the supplied string.
75
+ #
76
+ # @example
77
+ # Money.us_dollar(0).format(:display_free => true) #=> "free"
78
+ # Money.us_dollar(0).format(:display_free => "gratis") #=> "gratis"
79
+ # Money.us_dollar(0).format #=> "$0.00"
80
+ #
81
+ # @option rules [Boolean] :with_currency (false) Whether the currency name
82
+ # should be appended to the result string.
83
+ #
84
+ # @example
85
+ # Money.ca_dollar(100).format #=> "$1.00"
86
+ # Money.ca_dollar(100).format(:with_currency => true) #=> "$1.00 CAD"
87
+ # Money.us_dollar(85).format(:with_currency => true) #=> "$0.85 USD"
88
+ #
89
+ # @option rules [Boolean, Integer] :round (false) Force rounding.
90
+ # Specify number of digits after decimal point. When true is given
91
+ # it uses currency's default decimal places count.
92
+ #
93
+ # @example
94
+ # Money.us_dollar(100.1).format # => "$1.001"
95
+ # Money.us_dollar(100.1).format(round: true) # => "$1"
96
+ # Money.us_dollar(100.9).format(round: true) # => "$1.01"
97
+ # Money.us_dollar(100.9).format(round: 1) # => "$1.00"
98
+ #
99
+ # @option rules [Boolean] :no_cents (false) Whether cents should be omitted.
100
+ #
101
+ # @example
102
+ # Money.ca_dollar(100).format(:no_cents => true) #=> "$1"
103
+ # Money.ca_dollar(599).format(:no_cents => true) #=> "$5"
104
+ #
105
+ # @option rules [Boolean] :no_cents_if_whole (false) Whether cents should be
106
+ # omitted if the cent value is zero
107
+ #
108
+ # @example
109
+ # Money.ca_dollar(10000).format(:no_cents_if_whole => true) #=> "$100"
110
+ # Money.ca_dollar(10034).format(:no_cents_if_whole => true) #=> "$100.34"
111
+ #
112
+ # @option rules [Boolean, String, nil] :symbol (true) Whether a money symbol
113
+ # should be prepended to the result string. The default is true. This method
114
+ # attempts to pick a symbol that's suitable for the given currency.
115
+ #
116
+ # @example
117
+ # Money.new(100, "USD") #=> "$1.00"
118
+ # Money.new(100, "GBP") #=> "£1.00"
119
+ # Money.new(100, "EUR") #=> "€1.00"
120
+ #
121
+ # # Same thing.
122
+ # Money.new(100, "USD").format(:symbol => true) #=> "$1.00"
123
+ # Money.new(100, "GBP").format(:symbol => true) #=> "£1.00"
124
+ # Money.new(100, "EUR").format(:symbol => true) #=> "€1.00"
125
+ #
126
+ # # You can specify a false expression or an empty string to disable
127
+ # # prepending a money symbol.§
128
+ # Money.new(100, "USD").format(:symbol => false) #=> "1.00"
129
+ # Money.new(100, "GBP").format(:symbol => nil) #=> "1.00"
130
+ # Money.new(100, "EUR").format(:symbol => "") #=> "1.00"
131
+ #
132
+ # # If the symbol for the given currency isn't known, then it will default
133
+ # # to "¤" as symbol.
134
+ # Money.new(100, "AWG").format(:symbol => true) #=> "¤1.00"
135
+ #
136
+ # # You can specify a string as value to enforce using a particular symbol.
137
+ # Money.new(100, "AWG").format(:symbol => "ƒ") #=> "ƒ1.00"
138
+ #
139
+ # # You can specify a indian currency format
140
+ # Money.new(10000000, "INR").format(:south_asian => true) #=> "1,00,000.00"
141
+ # Money.new(10000000).format(:south_asian => true) #=> "$1,00,000.00"
142
+ #
143
+ # @option rules [Boolean, nil] :symbol_space (true) Whether
144
+ # a space between the money symbol and the amount should be inserted when
145
+ # +:symbol_position+ is +:before+.
146
+ # The default is false when +:symbol_position+ is +:before+,
147
+ # and true when +:symbol_position+ is +:after+.
148
+ # Ignored if +:symbol+ is false.
149
+ #
150
+ # @example
151
+ # # Default is to not insert a space.
152
+ # Money.new(100, "USD").format #=> "$1.00"
153
+ #
154
+ # # Same thing.
155
+ # Money.new(100, "USD").format(:symbol_space => true) #=> "$1.00"
156
+ #
157
+ # # If set to false, will insert a space.
158
+ # Money.new(100, "USD").format(:symbol_space => false) #=> "$ 1.00"
159
+ #
160
+ # # Default is to insert a space.
161
+ # Money.new(100, "USD").format(:symbol_position => :after) #=> "1.00 $"
162
+ #
163
+ # # If set to true, will not insert a space.
164
+ # Money.new(100, "USD").format(:symbol_position => :after, :symbol_space => true) #=> "1.00$"
165
+ #
166
+ # @example
167
+ #
168
+ # @option rules [Boolean, String, nil] :separator (true) Whether the
169
+ # currency should be separated by the specified character or '.'
170
+ #
171
+ # @example
172
+ # # If a string is specified, it's value is used.
173
+ # Money.new(100, "USD").format(:separator => ",") #=> "$1,00"
174
+ #
175
+ # # If the separator for a given currency isn't known, then it will default
176
+ # # to "." as separator.
177
+ # Money.new(100, "FOO").format #=> "$1.00"
178
+ #
179
+ # @option rules [Boolean, String, nil] :delimiter (true) Whether
180
+ # the currency should be delimited by the specified character or ','
181
+ #
182
+ # @example
183
+ # # If false is specified, no delimiter is used.
184
+ # Money.new(100000, "USD").format(:delimiter => false) #=> "1000.00"
185
+ # Money.new(100000, "USD").format(:delimiter => nil) #=> "1000.00"
186
+ # Money.new(100000, "USD").format(:delimiter => "") #=> "1000.00"
187
+ #
188
+ # # If a string is specified, it's value is used.
189
+ # Money.new(100000, "USD").format(:delimiter => ".") #=> "$1.000.00"
190
+ #
191
+ # # If the delimiter for a given currency isn't known, then it will
192
+ # # default to "," as delimiter.
193
+ # Money.new(100000, "FOO").format #=> "$1,000.00"
194
+ #
195
+ # @option rules [Boolean] :html (false) Whether the currency should be
196
+ # HTML-formatted. Only useful in combination with +:with_currency+.
197
+ #
198
+ # @example
199
+ # Money.ca_dollar(570).format(:html => true, :with_currency => true)
200
+ # #=> "$5.70 <span class=\"currency\">CAD</span>"
201
+ #
202
+ # @option rules [Boolean] :sign_before_symbol (false) Whether the sign should be
203
+ # before the currency symbol.
204
+ #
205
+ # @example
206
+ # # You can specify to display the sign before the symbol for negative numbers
207
+ # Money.new(-100, "GBP").format(:sign_before_symbol => true) #=> "-£1.00"
208
+ # Money.new(-100, "GBP").format(:sign_before_symbol => false) #=> "£-1.00"
209
+ # Money.new(-100, "GBP").format #=> "£-1.00"
210
+ #
211
+ # @option rules [Boolean] :sign_positive (false) Whether positive numbers should be
212
+ # signed, too.
213
+ #
214
+ # @example
215
+ # # You can specify to display the sign with positive numbers
216
+ # Money.new(100, "GBP").format(:sign_positive => true, :sign_before_symbol => true) #=> "+£1.00"
217
+ # Money.new(100, "GBP").format(:sign_positive => true, :sign_before_symbol => false) #=> "£+1.00"
218
+ # Money.new(100, "GBP").format(:sign_positive => false, :sign_before_symbol => true) #=> "£1.00"
219
+ # Money.new(100, "GBP").format(:sign_positive => false, :sign_before_symbol => false) #=> "£1.00"
220
+ # Money.new(100, "GBP").format #=> "£+1.00"
221
+ #
222
+ # @option rules [Boolean] :disambiguate (false) Prevents the result from being ambiguous
223
+ # due to equal symbols for different currencies. Uses the `disambiguate_symbol`.
224
+ #
225
+ # @example
226
+ # Money.new(10000, "USD").format(:disambiguate => false) #=> "$100.00"
227
+ # Money.new(10000, "CAD").format(:disambiguate => false) #=> "$100.00"
228
+ # Money.new(10000, "USD").format(:disambiguate => true) #=> "$100.00"
229
+ # Money.new(10000, "CAD").format(:disambiguate => true) #=> "C$100.00"
230
+ #
231
+ # @option rules [Boolean] :html_wrap_symbol (false) Wraps the currency symbol
232
+ # in a html <span> tag.
233
+ #
234
+ # @example
235
+ # Money.new(10000, "USD").format(:disambiguate => false)
236
+ # #=> "<span class=\"currency_symbol\">$100.00</span>
237
+ #
238
+ # @option rules [Symbol] :symbol_position (:before) `:before` if the currency
239
+ # symbol goes before the amount, `:after` if it goes after.
240
+ #
241
+ # @example
242
+ # Money.new(10000, "USD").format(:symbol_position => :before) #=> "$100.00"
243
+ # Money.new(10000, "USD").format(:symbol_position => :after) #=> "100.00 $"
244
+ #
245
+ # @option rules [Boolean] :translate_symbol (true) `true` Checks for custom
246
+ # symbol definitions using I18n.
247
+ #
248
+ # @example
249
+ # # With the following entry in the translation files:
250
+ # # en:
251
+ # # number:
252
+ # # currency:
253
+ # # symbol:
254
+ # # CAD: "CAD$"
255
+ # Money.new(10000, "CAD").format(:translate_symbol => true) #=> "CAD$100.00"
256
+ #
257
+ # @example
258
+ # Money.new(89000, :btc).format(:drop_trailing_zeros => true) #=> B⃦0.00089
259
+ # Money.new(110, :usd).format(:drop_trailing_zeros => true) #=> $1.1
260
+ #
261
+ # Note that the default rules can be defined through {Money.default_rules} hash.
262
+ #
263
+ # @see Money.default_rules Money.default_rules for more information.
264
+ def format
265
+ prepare_rules
266
+ return display_free if money.to_d == 0 && rules[:display_free]
267
+ str = format_number(money.to_d)
268
+ str = add_symbol_and_sign(str)
269
+ add_currency(str)
270
+ end
271
+
272
+ def format_number(val)
273
+ number_str =
274
+ if rules[:no_cents] || (rules[:no_cents_if_whole] && val % 1 == 0)
275
+ val.to_i.to_s
276
+ else
277
+ round = rules[:round]
278
+ if round
279
+ decimal_places = round == true ? currency.decimal_places : round
280
+ val = val.round(decimal_places)
281
+ end
282
+ self.class.decimal_str(val.abs, decimal_places || currency.decimal_places)
283
+ end
284
+
285
+ units, fractions = number_str.split('.')
286
+ if rules[:drop_trailing_zeros]
287
+ fractions.sub!(/0+\z/, '')
288
+ fractions = nil if fractions.empty?
289
+ end
290
+
291
+ units = apply_delimiter(units)
292
+ separator = rules[:separator] || self.separator
293
+ fractions ? "#{units}#{separator}#{fractions}" : units
294
+ end
295
+
296
+ def add_symbol_and_sign(number_str)
297
+ symbol_position = self.symbol_position
298
+ symbol = self.symbol
299
+ sign =
300
+ if money.negative?
301
+ '-'
302
+ elsif rules[:sign_positive] && money.positive?
303
+ '+'
304
+ end
305
+
306
+ if rules[:sign_before_symbol]
307
+ sign_before = sign
308
+ sign = nil
309
+ end
310
+
311
+ if symbol && !symbol.empty?
312
+ symbol = "<span class=\"currency_symbol\">#{symbol}</span>" if rules[:html_wrap_symbol]
313
+ case symbol_position
314
+ when :before
315
+ symbol_space = rules[:symbol_space] ? ' ' : nil
316
+ "#{sign_before}#{symbol}#{symbol_space}#{sign}#{number_str}"
317
+ when :after
318
+ symbol_space = rules.fetch(:symbol_space) { true } ? ' ' : nil
319
+ "#{sign_before}#{sign}#{number_str}#{symbol_space}#{symbol}"
320
+ else
321
+ raise ArgumentError, ':symbol_position must be :before or :after'
322
+ end
323
+ else
324
+ "#{sign_before}#{sign}#{number_str}"
325
+ end
326
+ end
327
+
328
+ def add_currency(str)
329
+ return str unless rules[:with_currency]
330
+ currency_str = currency.to_s
331
+ currency_str = "<span class=\"currency\">#{currency_str}</span>" if rules[:html]
332
+ str << ' ' << currency_str
333
+ end
334
+
335
+ def display_free
336
+ rules[:display_free].respond_to?(:to_str) ? rules[:display_free] : 'free'
337
+ end
338
+
339
+ def delimiter
340
+ val_from_i18n(:delimiter, ',')
341
+ end
342
+
343
+ def separator
344
+ val_from_i18n(:separator, '.')
345
+ end
346
+
347
+ def symbol
348
+ if rules[:translate_symbol] && rules[:symbol] != false
349
+ val = symbol_from_i18n
350
+ return val if val
351
+ end
352
+ if rules.key?(:symbol)
353
+ symbol = rules[:symbol]
354
+ if symbol == true
355
+ rules[:disambiguate] && currency.disambiguate_symbol || money.symbol
356
+ else
357
+ symbol
358
+ end
359
+ elsif rules[:html]
360
+ currency.html_entity || currency.symbol
361
+ else
362
+ rules[:disambiguate] && currency.disambiguate_symbol || money.symbol
363
+ end
364
+ end
365
+
366
+ def symbol_position
367
+ rules[:symbol_position] || (currency.symbol_first? ? :before : :after)
368
+ end
369
+
370
+ private
371
+
372
+ def val_from_i18n(name, default)
373
+ if self.class.use_i18n
374
+ I18n.t name, scope: 'number.currency.format', default: ->(*) do
375
+ I18n.t name, scope: 'number.format',
376
+ default: ->(*) { currency.public_send(name) || default }
377
+ end
378
+ else
379
+ currency.public_send(name) || default
380
+ end
381
+ end
382
+
383
+ def symbol_from_i18n
384
+ I18n.t currency.code, scope: 'number.currency.symbol', raise: true
385
+ rescue I18n::MissingTranslationData
386
+ end
387
+
388
+ def apply_delimiter(units)
389
+ parts =
390
+ if rules[:south_asian]
391
+ self.class.rsplit_str_by(units[0...-3], 2) + [[units[-3..-1]]]
392
+ else
393
+ self.class.rsplit_str_by(units, 3)
394
+ end
395
+ return parts.first if parts.one?
396
+ delimiter = rules.key?(:delimiter) ? rules[:delimiter] || '' : self.delimiter
397
+ parts.join(delimiter)
398
+ end
399
+
400
+ def prepare_rules
401
+ @rules = self.class.default_rules.merge(rules)
402
+ end
403
+ end
404
+ end