money_helper 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NjA5MGU2NTI2YjJiNzZhMWFmZjFmYjMwNjI5ZjNmMzc1NzgyM2E1OQ==
5
+ data.tar.gz: !binary |-
6
+ Y2M0OGZhYjlhYjZiZDc4NGRlN2ZhM2YxZjAzYjY4N2ZiYzU5MDNmMQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NTUxMWNlNTZiNjc1ZWJkZWYyODc5MTMzMzRiMDI0NWRjNGRmZDNmN2Y4MTY1
10
+ MmY5M2QxZWM4ZTdhZjQ2MmVkYzk5ZWVkMzAwNzdlNGEyOGFlYmYyZjNlODlh
11
+ NWM4OWY4MTlmMDMwYjI4MDIxYTNjZjg4MWI5N2I4ODM4NjcwNjc=
12
+ data.tar.gz: !binary |-
13
+ ZDhiNDFmMmJkMzhkMzdmOGMwNDZmZTdiNTRmMTY3MzI3M2RhMzhlYTM5MjUx
14
+ OWQzYmU5ZjM4NjQ2YTg0NjZkM2UzY2Q2MTczZDg0NTkyM2NjMGRkNGFiZTgw
15
+ ODkwMDM3NjQ2N2U5ZjhiYTRlYjY5ZDM4YWUxMDk1YmQ4MTgwYzQ=
@@ -27,17 +27,215 @@ module MoneyHelper
27
27
  def self.money_to_text(amount, currency, number_only = false)
28
28
  return nil unless amount.present?
29
29
  currency = "USD" if currency.blank?
30
- symbol = Money::Currency.new(currency).symbol
31
- symbol.strip! if symbol.present?
30
+ symbol = symbol_for_code(currency)
32
31
  include_symbol = !number_only && symbol.present? && OK_SYMBOLS.include?(symbol)
32
+ valid_currency = currency if code_valid?(currency)
33
33
  (number_only || SYMBOL_ONLY.include?(currency) ? "" : currency + " ") +
