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.
- checksums.yaml +15 -0
- data/lib/money_helper.rb +214 -3
- data/spec/money_helper_spec.rb +9 -2
- metadata +15 -29
checksums.yaml
ADDED
@@ -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=
|
data/lib/money_helper.rb
CHANGED
@@ -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 =
|
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
|
-
|
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
|
data/spec/money_helper_spec.rb
CHANGED
@@ -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
|
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
|
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.
|
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:
|
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
|
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
|
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.
|
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.
|
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
|
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.
|
85
|
+
rubygems_version: 2.1.10
|
100
86
|
signing_key:
|
101
|
-
specification_version:
|
87
|
+
specification_version: 4
|
102
88
|
summary: MoneyHelper international price formatting utility
|
103
89
|
test_files:
|
104
90
|
- spec/money_helper_spec.rb
|