money_helper 0.0.3 → 0.0.4

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,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