34
- Money.parse(amount.ceil, currency).format({
34
+ parse_money(amount.ceil, valid_currency).format({
35
35
  no_cents: true,
36
36
  symbol_position: :before,
37
37
  symbol: include_symbol
38
38
  }).delete(' ')
39
39
  end
40
40
 
41
+ # Parses the current string and converts it to a +Money+ object.
42
+ # Excess characters will be discarded.
43
+ #
44
+ # @param [String, #to_s] input The input to parse.
45
+ # @param [Currency, String, Symbol] currency The currency format.
46
+ # The currency to set the resulting +Money+ object to.
47
+ #
48
+ # @return [Money]
49
+ #
50
+ # @raise [ArgumentError] If any +currency+ is supplied and
51
+ # given value doesn't match the one extracted from
52
+ # the +input+ string.
53
+ #
54
+ # @example
55
+ # '100'.to_money #=> #<Money @fractional=10000>
56
+ # '100.37'.to_money #=> #<Money @fractional=10037>
57
+ # '100 USD'.to_money #=> #<Money @fractional=10000, @currency=#<Money::Currency id: usd>>
58
+ # 'USD 100'.to_money #=> #<Money @fractional=10000, @currency=#<Money::Currency id: usd>>
59
+ # '$100 USD'.to_money #=> #<Money @fractional=10000, @currency=#<Money::Currency id: usd>>
60
+ # 'hello 2000 world'.to_money #=> #<Money @fractional=200000 @currency=#<Money::Currency id: usd>>
61
+ #
62
+ # @example Mismatching currencies
63
+ # 'USD 2000'.to_money("EUR") #=> ArgumentError
64
+ #
65
+ # @see #from_string
66
+ #
67
+ def self.parse_money(input, currency = nil)
68
+ i = input.to_s.strip
69
+
70
+ # raise Money::Currency.table.collect{|c| c[1][:symbol]}.inspect
71
+
72
+ # Check the first character for a currency symbol, alternatively get it
73
+ # from the stated currency string
74
+ c = if Money.assume_from_symbol && i =~ /^(\$|€|£)/
75
+ case i
76
+ when /^\$/ then "USD"
77
+ when /^€/ then "EUR"
78
+ when /^£/ then "GBP"
79
+ end
80
+ else
81
+ i[/[A-Z]{2,3}/]
82
+ end
83
+
84
+ # check that currency passed and embedded currency are the same,
85
+ # and negotiate the final currency
86
+ if currency.nil? and c.nil?
87
+ currency = Money.default_currency
88
+ elsif currency.nil?
89
+ currency = c
90
+ elsif c.nil?
91
+ currency = currency
92
+ elsif currency != c
93
+ # TODO: ParseError
94
+ raise ArgumentError, "Mismatching Currencies"
95
+ end
96
+ currency = Money::Currency.wrap(currency)
97
+
98
+ fractional = extract_cents(i, currency)
99
+ Money.new(fractional, currency)
100
+ end
101
+
102
+ # Takes a number string and attempts to massage out the number.
103
+ #
104
+ # @param [String] input The string containing a potential number.
105
+ #
106
+ # @return [Integer]
107
+ #
108
+ def self.extract_cents(input, currency = Money.default_currency)
109
+ # remove anything that's not a number, potential thousands_separator, or minus sign
110
+ num = input.gsub(/[^\d.,'-]/, '')
111
+
112
+ # set a boolean flag for if the number is negative or not
113
+ negative = num =~ /^-|-$/ ? true : false
114
+
115
+ # decimal mark character
116
+ decimal_char = currency.decimal_mark
117
+
118
+ # if negative, remove the minus sign from the number
119
+ # if it's not negative, the hyphen makes the value invalid
120
+ if negative
121
+ num = num.sub(/^-|-$/, '')
122
+ end
123
+
124
+ raise ArgumentError, "Invalid currency amount (hyphen)" if num.include?('-')
125
+
126
+ #if the number ends with punctuation, just throw it out. If it means decimal,
127
+ #it won't hurt anything. If it means a literal period or comma, this will
128
+ #save it from being mis-interpreted as a decimal.
129
+ num.chop! if num.match(/[\.|,]$/)
130
+
131
+ # gather all decimal_marks within the result number
132
+ used_delimiters = num.scan(/[^\d]/)
133
+
134
+ # determine the number of unique decimal_marks within the number
135
+ #
136
+ # e.g.
137
+ # $1,234,567.89 would return 2 (, and .)
138
+ # $125,00 would return 1
139
+ # $199 would return 0
140
+ # $1 234,567.89 would raise an error (decimal_marks are space, comma, and period)
141
+ case used_delimiters.uniq.length
142
+ # no decimal_mark or thousands_separator; major (dollars) is the number, and minor (cents) is 0
143
+ when 0 then major, minor = num, 0
144
+
145
+ # two decimal_marks, so we know the last item in this array is the
146
+ # major/minor thousands_separator and the rest are decimal_marks
147
+ when 2
148
+ thousands_separator, decimal_mark = used_delimiters.uniq
149
+
150
+ # remove all thousands_separator, split on the decimal_mark
151
+ major, minor = num.gsub(thousands_separator, '').split(decimal_mark)
152
+ min = 0 unless min
153
+ when 1
154
+ # we can't determine if the comma or period is supposed to be a decimal_mark or a thousands_separator
155
+ # e.g.
156
+ # 1,00 - comma is a thousands_separator
157
+ # 1.000 - period is a thousands_separator
158
+ # 1,000 - comma is a decimal_mark
159
+ # 1,000,000 - comma is a decimal_mark
160
+ # 10000,00 - comma is a thousands_separator
161
+ # 1000,000 - comma is a thousands_separator
162
+
163
+ # assign first decimal_mark for reusability
164
+ decimal_mark = used_delimiters.first
165
+
166
+ # When we have identified the decimal mark character
167
+ if decimal_char == decimal_mark
168
+ major, minor = num.split(decimal_char)
169
+
170
+ else
171
+ # decimal_mark is used as a decimal_mark when there are multiple instances, always
172
+ if num.scan(decimal_mark).length > 1 # multiple matches; treat as decimal_mark
173
+ major, minor = num.gsub(decimal_mark, ''), 0
174
+ else
175
+ # ex: 1,000 - 1.0000 - 10001.000
176
+ # split number into possible major (dollars) and minor (cents) values
177
+ possible_major, possible_minor = num.split(decimal_mark)
178
+ possible_major ||= "0"
179
+ possible_minor ||= "00"
180
+
181
+ # if the minor (cents) length isn't 3, assign major/minor from the possibles
182
+ # e.g.
183
+ # 1,00 => 1.00
184
+ # 1.0000 => 1.00
185
+ # 1.2 => 1.20
186
+ if possible_minor.length != 3 # thousands_separator
187
+ major, minor = possible_major, possible_minor
188
+ else
189
+ # minor length is three
190
+ # let's try to figure out intent of the thousands_separator
191
+
192
+ # the major length is greater than three, which means
193
+ # the comma or period is used as a thousands_separator
194
+ # e.g.
195
+ # 1000,000
196
+ # 100000,000
197
+ if possible_major.length > 3
198
+ major, minor = possible_major, possible_minor
199
+ else
200
+ # number is in format ###{sep}### or ##{sep}### or #{sep}###
201
+ # handle as , is sep, . is thousands_separator
202
+ if decimal_mark == '.'
203
+ major, minor = possible_major, possible_minor
204
+ else
205
+ major, minor = "#{possible_major}#{possible_minor}", 0
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
211
+ else
212
+ # TODO: ParseError
213
+ raise ArgumentError, "Invalid currency amount"
214
+ end
215
+
216
+ # build the string based on major/minor since decimal_mark/thousands_separator have been removed
217
+ # avoiding floating point arithmetic here to ensure accuracy
218
+ cents = (major.to_i * currency.subunit_to_unit)
219
+ # Because of an bug in JRuby, we can't just call #floor
220
+ minor = minor.to_s
221
+ minor = if minor.size < currency.decimal_places
222
+ (minor + ("0" * currency.decimal_places))[0,currency.decimal_places].to_i
223
+ elsif minor.size > currency.decimal_places
224
+ if minor[currency.decimal_places,1].to_i >= 5
225
+ minor[0,currency.decimal_places].to_i+1
226
+ else
227
+ minor[0,currency.decimal_places].to_i
228
+ end
229
+ else
230
+ minor.to_i
231
+ end
232
+
233
+ cents += minor
234
+
235
+ # if negative, multiply by -1; otherwise, return positive cents
236
+ negative ? cents * -1 : cents
237
+ end
238
+
41
239
  ##
42
240
  # Formats a low and high amount in the given currency into a price string
43
241
  #
@@ -68,4 +266,17 @@ module MoneyHelper
68
266
  end
69
267
  end
70
268
 
269
+ private
270
+
271
+ def self.code_valid?(code)
272
+ Money::Currency.stringified_keys.include?(code.downcase)
273
+ end
274
+
275
+ def self.symbol_for_code(code)
276
+ return unless code && code_valid?(code)
277
+ Money::Currency.new(code).symbol.tap do |symbol|
278
+ symbol.strip! if symbol.present?
279
+ end
280
+ end
281
+
71
282
  end
@@ -8,7 +8,7 @@ describe MoneyHelper do
8
8
  MoneyHelper.money_to_text(30000, "EUR").should eql("€30.000")
9
9
  MoneyHelper.money_to_text(30000, "GBP").should eql("£30,000")
10
10
  MoneyHelper.money_to_text(30000, "MYR").should eql("RM30,000")
11
- MoneyHelper.money_to_text(30000, "TRY").should eql("TL30,000")
11
+ MoneyHelper.money_to_text(30000, "TRY").should eql("TL30.000")
12
12
  MoneyHelper.money_to_text(30000, "USD").should eql("$30,000")
13
13
  end
14
14
  it "includes ISO code and symbol for ambiguous currencies" do
@@ -55,7 +55,7 @@ describe MoneyHelper do
55
55
  MoneyHelper.money_to_text(30000, "HNL").should eql("HNL L30,000")
56
56
  MoneyHelper.money_to_text(30000, "LSL").should eql("LSL L30,000")
57
57
  MoneyHelper.money_to_text(30000, "MDL").should eql("MDL L30,000")
58
- MoneyHelper.money_to_text(30000, "RON").should eql("RON L30.000")
58
+ MoneyHelper.money_to_text(30000, "RON").should eql("RON 30.000")
59
59
  MoneyHelper.money_to_text(30000, "SZL").should eql("SZL L30,000")
60
60
 
61
61
  MoneyHelper.money_to_text(30000, "ANG").should eql("ANG ƒ30.000")
@@ -141,6 +141,10 @@ describe MoneyHelper do
141
141
  MoneyHelper.money_to_text("", "USD").should be_nil
142
142
  MoneyHelper.money_to_text(nil, "USD").should be_nil
143
143
  end
144
+ it "falls back to ISO code when currency can't be found" do
145
+ MoneyHelper.money_to_text(10_000, "ITL").should eql("ITL 10,000")
146
+ MoneyHelper.money_to_text(10_000, "ITL", true).should eql("10,000")
147
+ end
144
148
  end
145
149
  describe "money_range_to_text" do
146
150
  it "includes no indicator for currency for the upper amount in range" do
@@ -167,5 +171,8 @@ describe MoneyHelper do
167
171
  it "returns nil when both amounts are nil" do
168
172
  MoneyHelper.money_range_to_text(nil, nil, "USD").should be_nil
169
173
  end
174
+ it "falls back to ISO code when currency can't be found" do
175
+ MoneyHelper.money_range_to_text(10_000, 20_000, "ITL").should eql("ITL 10,000 - 20,000")
176
+ end
170
177
  end
171
178
  end
metadata CHANGED
@@ -1,8 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: money_helper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
5
- prerelease:
4
+ version: 0.0.4
6
5
  platform: ruby
7
6
  authors:
8
7
  - Sahil Yakhmi
@@ -10,44 +9,39 @@ authors:
10
9
  autorequire:
11
10
  bindir: bin
12
11
  cert_chain: []
13
- date: 2013-07-17 00:00:00.000000000 Z
12
+ date: 2014-01-08 00:00:00.000000000 Z
14
13
  dependencies:
15
14
  - !ruby/object:Gem::Dependency
16
15
  name: money
17
16
  requirement: !ruby/object:Gem::Requirement
18
- none: false
19
17
  requirements:
20
- - - ~>
18
+ - - ! '>='
21
19
  - !ruby/object:Gem::Version
22
- version: 5.1.1
20
+ version: '5.1'
23
21
  type: :runtime
24
22
  prerelease: false
25
23
  version_requirements: !ruby/object:Gem::Requirement
26
- none: false
27
24
  requirements:
28
- - - ~>
25
+ - - ! '>='
29
26
  - !ruby/object:Gem::Version
30
- version: 5.1.1
27
+ version: '5.1'
31
28
  - !ruby/object:Gem::Dependency
32
29
  name: activesupport
33
30
  requirement: !ruby/object:Gem::Requirement
34
- none: false
35
31
  requirements:
36
- - - ~>
32
+ - - ! '>='
37
33
  - !ruby/object:Gem::Version
38
- version: '3.2'
34
+ version: '3.0'
39
35
  type: :runtime
40
36
  prerelease: false
41
37
  version_requirements: !ruby/object:Gem::Requirement
42
- none: false
43
38
  requirements:
44
- - - ~>
39
+ - - ! '>='
45
40
  - !ruby/object:Gem::Version
46
- version: '3.2'
41
+ version: '3.0'
47
42
  - !ruby/object:Gem::Dependency
48
43
  name: rspec
49
44
  requirement: !ruby/object:Gem::Requirement
50
- none: false
51
45
  requirements:
52
46
  - - ! '>='
53
47
  - !ruby/object:Gem::Version
@@ -55,13 +49,12 @@ dependencies:
55
49
  type: :development
56
50
  prerelease: false
57
51
  version_requirements: !ruby/object:Gem::Requirement
58
- none: false
59
52
  requirements:
60
53
  - - ! '>='
61
54
  - !ruby/object:Gem::Version
62
55
  version: '0'
63
- description: A simple class to assist in formatting unambiguous prices and price ranges
64
- in international currencies in a Roman Script context.
56
+ description: A simple module to assist in formatting unambiguous prices and price
57
+ ranges in international currencies in a Roman Script context.
65
58
  email: sahil@artsymail.net
66
59
  executables: []
67
60
  extensions: []
@@ -72,33 +65,26 @@ files:
72
65
  homepage: https://github.com/syakhmi/money_helper
73
66
  licenses:
74
67
  - MIT
68
+ metadata: {}
75
69
  post_install_message:
76
70
  rdoc_options: []
77
71
  require_paths:
78
72
  - lib
79
73
  required_ruby_version: !ruby/object:Gem::Requirement
80
- none: false
81
74
  requirements:
82
75
  - - ! '>='
83
76
  - !ruby/object:Gem::Version
84
77
  version: '0'
85
- segments:
86
- - 0
87
- hash: 2615965141994932059
88
78
  required_rubygems_version: !ruby/object:Gem::Requirement
89
- none: false
90
79
  requirements:
91
80
  - - ! '>='
92
81
  - !ruby/object:Gem::Version
93
82
  version: '0'
94
- segments:
95
- - 0
96
- hash: 2615965141994932059
97
83
  requirements: []
98
84
  rubyforge_project:
99
- rubygems_version: 1.8.25
85
+ rubygems_version: 2.1.10
100
86
  signing_key:
101
- specification_version: 3
87
+ specification_version: 4
102
88
  summary: MoneyHelper international price formatting utility
103
89
  test_files:
104
90
  - spec/money_helper_spec.rb