monetize 1.4.0 → 1.9.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.travis.yml +14 -2
- data/CHANGELOG.md +86 -11
- data/Gemfile +11 -1
- data/README.md +16 -6
- data/lib/monetize.rb +36 -192
- data/lib/{collection.rb → monetize/collection.rb} +0 -1
- data/lib/monetize/core_extensions/hash.rb +2 -1
- data/lib/monetize/core_extensions/string.rb +1 -1
- data/lib/monetize/errors.rb +6 -0
- data/lib/monetize/parser.rb +159 -0
- data/lib/monetize/version.rb +1 -1
- data/monetize.gemspec +12 -6
- data/spec/core_extensions_spec.rb +41 -2
- data/spec/monetize_spec.rb +178 -92
- metadata +24 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6268692fea7908fbc268790bf98cca9795df28eed380b07d2683eed6f5dad4d1
|
4
|
+
data.tar.gz: 8d795add425b915dc88ecf910505af69b7c1f340ae412083e4c230f0622c2e27
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e4291ebf0a43a4bea5a2973186b1dca2d9d9fa08729acb956073efb2628d6898620f5eb1110ade3bba5dbac313894babd4ab59c0d1db92b8cb8e041974a0d402
|
7
|
+
data.tar.gz: 06d080183a460c5777b9f7443655bcb00d214ee18a6294f0838a3ac4bfec95715ab1ad5704001d286a6bc1ed0966a4a031942bd946ef9d249372ea527a120785
|
data/.travis.yml
CHANGED
@@ -1,8 +1,20 @@
|
|
1
|
-
|
1
|
+
sudo: false
|
2
|
+
language:
|
3
|
+
- ruby
|
4
|
+
- java
|
2
5
|
rvm:
|
3
6
|
- 1.9.3
|
4
7
|
- 2.0.0
|
5
|
-
- 2.1.
|
8
|
+
- 2.1.10
|
9
|
+
- 2.2.10
|
10
|
+
- 2.3.8
|
11
|
+
- 2.4.6
|
12
|
+
- 2.5.5
|
13
|
+
- 2.6.3
|
14
|
+
- jruby-9.0.5.0
|
15
|
+
jdk: openjdk8
|
16
|
+
before_install:
|
17
|
+
- gem install bundler --version '~> 1.17'
|
6
18
|
script: bundle exec rspec spec
|
7
19
|
notifications:
|
8
20
|
recipients:
|
data/CHANGELOG.md
CHANGED
@@ -1,21 +1,96 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
## 1.4
|
4
|
-
-
|
5
|
-
- Add capability to parse currency amounts given with suffixes (K, M, B, and T)
|
3
|
+
## 1.9.4
|
4
|
+
- Fix symbol parsing that are surrounded by other characters
|
6
5
|
|
7
|
-
## 1.3
|
8
|
-
-
|
9
|
-
- Added correct parsing of Brazilian Real $R symbol
|
10
|
-
- Add testing task for Brazilian Real parsing
|
11
|
-
- Add Lira Sign (₤) as a symbol for GBP
|
6
|
+
## 1.9.3
|
7
|
+
- Fix input parsing when currency symbol after the amount
|
12
8
|
|
13
|
-
## 1.
|
14
|
-
-
|
9
|
+
## 1.9.2
|
10
|
+
- Respect Money.rounding_mode when parsing strings
|
11
|
+
- Deprecate Monetize.extract_cents
|
12
|
+
|
13
|
+
## 1.9.1
|
14
|
+
- Ruby 2.6 support
|
15
|
+
|
16
|
+
## 1.9.0
|
17
|
+
- Relax Money gem dependency to ~> 6.12
|
18
|
+
- Refactor `Monetize::Parser`
|
19
|
+
- Ruby 2.5 support
|
20
|
+
|
21
|
+
## 1.8.0
|
22
|
+
- Money version updated to 6.11.2
|
23
|
+
- Fix rounding problem in `.from_bigdecimal`
|
15
24
|
|
16
|
-
##
|
25
|
+
## 1.7.0
|
26
|
+
- Add South Korean Won currency
|
27
|
+
- Money version updated to 6.9
|
28
|
+
- Coveralls version update to 0.8.20
|
29
|
+
- Add South Korean Won currency
|
30
|
+
- Improve conversion to Money from Hash
|
31
|
+
|
32
|
+
## 1.6.0
|
33
|
+
- Ruby 2.4.0 support
|
34
|
+
- Money version updated to 6.8
|
35
|
+
|
36
|
+
## 1.5.0
|
37
|
+
- Fix issue where parsing a Money object resulted in a Money object with its currency set to `Money.default_currency`,
|
38
|
+
rather than the currency that it was sent in as.
|
39
|
+
- Add extra currencies:
|
40
|
+
- Azerbaijani manat
|
41
|
+
- Chinese yuan
|
42
|
+
- Czech koruna
|
43
|
+
- Hungarian forint
|
44
|
+
- Indian rupee
|
45
|
+
- Russian ruble
|
46
|
+
- Turkish Lira
|
47
|
+
- Ukrainian Hryvnia
|
48
|
+
- Swiss Frank
|
49
|
+
- Polish Zloty
|
50
|
+
- Kazakhstani Tenge
|
51
|
+
- Fix issue with losing precision on BigDecimal input
|
52
|
+
- Add Swedish krona
|
53
|
+
- Exclude ambiguous kr symbol from parsing
|
54
|
+
- Fix JPY parsing
|
55
|
+
- Subclass all errors to Monetize::Error
|
56
|
+
- Fix ruby 1.9.3 compatibility
|
57
|
+
- Suppress errors when using parse. Use `parse!` instead
|
58
|
+
- Strip currency symbol prefix when parsing input
|
59
|
+
|
60
|
+
## 1.4.0
|
17
61
|
- Fixed support for <code>Money.infinite_precision = true</code> in .to_money
|
18
62
|
- Add Rubocop config to project
|
19
63
|
- Reformat code to adapt to Rubocop guidelines
|
20
64
|
- Add config setting to always enforce currency delimiters
|
21
65
|
- Add rake console task
|
66
|
+
- Required Forwardable on Collection to resolve NameError [\#44](https://github.com/RubyMoney/monetize/issues/44)
|
67
|
+
- Add capability to parse currency amounts given with suffixes (K, M, B, and T)
|
68
|
+
|
69
|
+
## 1.3.1
|
70
|
+
- Updated Money version dependency to 6.6
|
71
|
+
|
72
|
+
## 1.3.0
|
73
|
+
- Add Lira Sign (₤) as a symbol for GBP
|
74
|
+
|
75
|
+
## 1.2.0
|
76
|
+
- Add support for parsing Yen symbol
|
77
|
+
- Add `Monetize.parse_collection` and `Monetize::Collection` class for parsing multiple values
|
78
|
+
- Add parsing of C$ for Canadian Dollar
|
79
|
+
- Add NilClass extension
|
80
|
+
- Add Hash extension
|
81
|
+
|
82
|
+
## 1.1.0
|
83
|
+
- Add :assume_from_symbol option to #parse
|
84
|
+
- Enable #parse to detect currency with signed amounts
|
85
|
+
- Updated Money version dependency to 6.5.0
|
86
|
+
|
87
|
+
## 1.0.0
|
88
|
+
- Updated Money version dependency to 6.4.0
|
89
|
+
|
90
|
+
## 0.4.1
|
91
|
+
- Updated Money version dependency to 6.2.1
|
92
|
+
|
93
|
+
## 0.4.0
|
94
|
+
- Added correct parsing of Brazilian Real $R symbol
|
95
|
+
- Add testing task for Brazilian Real parsing
|
96
|
+
- Updated Money version dependency to 6.2.0
|
data/Gemfile
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
gem 'coveralls', require: false
|
3
|
+
gem 'coveralls', '>= 0.8.20', require: false
|
4
|
+
|
5
|
+
# JSON and I18n gem no longer supports ruby < 2.0.0
|
6
|
+
if defined?(JRUBY_VERSION)
|
7
|
+
gem 'json'
|
8
|
+
elsif RUBY_VERSION =~ /^1/
|
9
|
+
gem 'json', '~> 1.8.3'
|
10
|
+
gem 'tins', '~> 1.6.0'
|
11
|
+
gem 'term-ansicolor', '~> 1.3.0'
|
12
|
+
gem 'i18n', '~> 0.9'
|
13
|
+
end
|
4
14
|
|
5
15
|
gemspec
|
data/README.md
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# Monetize
|
2
2
|
|
3
|
-
[![Gem Version](https://badge.fury.io/rb/monetize.
|
4
|
-
[![Build Status](https://travis-ci.org/RubyMoney/monetize.
|
5
|
-
[![Code Climate](https://codeclimate.com/github/RubyMoney/monetize.
|
6
|
-
[![Coverage Status](https://coveralls.io/repos/RubyMoney/monetize/badge.
|
7
|
-
[![Dependency Status](https://gemnasium.com/RubyMoney/monetize.
|
8
|
-
[![License](
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/monetize.svg)](http://badge.fury.io/rb/monetize)
|
4
|
+
[![Build Status](https://travis-ci.org/RubyMoney/monetize.svg?branch=master)](https://travis-ci.org/RubyMoney/monetize)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/RubyMoney/monetize.svg)](https://codeclimate.com/github/RubyMoney/monetize)
|
6
|
+
[![Coverage Status](https://coveralls.io/repos/RubyMoney/monetize/badge.svg)](https://coveralls.io/r/RubyMoney/monetize)
|
7
|
+
[![Dependency Status](https://gemnasium.com/RubyMoney/monetize.svg)](https://gemnasium.com/RubyMoney/monetize)
|
8
|
+
[![License](https://img.shields.io/badge/license-MIT-green.svg)](http://opensource.org/licenses/MIT)
|
9
9
|
|
10
10
|
A library for converting various objects into `Money` objects.
|
11
11
|
|
@@ -33,6 +33,16 @@ Monetize.parse("GBP 100") == Money.new(100_00, "GBP")
|
|
33
33
|
"100".to_money == Money.new(100_00, "USD")
|
34
34
|
```
|
35
35
|
|
36
|
+
`parse` will return `nil` if it is unable to parse the input. Use `parse!` instead if you want a `Monetize::Error` (or one of the subclasses) to be raised instead:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
>> Monetize.parse('OMG 100')
|
40
|
+
=> nil
|
41
|
+
|
42
|
+
>> Monetize.parse!('OMG 100')
|
43
|
+
Monetize::ParseError: Unknown currency 'omg'
|
44
|
+
```
|
45
|
+
|
36
46
|
Optionally, enable the ability to assume the currency from a passed symbol. Otherwise, currency symbols will be ignored, and USD used as the default currency:
|
37
47
|
|
38
48
|
```ruby
|
data/lib/monetize.rb
CHANGED
@@ -2,30 +2,12 @@
|
|
2
2
|
|
3
3
|
require 'money'
|
4
4
|
require 'monetize/core_extensions'
|
5
|
+
require 'monetize/errors'
|
5
6
|
require 'monetize/version'
|
6
|
-
require '
|
7
|
+
require 'monetize/parser'
|
8
|
+
require 'monetize/collection'
|
7
9
|
|
8
10
|
module Monetize
|
9
|
-
CURRENCY_SYMBOLS = {
|
10
|
-
'$' => 'USD',
|
11
|
-
'€' => 'EUR',
|
12
|
-
'£' => 'GBP',
|
13
|
-
'₤' => 'GBP',
|
14
|
-
'R$' => 'BRL',
|
15
|
-
'R' => 'ZAR',
|
16
|
-
'¥' => 'JPY',
|
17
|
-
'C$' => 'CAD'
|
18
|
-
}
|
19
|
-
|
20
|
-
MULTIPLIER_SUFFIXES = {
|
21
|
-
'K' => 3,
|
22
|
-
'M' => 6,
|
23
|
-
'B' => 9,
|
24
|
-
'T' => 12
|
25
|
-
}
|
26
|
-
MULTIPLIER_SUFFIXES.default = 0
|
27
|
-
MULTIPLIER_REGEXP = Regexp.new(format('^(.*?\d)(%s)\b([^\d]*)$', MULTIPLIER_SUFFIXES.keys.join('|')), 'i')
|
28
|
-
|
29
11
|
# Class methods
|
30
12
|
class << self
|
31
13
|
# @attr_accessor [true, false] assume_from_symbol Use this to enable the
|
@@ -37,195 +19,57 @@ module Monetize
|
|
37
19
|
# though, it will try to determine the correct separator by itself. Set this
|
38
20
|
# to true to enforce the delimiters set in the currency all the time.
|
39
21
|
attr_accessor :enforce_currency_delimiters
|
40
|
-
end
|
41
|
-
|
42
|
-
def self.parse(input, currency = Money.default_currency, options = {})
|
43
|
-
input = input.to_s.strip
|
44
|
-
|
45
|
-
computed_currency = if options.fetch(:assume_from_symbol) { assume_from_symbol }
|
46
|
-
compute_currency(input)
|
47
|
-
else
|
48
|
-
input[/[A-Z]{2,3}/]
|
49
|
-
end
|
50
|
-
|
51
|
-
currency = computed_currency || currency || Money.default_currency
|
52
|
-
currency = Money::Currency.wrap(currency)
|
53
|
-
|
54
|
-
fractional = extract_cents(input, currency)
|
55
|
-
Money.new(fractional, currency)
|
56
|
-
end
|
57
|
-
|
58
|
-
def self.parse_collection(input, currency = Money.default_currency, options = {})
|
59
|
-
Collection.parse(input, currency, options)
|
60
|
-
end
|
61
|
-
|
62
|
-
def self.from_string(value, currency = Money.default_currency)
|
63
|
-
value = BigDecimal.new(value.to_s)
|
64
|
-
from_bigdecimal(value, currency)
|
65
|
-
end
|
66
22
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
end
|
72
|
-
|
73
|
-
def self.from_float(value, currency = Money.default_currency)
|
74
|
-
value = BigDecimal.new(value.to_s)
|
75
|
-
from_bigdecimal(value, currency)
|
76
|
-
end
|
77
|
-
|
78
|
-
def self.from_bigdecimal(value, currency = Money.default_currency)
|
79
|
-
currency = Money::Currency.wrap(currency)
|
80
|
-
value *= currency.subunit_to_unit
|
81
|
-
value = value.round unless Money.infinite_precision
|
82
|
-
Money.new(value, currency)
|
83
|
-
end
|
84
|
-
|
85
|
-
def self.from_numeric(value, currency = Money.default_currency)
|
86
|
-
case value
|
87
|
-
when Fixnum
|
88
|
-
from_fixnum(value, currency)
|
89
|
-
when Numeric
|
90
|
-
value = BigDecimal.new(value.to_s)
|
91
|
-
from_bigdecimal(value, currency)
|
92
|
-
else
|
93
|
-
fail ArgumentError, "'value' should be a type of Numeric"
|
23
|
+
def parse(input, currency = Money.default_currency, options = {})
|
24
|
+
parse! input, currency, options
|
25
|
+
rescue Error
|
26
|
+
nil
|
94
27
|
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def self.extract_cents(input, currency = Money.default_currency)
|
98
|
-
multiplier_exp, input = extract_multiplier(input)
|
99
|
-
|
100
|
-
num = input.gsub(/[^\d.,'-]/, '')
|
101
|
-
|
102
|
-
negative, num = extract_sign(num)
|
103
|
-
|
104
|
-
num.chop! if num.match(/[\.|,]$/)
|
105
|
-
|
106
|
-
major, minor = extract_major_minor(num, currency)
|
107
28
|
|
108
|
-
|
29
|
+
def parse!(input, currency = Money.default_currency, options = {})
|
30
|
+
return input if input.is_a?(Money)
|
31
|
+
return from_numeric(input, currency) if input.is_a?(Numeric)
|
109
32
|
|
110
|
-
|
33
|
+
parser = Monetize::Parser.new(input, currency, options)
|
34
|
+
amount, currency = parser.parse
|
111
35
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
end
|
116
|
-
|
117
|
-
private
|
118
|
-
|
119
|
-
def self.apply_multiplier(multiplier_exp, cents, minor)
|
120
|
-
cents *= (10**multiplier_exp)
|
121
|
-
minor = minor.to_s + ('0' * multiplier_exp)
|
122
|
-
shift = minor[0...multiplier_exp].to_i * 100
|
123
|
-
cents += shift
|
124
|
-
minor = (minor[multiplier_exp..-1] || '')
|
125
|
-
[cents, minor]
|
126
|
-
end
|
127
|
-
|
128
|
-
def self.apply_sign(negative, cents)
|
129
|
-
negative ? cents * -1 : cents
|
130
|
-
end
|
131
|
-
|
132
|
-
def self.contains_currency_symbol?(amount)
|
133
|
-
amount =~ currency_symbol_regex
|
134
|
-
end
|
135
|
-
|
136
|
-
def self.compute_currency(amount)
|
137
|
-
if contains_currency_symbol?(amount)
|
138
|
-
matches = amount.match(currency_symbol_regex)
|
139
|
-
CURRENCY_SYMBOLS[matches[:symbol]]
|
140
|
-
else
|
141
|
-
amount[/[A-Z]{2,3}/]
|
36
|
+
Money.from_amount(amount, currency)
|
37
|
+
rescue Money::Currency::UnknownCurrency => e
|
38
|
+
fail ParseError, e.message
|
142
39
|
end
|
143
|
-
end
|
144
|
-
|
145
|
-
def self.extract_major_minor(num, currency)
|
146
|
-
used_delimiters = num.scan(/[^\d]/).uniq
|
147
40
|
|
148
|
-
|
149
|
-
|
150
|
-
[num, 0]
|
151
|
-
when 2
|
152
|
-
thousands_separator, decimal_mark = used_delimiters
|
153
|
-
split_major_minor(num.gsub(thousands_separator, ''), decimal_mark)
|
154
|
-
when 1
|
155
|
-
extract_major_minor_with_single_delimiter(num, currency, used_delimiters.first)
|
156
|
-
else
|
157
|
-
fail ArgumentError, 'Invalid currency amount'
|
41
|
+
def parse_collection(input, currency = Money.default_currency, options = {})
|
42
|
+
Collection.parse(input, currency, options)
|
158
43
|
end
|
159
|
-
end
|
160
44
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
elsif enforce_currency_delimiters and delimiter == currency.thousands_separator
|
165
|
-
[num.gsub(delimiter, ''), 0]
|
166
|
-
else
|
167
|
-
extract_major_minor_with_tentative_delimiter(num, delimiter)
|
45
|
+
def from_string(value, currency = Money.default_currency)
|
46
|
+
value = BigDecimal(value.to_s)
|
47
|
+
Money.from_amount(value, currency)
|
168
48
|
end
|
169
|
-
end
|
170
|
-
|
171
|
-
def self.extract_major_minor_with_tentative_delimiter(num, delimiter)
|
172
|
-
if num.scan(delimiter).length > 1
|
173
|
-
# Multiple matches; treat as thousands separator
|
174
|
-
[num.gsub(delimiter, ''), '00']
|
175
|
-
else
|
176
|
-
possible_major, possible_minor = split_major_minor(num, delimiter)
|
177
49
|
|
178
|
-
|
179
|
-
|
180
|
-
[possible_major, possible_minor]
|
181
|
-
else
|
182
|
-
["#{possible_major}#{possible_minor}", '00']
|
183
|
-
end
|
50
|
+
def from_fixnum(value, currency = Money.default_currency)
|
51
|
+
Money.from_amount(value, currency)
|
184
52
|
end
|
185
|
-
|
53
|
+
alias_method :from_integer, :from_fixnum
|
186
54
|
|
187
|
-
|
188
|
-
|
189
|
-
multiplier_suffix = matches[2].upcase
|
190
|
-
[MULTIPLIER_SUFFIXES[multiplier_suffix], "#{$1}#{$3}"]
|
191
|
-
else
|
192
|
-
[0, input]
|
55
|
+
def from_float(value, currency = Money.default_currency)
|
56
|
+
Money.from_amount(value, currency)
|
193
57
|
end
|
194
|
-
end
|
195
|
-
|
196
|
-
def self.extract_sign(input)
|
197
|
-
result = (input =~ /^-+(.*)$/ or input =~ /^(.*)-+$/) ? [true, $1] : [false, input]
|
198
|
-
fail ArgumentError, 'Invalid currency amount (hyphen)' if result[1].include?('-')
|
199
|
-
result
|
200
|
-
end
|
201
58
|
|
202
|
-
|
203
|
-
|
204
|
-
|
59
|
+
def from_bigdecimal(value, currency = Money.default_currency)
|
60
|
+
Money.from_amount(value, currency)
|
61
|
+
end
|
205
62
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
elsif minor.size < currency.decimal_places
|
210
|
-
(minor + ('0' * currency.decimal_places))[0, currency.decimal_places].to_i
|
211
|
-
elsif minor.size > currency.decimal_places
|
212
|
-
if minor[currency.decimal_places, 1].to_i >= 5
|
213
|
-
minor[0, currency.decimal_places].to_i + 1
|
214
|
-
else
|
215
|
-
minor[0, currency.decimal_places].to_i
|
216
|
-
end
|
217
|
-
else
|
218
|
-
minor.to_i
|
63
|
+
def from_numeric(value, currency = Money.default_currency)
|
64
|
+
fail ArgumentError, "'value' should be a type of Numeric" unless value.is_a?(Numeric)
|
65
|
+
Money.from_amount(value, currency)
|
219
66
|
end
|
220
|
-
end
|
221
67
|
|
222
|
-
|
223
|
-
|
224
|
-
minor = '00' unless minor
|
225
|
-
[major, minor]
|
226
|
-
end
|
68
|
+
def extract_cents(input, currency = Money.default_currency)
|
69
|
+
warn '[DEPRECATION] Monetize.extract_cents is deprecated. Use Monetize.parse().cents'
|
227
70
|
|
228
|
-
|
229
|
-
|
71
|
+
money = parse(input, currency)
|
72
|
+
money.cents if money
|
73
|
+
end
|
230
74
|
end
|
231
75
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
class Hash
|
4
4
|
def to_money(currency = nil)
|
5
|
-
|
5
|
+
hash_currency = self[:currency].is_a?(Hash) ? self[:currency][:iso_code] : self[:currency]
|
6
|
+
Money.new(self[:cents] || self[:fractional], hash_currency || currency || Money.default_currency)
|
6
7
|
end
|
7
8
|
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Monetize
|
4
|
+
class Parser
|
5
|
+
CURRENCY_SYMBOLS = {
|
6
|
+
'$' => 'USD',
|
7
|
+
'€' => 'EUR',
|
8
|
+
'£' => 'GBP',
|
9
|
+
'₤' => 'GBP',
|
10
|
+
'R$' => 'BRL',
|
11
|
+
'R' => 'ZAR',
|
12
|
+
'¥' => 'JPY',
|
13
|
+
'C$' => 'CAD',
|
14
|
+
'₼' => 'AZN',
|
15
|
+
'元' => 'CNY',
|
16
|
+
'Kč' => 'CZK',
|
17
|
+
'Ft' => 'HUF',
|
18
|
+
'₹' => 'INR',
|
19
|
+
'₽' => 'RUB',
|
20
|
+
'₺' => 'TRY',
|
21
|
+
'₴' => 'UAH',
|
22
|
+
'Fr' => 'CHF',
|
23
|
+
'zł' => 'PLN',
|
24
|
+
'₸' => 'KZT',
|
25
|
+
"₩" => 'KRW',
|
26
|
+
}
|
27
|
+
|
28
|
+
MULTIPLIER_SUFFIXES = { 'K' => 3, 'M' => 6, 'B' => 9, 'T' => 12 }
|
29
|
+
MULTIPLIER_SUFFIXES.default = 0
|
30
|
+
MULTIPLIER_REGEXP = Regexp.new(format('^(.*?\d)(%s)\b([^\d]*)$', MULTIPLIER_SUFFIXES.keys.join('|')), 'i')
|
31
|
+
|
32
|
+
DEFAULT_DECIMAL_MARK = '.'.freeze
|
33
|
+
|
34
|
+
def initialize(input, fallback_currency = Money.default_currency, options = {})
|
35
|
+
@input = input.to_s.strip
|
36
|
+
@fallback_currency = fallback_currency
|
37
|
+
@options = options
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse
|
41
|
+
currency = Money::Currency.wrap(parse_currency)
|
42
|
+
|
43
|
+
multiplier_exp, input = extract_multiplier
|
44
|
+
|
45
|
+
num = input.gsub(/(?:^#{currency.symbol}|[^\d.,'-]+)/, '')
|
46
|
+
|
47
|
+
negative, num = extract_sign(num)
|
48
|
+
|
49
|
+
num.chop! if num =~ /[\.|,]$/
|
50
|
+
|
51
|
+
major, minor = extract_major_minor(num, currency)
|
52
|
+
|
53
|
+
amount = BigDecimal([major, minor].join(DEFAULT_DECIMAL_MARK))
|
54
|
+
amount = apply_multiplier(multiplier_exp, amount)
|
55
|
+
amount = apply_sign(negative, amount)
|
56
|
+
|
57
|
+
[amount, currency]
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
attr_reader :input, :fallback_currency, :options
|
63
|
+
|
64
|
+
def parse_currency
|
65
|
+
computed_currency = nil
|
66
|
+
computed_currency = compute_currency if assume_from_symbol?
|
67
|
+
computed_currency ||= input[/[A-Z]{2,3}/]
|
68
|
+
|
69
|
+
computed_currency || fallback_currency || Money.default_currency
|
70
|
+
end
|
71
|
+
|
72
|
+
def assume_from_symbol?
|
73
|
+
options.fetch(:assume_from_symbol) { Monetize.assume_from_symbol }
|
74
|
+
end
|
75
|
+
|
76
|
+
def apply_multiplier(multiplier_exp, amount)
|
77
|
+
amount * 10**multiplier_exp
|
78
|
+
end
|
79
|
+
|
80
|
+
def apply_sign(negative, amount)
|
81
|
+
negative ? amount * -1 : amount
|
82
|
+
end
|
83
|
+
|
84
|
+
def compute_currency
|
85
|
+
match = input.match(currency_symbol_regex)
|
86
|
+
CURRENCY_SYMBOLS[match.to_s] if match
|
87
|
+
end
|
88
|
+
|
89
|
+
def extract_major_minor(num, currency)
|
90
|
+
used_delimiters = num.scan(/[^\d]/).uniq
|
91
|
+
|
92
|
+
case used_delimiters.length
|
93
|
+
when 0
|
94
|
+
[num, 0]
|
95
|
+
when 2
|
96
|
+
thousands_separator, decimal_mark = used_delimiters
|
97
|
+
split_major_minor(num.gsub(thousands_separator, ''), decimal_mark)
|
98
|
+
when 1
|
99
|
+
extract_major_minor_with_single_delimiter(num, currency, used_delimiters.first)
|
100
|
+
else
|
101
|
+
fail ParseError, 'Invalid amount'
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def extract_major_minor_with_single_delimiter(num, currency, delimiter)
|
106
|
+
if delimiter == currency.decimal_mark
|
107
|
+
split_major_minor(num, delimiter)
|
108
|
+
elsif Monetize.enforce_currency_delimiters && delimiter == currency.thousands_separator
|
109
|
+
[num.gsub(delimiter, ''), 0]
|
110
|
+
else
|
111
|
+
extract_major_minor_with_tentative_delimiter(num, delimiter)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def extract_major_minor_with_tentative_delimiter(num, delimiter)
|
116
|
+
if num.scan(delimiter).length > 1
|
117
|
+
# Multiple matches; treat as thousands separator
|
118
|
+
[num.gsub(delimiter, ''), '00']
|
119
|
+
else
|
120
|
+
possible_major, possible_minor = split_major_minor(num, delimiter)
|
121
|
+
|
122
|
+
if possible_minor.length != 3 || possible_major.length > 3 || delimiter == '.'
|
123
|
+
# Doesn't look like thousands separator
|
124
|
+
[possible_major, possible_minor]
|
125
|
+
else
|
126
|
+
["#{possible_major}#{possible_minor}", '00']
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def extract_multiplier
|
132
|
+
if (matches = MULTIPLIER_REGEXP.match(input))
|
133
|
+
multiplier_suffix = matches[2].upcase
|
134
|
+
[MULTIPLIER_SUFFIXES[multiplier_suffix], "#{$1}#{$3}"]
|
135
|
+
else
|
136
|
+
[0, input]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def extract_sign(input)
|
141
|
+
result = (input =~ /^-+(.*)$/ || input =~ /^(.*)-+$/) ? [true, $1] : [false, input]
|
142
|
+
fail ParseError, 'Invalid amount (hyphen)' if result[1].include?('-')
|
143
|
+
result
|
144
|
+
end
|
145
|
+
|
146
|
+
def regex_safe_symbols
|
147
|
+
CURRENCY_SYMBOLS.keys.map { |key| Regexp.escape(key) }.join('|')
|
148
|
+
end
|
149
|
+
|
150
|
+
def split_major_minor(num, delimiter)
|
151
|
+
major, minor = num.split(delimiter)
|
152
|
+
[major, minor || '00']
|
153
|
+
end
|
154
|
+
|
155
|
+
def currency_symbol_regex
|
156
|
+
/(?<![A-Z])(#{regex_safe_symbols})(?![A-Z])/i
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
data/lib/monetize/version.rb
CHANGED
data/monetize.gemspec
CHANGED
@@ -7,8 +7,8 @@ require 'English'
|
|
7
7
|
Gem::Specification.new do |spec|
|
8
8
|
spec.name = 'monetize'
|
9
9
|
spec.version = Monetize::VERSION
|
10
|
-
spec.authors = ['Shane Emmons']
|
11
|
-
spec.email = ['shane@emmons.io']
|
10
|
+
spec.authors = ['Shane Emmons', 'Anthony Dmitriyev']
|
11
|
+
spec.email = ['shane@emmons.io', 'anthony.dmitriyev@gmail.com']
|
12
12
|
spec.description = 'A library for converting various objects into `Money` objects.'
|
13
13
|
spec.summary = 'A library for converting various objects into `Money` objects.'
|
14
14
|
spec.homepage = 'https://github.com/RubyMoney/monetize'
|
@@ -19,9 +19,15 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
20
|
spec.require_paths = ['lib']
|
21
21
|
|
22
|
-
spec.add_dependency 'money', '~> 6.
|
22
|
+
spec.add_dependency 'money', '~> 6.12'
|
23
23
|
|
24
|
-
spec.add_development_dependency 'bundler'
|
25
|
-
spec.add_development_dependency 'rake'
|
26
|
-
spec.add_development_dependency 'rspec', '~> 3.0
|
24
|
+
spec.add_development_dependency 'bundler'
|
25
|
+
spec.add_development_dependency 'rake', '~> 10.2'
|
26
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
27
|
+
|
28
|
+
if spec.respond_to?(:metadata)
|
29
|
+
spec.metadata['changelog_uri'] = 'https://github.com/RubyMoney/monetize/blob/master/CHANGELOG.md'
|
30
|
+
spec.metadata['source_code_uri'] = 'https://github.com/RubyMoney/monetize/'
|
31
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/RubyMoney/monetize/issues'
|
32
|
+
end
|
27
33
|
end
|
@@ -31,7 +31,7 @@ describe Monetize, 'core extensions' do
|
|
31
31
|
expect(money.cents).to eq 100_37
|
32
32
|
expect(money.currency).to eq Money.default_currency
|
33
33
|
|
34
|
-
money = BigDecimal
|
34
|
+
money = BigDecimal('1234').to_money
|
35
35
|
expect(money.cents).to eq 1_234_00
|
36
36
|
expect(money.currency).to eq Money.default_currency
|
37
37
|
end
|
@@ -67,7 +67,8 @@ describe Monetize, 'core extensions' do
|
|
67
67
|
'-1,000' => Money.new(-1_000_00),
|
68
68
|
'1,000.5' => Money.new(1_000_50),
|
69
69
|
'1,000.51' => Money.new(1_000_51),
|
70
|
-
'1,000.505' => Money.new(
|
70
|
+
'1,000.505' => Money.new(1_000_50), # ROUND_HALF_EVEN default bankers rounding
|
71
|
+
'1,000.515' => Money.new(1_000_52), # ROUND_HALF_EVEN default bankers rounding
|
71
72
|
'1,000.504' => Money.new(1_000_50),
|
72
73
|
'1,000.0000' => Money.new(1_000_00),
|
73
74
|
'1,000.5000' => Money.new(1_000_50),
|
@@ -149,6 +150,28 @@ describe Monetize, 'core extensions' do
|
|
149
150
|
expect('1'.to_money('JPY')).to eq Money.new(1, 'JPY')
|
150
151
|
expect('1.5'.to_money('KWD').cents).to eq 1_500
|
151
152
|
end
|
153
|
+
|
154
|
+
it 'respects Money.rounding_mode' do
|
155
|
+
expect('1.009'.to_money).to eq(Money.new(1_01))
|
156
|
+
|
157
|
+
Money.rounding_mode(BigDecimal::ROUND_DOWN) do
|
158
|
+
expect('1.009'.to_money).to eq(Money.new(1_00))
|
159
|
+
end
|
160
|
+
|
161
|
+
expect('1.001'.to_money).to eq(Money.new(1_00))
|
162
|
+
|
163
|
+
Money.rounding_mode(BigDecimal::ROUND_UP) do
|
164
|
+
expect('1.001'.to_money).to eq(Money.new(1_01))
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'produces results similar to Money.from_amount for all the rounding edge cases' do
|
169
|
+
(1_000..1_010).each do |amount|
|
170
|
+
amount = amount.to_f / 1000
|
171
|
+
|
172
|
+
expect(amount.to_s.to_money).to eq(Money.from_amount(amount))
|
173
|
+
end
|
174
|
+
end
|
152
175
|
end
|
153
176
|
|
154
177
|
describe '#to_currency' do
|
@@ -182,6 +205,14 @@ describe Monetize, 'core extensions' do
|
|
182
205
|
end
|
183
206
|
end
|
184
207
|
|
208
|
+
context 'when currency argument is a hash' do
|
209
|
+
subject(:hash) { {cents: 100, currency: {iso_code: 'EUR'}} }
|
210
|
+
|
211
|
+
it 'converts Hash to Money using iso_code from currency hash' do
|
212
|
+
expect(hash.to_money).to eq(Money.new(100, 'EUR'))
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
185
216
|
context 'when no currency is passed' do
|
186
217
|
subject(:hash) { {cents: 123} }
|
187
218
|
|
@@ -191,6 +222,14 @@ describe Monetize, 'core extensions' do
|
|
191
222
|
expect(hash.to_money('USD')).to eq(Money.new(123, 'USD'))
|
192
223
|
end
|
193
224
|
end
|
225
|
+
|
226
|
+
context "when key name is 'fractional'" do
|
227
|
+
subject(:hash) { {fractional: 100} }
|
228
|
+
|
229
|
+
it 'converts Hash to Money, interpreting fractional as cents' do
|
230
|
+
expect(hash.to_money).to eq(Money.new(100, 'USD'))
|
231
|
+
end
|
232
|
+
end
|
194
233
|
end
|
195
234
|
end
|
196
235
|
|
data/spec/monetize_spec.rb
CHANGED
@@ -56,76 +56,78 @@ describe Monetize do
|
|
56
56
|
Monetize.assume_from_symbol = false
|
57
57
|
end
|
58
58
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
59
|
+
Monetize::Parser::CURRENCY_SYMBOLS.each_pair do |symbol, iso_code|
|
60
|
+
context iso_code do
|
61
|
+
let(:currency) { Money::Currency.find(iso_code) }
|
62
|
+
let(:amount) { 5_95 }
|
63
|
+
let(:amount_in_units) { amount.to_f / currency.subunit_to_unit }
|
64
|
+
|
65
|
+
it 'ensures correct amount calculations for test' do
|
66
|
+
expect(amount_in_units * currency.subunit_to_unit).to eq(amount)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "parses formatted inputs with #{iso_code} passed as a symbol" do
|
70
|
+
expect(Monetize.parse("#{symbol}#{amount_in_units}")).to eq Money.new(amount, iso_code)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "parses formatted inputs with #{iso_code} symbol is after the amount" do
|
74
|
+
expect(Monetize.parse("#{amount_in_units}#{symbol}")).to eq Money.new(amount, iso_code)
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'prefix' do
|
78
|
+
it 'parses formatted inputs with plus sign and currency as a symbol' do
|
79
|
+
expect(Monetize.parse("+#{symbol}#{amount_in_units}")).to eq Money.new(amount, iso_code)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'parses formatted inputs with minus sign and currency as a symbol' do
|
83
|
+
expect(Monetize.parse("-#{symbol}#{amount_in_units}")).to eq Money.new(-amount, iso_code)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'postfix' do
|
88
|
+
it 'parses formatted inputs with currency symbol and postfix minus sign' do
|
89
|
+
expect(Monetize.parse("#{symbol}#{amount_in_units}-")).to eq Money.new(-amount, iso_code)
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'parses formatted inputs with currency symbol and postfix plus sign' do
|
93
|
+
expect(Monetize.parse("#{symbol}#{amount_in_units}+")).to eq Money.new(amount, iso_code)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'amount suffixes' do
|
98
|
+
it 'parses formatted inputs with amounts given with suffixes' do
|
99
|
+
expect(Monetize.parse("#{symbol}1.26K")).to eq Money.new(1_260 * currency.subunit_to_unit, iso_code)
|
100
|
+
expect(Monetize.parse("#{symbol}126.36M")).to eq Money.new(126_360_000 * currency.subunit_to_unit, iso_code)
|
101
|
+
expect(Monetize.parse("#{symbol}.45B")).to eq Money.new(450_000_000 * currency.subunit_to_unit, iso_code)
|
102
|
+
expect(Monetize.parse("-#{symbol}2.45B")).to eq Money.new(-2_450_000_000 * currency.subunit_to_unit, iso_code)
|
103
|
+
expect(Monetize.parse("#{symbol}1.65T")).to eq Money.new(1_650_000_000_000 * currency.subunit_to_unit, iso_code)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'parses formatted inputs with symbol and surrounding spaces' do
|
108
|
+
expect(Monetize.parse(" #{symbol}#{amount_in_units} ")).to eq Money.new(amount, iso_code)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'parses formatted inputs without currency detection when overridden' do
|
112
|
+
expect(Monetize.parse("#{symbol}5.95", nil, assume_from_symbol: false)).to eq Money.new(amount, 'USD')
|
113
|
+
end
|
114
|
+
end
|
89
115
|
end
|
90
116
|
|
91
117
|
it 'should assume default currency if not a recognised symbol' do
|
92
118
|
expect(Monetize.parse('L9.99')).to eq Money.new(999, 'USD')
|
93
119
|
end
|
94
120
|
|
95
|
-
it '
|
96
|
-
expect(Monetize.parse('
|
97
|
-
|
98
|
-
|
99
|
-
it 'parses formatted inputs with minus sign and currency symbol' do
|
100
|
-
expect(Monetize.parse('-€9.99')).to eq Money.new(-999, 'EUR')
|
101
|
-
expect(Monetize.parse('-£9.99')).to eq Money.new(-999, 'GBP')
|
102
|
-
expect(Monetize.parse('-R$R9.99')).to eq Money.new(-999, 'BRL')
|
103
|
-
expect(Monetize.parse('-¥999')).to eq Money.new(-999, 'JPY')
|
104
|
-
expect(Monetize.parse('-C$9.99')).to eq Money.new(-999, 'CAD')
|
105
|
-
end
|
106
|
-
|
107
|
-
it 'parses formatted inputs with plus and GBP passed as symbol' do
|
108
|
-
expect(Monetize.parse('+€9.99')).to eq Money.new(999, 'EUR')
|
109
|
-
expect(Monetize.parse('+£9.99')).to eq Money.new(999, 'GBP')
|
110
|
-
expect(Monetize.parse('+R$R9.99')).to eq Money.new(999, 'BRL')
|
111
|
-
expect(Monetize.parse('+¥999')).to eq Money.new(999, 'JPY')
|
112
|
-
expect(Monetize.parse('+C$9.99')).to eq Money.new(999, 'CAD')
|
113
|
-
end
|
114
|
-
|
115
|
-
it 'parses formatted inputs with currency symbol and postfix minus sign' do
|
116
|
-
expect(Monetize.parse('€9.99-')).to eq Money.new(-999, 'EUR')
|
117
|
-
end
|
118
|
-
|
119
|
-
it 'parses formatted inputs with currency symbol and postfix plus sign' do
|
120
|
-
expect(Monetize.parse('€9.99+')).to eq Money.new(999, 'EUR')
|
121
|
+
it 'ignores ZAR symbols that is part of a text' do
|
122
|
+
expect(Monetize.parse('EUR 9.99')).to eq Money.new(999, 'EUR')
|
123
|
+
expect(Monetize.parse('9.99 EUR')).to eq Money.new(999, 'EUR')
|
121
124
|
end
|
122
125
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
expect(Monetize.parse('€1.65T')).to eq Money.new(1_650_000_000_000_00, 'EUR')
|
126
|
+
context 'negatives' do
|
127
|
+
it 'ignores the ambiguous kr symbol' do
|
128
|
+
# Could mean either of DKK, EEK, ISK, NOK, SEK
|
129
|
+
expect(Monetize.parse('kr9.99')).to eq Money.new(999, 'USD')
|
130
|
+
end
|
129
131
|
end
|
130
132
|
end
|
131
133
|
|
@@ -158,6 +160,18 @@ describe Monetize do
|
|
158
160
|
it 'should opt out by default' do
|
159
161
|
expect(Monetize.assume_from_symbol).to be_falsy
|
160
162
|
end
|
163
|
+
|
164
|
+
context 'ISO code' do
|
165
|
+
it 'parses currency given as ISO code' do
|
166
|
+
expect('20.00 USD'.to_money).to eq Money.new(20_00, 'USD')
|
167
|
+
expect('20.00 EUR'.to_money).to eq Money.new(20_00, 'EUR')
|
168
|
+
expect('20.00 GBP'.to_money).to eq Money.new(20_00, 'GBP')
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'raises an error if currency code is invalid' do
|
172
|
+
expect { '20.00 OMG'.to_money }.to raise_error Monetize::ParseError
|
173
|
+
end
|
174
|
+
end
|
161
175
|
end
|
162
176
|
|
163
177
|
it 'parses USD-formatted inputs under $10' do
|
@@ -177,10 +191,16 @@ describe Monetize do
|
|
177
191
|
expect(Monetize.parse('1,111,234,567.89')).to eq Money.new(1_111_234_567_89, 'USD')
|
178
192
|
end
|
179
193
|
|
180
|
-
it '
|
181
|
-
expect
|
182
|
-
expect
|
183
|
-
expect
|
194
|
+
it 'parses DKK-formatted inputs' do
|
195
|
+
expect(Monetize.parse('kr.123,45', 'DKK')).to eq Money.new(123_45, 'DKK')
|
196
|
+
expect(Monetize.parse('kr.123.45', 'DKK')).to eq Money.new(123_45, 'DKK')
|
197
|
+
expect(Monetize.parse('kr.45k', 'DKK')).to eq Money.new(45_000_00, 'DKK')
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'returns nil if input is a price range' do
|
201
|
+
expect(Monetize.parse('$5.95-10.95')).to be_nil
|
202
|
+
expect(Monetize.parse('$5.95 - 10.95')).to be_nil
|
203
|
+
expect(Monetize.parse('$5.95 - $10.95')).to be_nil
|
184
204
|
end
|
185
205
|
|
186
206
|
it 'does not return a price for completely invalid input' do
|
@@ -197,8 +217,16 @@ describe Monetize do
|
|
197
217
|
expect(Monetize.parse('$5.95-')).to eq five_ninety_five
|
198
218
|
end
|
199
219
|
|
200
|
-
it '
|
201
|
-
expect
|
220
|
+
it 'returns nil when unable to detect polarity' do
|
221
|
+
expect(Monetize.parse('-$5.95-')).to be_nil
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'returns nil when more than 2 digit separators are used' do
|
225
|
+
expect(Monetize.parse("123.34,56'89 EUR")).to be_nil
|
226
|
+
end
|
227
|
+
|
228
|
+
it 'parses correctly strings with repeated digit separator' do
|
229
|
+
expect(Monetize.parse('19.12.89', 'EUR')).to eq Money.new(191_289_00, 'EUR')
|
202
230
|
end
|
203
231
|
|
204
232
|
it 'parses correctly strings with exactly 3 decimal digits' do
|
@@ -210,6 +238,32 @@ describe Monetize do
|
|
210
238
|
Monetize.enforce_currency_delimiters = false
|
211
239
|
end
|
212
240
|
|
241
|
+
context 'Money object attempting to be parsed' do
|
242
|
+
let(:money) { Money.new(595, 'GBP') }
|
243
|
+
|
244
|
+
it 'returns the original Money object' do
|
245
|
+
expect(Monetize.parse(money)).to eq money
|
246
|
+
expect(Monetize.parse(money).currency).to eq 'GBP'
|
247
|
+
expect(Monetize.parse(money).cents).to eq 595
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
context 'parsing an instance of Numeric class' do
|
252
|
+
let(:integer) { 10 }
|
253
|
+
let(:float) { 10.0 }
|
254
|
+
let(:big_decimal) { BigDecimal('10') }
|
255
|
+
|
256
|
+
[:integer, :float, :big_decimal].each do |type|
|
257
|
+
it "returns a new Money object based on the #{type} input" do
|
258
|
+
money = Monetize.parse(send(type), 'USD')
|
259
|
+
|
260
|
+
expect(money).to be_instance_of(Money)
|
261
|
+
expect(money.currency).to eq('USD')
|
262
|
+
expect(money.cents).to eq(10_00)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
213
267
|
context 'custom currencies with 4 decimal places' do
|
214
268
|
before :each do
|
215
269
|
Money::Currency.register(JSON.parse(bar, symbolize_names: true))
|
@@ -265,6 +319,18 @@ describe Monetize do
|
|
265
319
|
end
|
266
320
|
end
|
267
321
|
|
322
|
+
describe '.parse!' do
|
323
|
+
it 'does not return a price if there is a price range' do
|
324
|
+
expect { Monetize.parse!('$5.95-10.95') }.to raise_error Monetize::ParseError
|
325
|
+
expect { Monetize.parse!('$5.95 - 10.95') }.to raise_error Monetize::ParseError
|
326
|
+
expect { Monetize.parse!('$5.95 - $10.95') }.to raise_error Monetize::ParseError
|
327
|
+
end
|
328
|
+
|
329
|
+
it 'raises ArgumentError when unable to detect polarity' do
|
330
|
+
expect { Monetize.parse!('-$5.95-') }.to raise_error Monetize::ParseError
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
268
334
|
describe '.parse_collection' do
|
269
335
|
it 'parses into a Money::Collection' do
|
270
336
|
expect(Monetize.parse_collection('$7')).to be_a Monetize::Collection
|
@@ -287,6 +353,10 @@ describe Monetize do
|
|
287
353
|
expect(collection.first).to eq Monetize.parse('$4')
|
288
354
|
expect(collection.last).to eq Monetize.parse('$10')
|
289
355
|
end
|
356
|
+
|
357
|
+
it 'raises an error if argument is invalid' do
|
358
|
+
expect { Monetize.parse_collection(nil) }.to raise_error Monetize::ArgumentError
|
359
|
+
end
|
290
360
|
end
|
291
361
|
|
292
362
|
describe '.from_string' do
|
@@ -337,6 +407,10 @@ describe Monetize do
|
|
337
407
|
m = Monetize.from_fixnum(1, 'EUR')
|
338
408
|
expect(m.currency).to eq Money::Currency.wrap('EUR')
|
339
409
|
end
|
410
|
+
|
411
|
+
it 'is aliased as from_integer' do
|
412
|
+
expect(Monetize.from_integer(1)).to eq(Monetize.from_fixnum(1))
|
413
|
+
end
|
340
414
|
end
|
341
415
|
|
342
416
|
describe '.from_float' do
|
@@ -366,25 +440,31 @@ describe Monetize do
|
|
366
440
|
|
367
441
|
describe '.from_bigdecimal' do
|
368
442
|
it 'converts given amount to cents' do
|
369
|
-
expect(Monetize.from_bigdecimal(BigDecimal
|
370
|
-
expect(Monetize.from_bigdecimal(BigDecimal
|
371
|
-
expect(Monetize.from_bigdecimal(BigDecimal
|
443
|
+
expect(Monetize.from_bigdecimal(BigDecimal('1'))).to eq Money.new(1_00)
|
444
|
+
expect(Monetize.from_bigdecimal(BigDecimal('1'))).to eq Money.new(1_00, 'USD')
|
445
|
+
expect(Monetize.from_bigdecimal(BigDecimal('1'), 'EUR')).to eq Money.new(1_00, 'EUR')
|
372
446
|
end
|
373
447
|
|
374
448
|
it 'respects :subunit_to_unit currency property' do
|
375
|
-
expect(Monetize.from_bigdecimal(BigDecimal
|
376
|
-
expect(Monetize.from_bigdecimal(BigDecimal
|
377
|
-
expect(Monetize.from_bigdecimal(BigDecimal
|
449
|
+
expect(Monetize.from_bigdecimal(BigDecimal('1'), 'USD')).to eq Money.new(1_00, 'USD')
|
450
|
+
expect(Monetize.from_bigdecimal(BigDecimal('1'), 'TND')).to eq Money.new(1_000, 'TND')
|
451
|
+
expect(Monetize.from_bigdecimal(BigDecimal('1'), 'JPY')).to eq Money.new(1, 'JPY')
|
452
|
+
end
|
453
|
+
|
454
|
+
it 'respects rounding mode when rounding amount to the nearest cent' do
|
455
|
+
amount = BigDecimal('1.005')
|
456
|
+
|
457
|
+
expect(Monetize.from_bigdecimal(amount, 'USD')).to eq Money.from_amount(amount, 'USD')
|
378
458
|
end
|
379
459
|
|
380
460
|
it 'accepts a currency options' do
|
381
|
-
m = Monetize.from_bigdecimal(BigDecimal
|
461
|
+
m = Monetize.from_bigdecimal(BigDecimal('1'))
|
382
462
|
expect(m.currency).to eq Money.default_currency
|
383
463
|
|
384
|
-
m = Monetize.from_bigdecimal(BigDecimal
|
464
|
+
m = Monetize.from_bigdecimal(BigDecimal('1'), Money::Currency.wrap('EUR'))
|
385
465
|
expect(m.currency).to eq Money::Currency.wrap('EUR')
|
386
466
|
|
387
|
-
m = Monetize.from_bigdecimal(BigDecimal
|
467
|
+
m = Monetize.from_bigdecimal(BigDecimal('1'), 'EUR')
|
388
468
|
expect(m.currency).to eq Money::Currency.wrap('EUR')
|
389
469
|
end
|
390
470
|
|
@@ -398,11 +478,11 @@ describe Monetize do
|
|
398
478
|
end
|
399
479
|
|
400
480
|
it 'keeps precision' do
|
401
|
-
expect(Monetize.from_bigdecimal(BigDecimal
|
402
|
-
expect(Monetize.from_bigdecimal(BigDecimal
|
403
|
-
expect(Monetize.from_bigdecimal(BigDecimal
|
404
|
-
expect(Monetize.from_bigdecimal(BigDecimal
|
405
|
-
expect(Monetize.from_bigdecimal(BigDecimal
|
481
|
+
expect(Monetize.from_bigdecimal(BigDecimal('1'))).to eq Money.new(100)
|
482
|
+
expect(Monetize.from_bigdecimal(BigDecimal('1.23456'))).to eq Money.new(123.456)
|
483
|
+
expect(Monetize.from_bigdecimal(BigDecimal('-1.23456'))).to eq Money.new(-123.456)
|
484
|
+
expect(Monetize.from_bigdecimal(BigDecimal('1.23456'))).to eq Money.new(123.456, 'USD')
|
485
|
+
expect(Monetize.from_bigdecimal(BigDecimal('1.23456'), 'EUR')).to eq Money.new(123.456, 'EUR')
|
406
486
|
|
407
487
|
expect('1'.to_money).to eq Money.new(100)
|
408
488
|
expect('1.23456'.to_money).to eq Money.new(123.456)
|
@@ -417,19 +497,11 @@ describe Monetize do
|
|
417
497
|
it 'converts given amount to cents' do
|
418
498
|
expect(Monetize.from_numeric(1)).to eq Money.new(1_00)
|
419
499
|
expect(Monetize.from_numeric(1.0)).to eq Money.new(1_00)
|
420
|
-
expect(Monetize.from_numeric(BigDecimal
|
500
|
+
expect(Monetize.from_numeric(BigDecimal('1'))).to eq Money.new(1_00)
|
421
501
|
end
|
422
502
|
|
423
503
|
it 'raises ArgumentError with unsupported argument' do
|
424
|
-
expect { Monetize.from_numeric('100') }.to raise_error(ArgumentError)
|
425
|
-
end
|
426
|
-
|
427
|
-
it 'optimizes workload' do
|
428
|
-
expect(Monetize).to receive(:from_fixnum).with(1, 'USD').and_return(Money.new(1_00, 'USD'))
|
429
|
-
expect(Monetize.from_numeric(1, 'USD')).to eq Money.new(1_00, 'USD')
|
430
|
-
expect(Monetize).to receive(:from_bigdecimal).with(BigDecimal.new('1.0'), 'USD').
|
431
|
-
and_return(Money.new(1_00, 'USD'))
|
432
|
-
expect(Monetize.from_numeric(1.0, 'USD')).to eq Money.new(1_00, 'USD')
|
504
|
+
expect { Monetize.from_numeric('100') }.to raise_error(Monetize::ArgumentError)
|
433
505
|
end
|
434
506
|
|
435
507
|
it 'respects :subunit_to_unit currency property' do
|
@@ -457,6 +529,20 @@ describe Monetize do
|
|
457
529
|
end
|
458
530
|
|
459
531
|
describe '.extract_cents' do
|
532
|
+
it 'is deprecated' do
|
533
|
+
allow(Monetize).to receive(:warn)
|
534
|
+
|
535
|
+
Monetize.extract_cents('100')
|
536
|
+
|
537
|
+
expect(Monetize)
|
538
|
+
.to have_received(:warn)
|
539
|
+
.with('[DEPRECATION] Monetize.extract_cents is deprecated. Use Monetize.parse().cents')
|
540
|
+
end
|
541
|
+
|
542
|
+
it 'extracts cents from a given string' do
|
543
|
+
expect(Monetize.extract_cents('10.99')).to eq(1099)
|
544
|
+
end
|
545
|
+
|
460
546
|
it "correctly treats pipe marks '|' in input (regression test)" do
|
461
547
|
expect(Monetize.extract_cents('100|0')).to eq Monetize.extract_cents('100!0')
|
462
548
|
end
|
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: monetize
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.4
|
4
|
+
version: 1.9.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shane Emmons
|
8
|
+
- Anthony Dmitriyev
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2020-01-07 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: money
|
@@ -16,59 +17,60 @@ dependencies:
|
|
16
17
|
requirements:
|
17
18
|
- - "~>"
|
18
19
|
- !ruby/object:Gem::Version
|
19
|
-
version: '6.
|
20
|
+
version: '6.12'
|
20
21
|
type: :runtime
|
21
22
|
prerelease: false
|
22
23
|
version_requirements: !ruby/object:Gem::Requirement
|
23
24
|
requirements:
|
24
25
|
- - "~>"
|
25
26
|
- !ruby/object:Gem::Version
|
26
|
-
version: '6.
|
27
|
+
version: '6.12'
|
27
28
|
- !ruby/object:Gem::Dependency
|
28
29
|
name: bundler
|
29
30
|
requirement: !ruby/object:Gem::Requirement
|
30
31
|
requirements:
|
31
|
-
- - "
|
32
|
+
- - ">="
|
32
33
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
34
|
+
version: '0'
|
34
35
|
type: :development
|
35
36
|
prerelease: false
|
36
37
|
version_requirements: !ruby/object:Gem::Requirement
|
37
38
|
requirements:
|
38
|
-
- - "
|
39
|
+
- - ">="
|
39
40
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
41
|
+
version: '0'
|
41
42
|
- !ruby/object:Gem::Dependency
|
42
43
|
name: rake
|
43
44
|
requirement: !ruby/object:Gem::Requirement
|
44
45
|
requirements:
|
45
|
-
- - "
|
46
|
+
- - "~>"
|
46
47
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
48
|
+
version: '10.2'
|
48
49
|
type: :development
|
49
50
|
prerelease: false
|
50
51
|
version_requirements: !ruby/object:Gem::Requirement
|
51
52
|
requirements:
|
52
|
-
- - "
|
53
|
+
- - "~>"
|
53
54
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
55
|
+
version: '10.2'
|
55
56
|
- !ruby/object:Gem::Dependency
|
56
57
|
name: rspec
|
57
58
|
requirement: !ruby/object:Gem::Requirement
|
58
59
|
requirements:
|
59
60
|
- - "~>"
|
60
61
|
- !ruby/object:Gem::Version
|
61
|
-
version: 3.0
|
62
|
+
version: '3.0'
|
62
63
|
type: :development
|
63
64
|
prerelease: false
|
64
65
|
version_requirements: !ruby/object:Gem::Requirement
|
65
66
|
requirements:
|
66
67
|
- - "~>"
|
67
68
|
- !ruby/object:Gem::Version
|
68
|
-
version: 3.0
|
69
|
+
version: '3.0'
|
69
70
|
description: A library for converting various objects into `Money` objects.
|
70
71
|
email:
|
71
72
|
- shane@emmons.io
|
73
|
+
- anthony.dmitriyev@gmail.com
|
72
74
|
executables: []
|
73
75
|
extensions: []
|
74
76
|
extra_rdoc_files: []
|
@@ -83,14 +85,16 @@ files:
|
|
83
85
|
- LICENSE.txt
|
84
86
|
- README.md
|
85
87
|
- Rakefile
|
86
|
-
- lib/collection.rb
|
87
88
|
- lib/monetize.rb
|
89
|
+
- lib/monetize/collection.rb
|
88
90
|
- lib/monetize/core_extensions.rb
|
89
91
|
- lib/monetize/core_extensions/hash.rb
|
90
92
|
- lib/monetize/core_extensions/nil_class.rb
|
91
93
|
- lib/monetize/core_extensions/numeric.rb
|
92
94
|
- lib/monetize/core_extensions/string.rb
|
93
95
|
- lib/monetize/core_extensions/symbol.rb
|
96
|
+
- lib/monetize/errors.rb
|
97
|
+
- lib/monetize/parser.rb
|
94
98
|
- lib/monetize/version.rb
|
95
99
|
- monetize.gemspec
|
96
100
|
- spec/core_extensions_spec.rb
|
@@ -99,7 +103,10 @@ files:
|
|
99
103
|
homepage: https://github.com/RubyMoney/monetize
|
100
104
|
licenses:
|
101
105
|
- MIT
|
102
|
-
metadata:
|
106
|
+
metadata:
|
107
|
+
changelog_uri: https://github.com/RubyMoney/monetize/blob/master/CHANGELOG.md
|
108
|
+
source_code_uri: https://github.com/RubyMoney/monetize/
|
109
|
+
bug_tracker_uri: https://github.com/RubyMoney/monetize/issues
|
103
110
|
post_install_message:
|
104
111
|
rdoc_options: []
|
105
112
|
require_paths:
|
@@ -115,8 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
115
122
|
- !ruby/object:Gem::Version
|
116
123
|
version: '0'
|
117
124
|
requirements: []
|
118
|
-
|
119
|
-
rubygems_version: 2.4.5.1
|
125
|
+
rubygems_version: 3.0.3
|
120
126
|
signing_key:
|
121
127
|
specification_version: 4
|
122
128
|
summary: A library for converting various objects into `Money` objects.
|
@@ -124,4 +130,3 @@ test_files:
|
|
124
130
|
- spec/core_extensions_spec.rb
|
125
131
|
- spec/monetize_spec.rb
|
126
132
|
- spec/spec_helper.rb
|
127
|
-
has_rdoc:
|