monetize 1.3.1 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +32 -0
- data/CHANGELOG.md +9 -2
- data/Gemfile +2 -2
- data/Rakefile +13 -1
- data/lib/collection.rb +3 -5
- data/lib/monetize.rb +124 -94
- data/lib/monetize/core_extensions.rb +5 -5
- data/lib/monetize/version.rb +1 -1
- data/monetize.gemspec +16 -15
- data/spec/core_extensions_spec.rb +122 -125
- data/spec/monetize_spec.rb +240 -199
- data/spec/spec_helper.rb +3 -3
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f96f9f99c2fc04ab625a2977f370eff77d33696
|
4
|
+
data.tar.gz: 75817ce67a91ae35019f6ef3a3b290b937b64749
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 657c4f4be74370f9a8b2b1f4fcb91e60a013e9d2e01f39f95258a48c47710c5418767770af32ca61c19d52b0a94ccaa875fd3a4b7d8f21023ff7f4b036700c90
|
7
|
+
data.tar.gz: 4cb6265937f209b0797cc4c0d2e78b4fa27a9f27f9f045cfac8d00812dcb2ccb86072672897b7fb36f7337dab52cbb62c3e58739b9cdcdc9ec32b73393bedd9a
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
Documentation:
|
2
|
+
Enabled: false
|
3
|
+
Metrics/AbcSize:
|
4
|
+
Enabled: false
|
5
|
+
Metrics/ClassLength:
|
6
|
+
Enabled: false
|
7
|
+
Metrics/MethodLength:
|
8
|
+
Enabled: false
|
9
|
+
Metrics/ModuleLength:
|
10
|
+
Enabled: false
|
11
|
+
Style/AndOr:
|
12
|
+
Enabled: false
|
13
|
+
Style/ClassAndModuleChildren:
|
14
|
+
Enabled: false
|
15
|
+
Style/PerlBackrefs:
|
16
|
+
Enabled: false
|
17
|
+
|
18
|
+
Style/CollectionMethods:
|
19
|
+
Enabled: yes
|
20
|
+
Lint/LiteralInInterpolation:
|
21
|
+
Enabled: yes
|
22
|
+
|
23
|
+
Metrics/LineLength:
|
24
|
+
Max: 120
|
25
|
+
Metrics/ParameterLists:
|
26
|
+
CountKeywordArgs: false
|
27
|
+
Style/DotPosition:
|
28
|
+
EnforcedStyle: trailing
|
29
|
+
Style/EmptyElse:
|
30
|
+
EnforcedStyle: empty
|
31
|
+
Style/SpaceInsideHashLiteralBraces:
|
32
|
+
EnforcedStyle: no_space
|
data/CHANGELOG.md
CHANGED
@@ -1,14 +1,21 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
##
|
3
|
+
## 1.4.0
|
4
4
|
- Required Forwardable on Collection to resolve NameError [\#44](https://github.com/RubyMoney/monetize/issues/44)
|
5
5
|
- Add capability to parse currency amounts given with suffixes (K, M, B, and T)
|
6
6
|
|
7
7
|
## 1.3.0
|
8
8
|
- Add NilClass extension
|
9
9
|
- Added correct parsing of Brazilian Real $R symbol
|
10
|
-
- Add testing task for Brazilian Real parsing
|
10
|
+
- Add testing task for Brazilian Real parsing
|
11
11
|
- Add Lira Sign (₤) as a symbol for GBP
|
12
12
|
|
13
13
|
## 1.3.1
|
14
14
|
- Updated Money version dependency to 6.6
|
15
|
+
|
16
|
+
## master
|
17
|
+
- Fixed support for <code>Money.infinite_precision = true</code> in .to_money
|
18
|
+
- Add Rubocop config to project
|
19
|
+
- Reformat code to adapt to Rubocop guidelines
|
20
|
+
- Add config setting to always enforce currency delimiters
|
21
|
+
- Add rake console task
|
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -1,6 +1,18 @@
|
|
1
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
2
|
require 'rspec/core/rake_task'
|
3
3
|
|
4
4
|
task default: :spec
|
5
5
|
|
6
6
|
RSpec::Core::RakeTask.new
|
7
|
+
|
8
|
+
task :environment do
|
9
|
+
require_relative 'lib/monetize'
|
10
|
+
end
|
11
|
+
|
12
|
+
desc 'Start a console with library loaded'
|
13
|
+
task console: :environment do
|
14
|
+
require 'irb'
|
15
|
+
require 'irb/completion'
|
16
|
+
ARGV.clear
|
17
|
+
IRB.start
|
18
|
+
end
|
data/lib/collection.rb
CHANGED
@@ -19,7 +19,7 @@ module Monetize
|
|
19
19
|
if input.respond_to? :strip
|
20
20
|
@input = input.clone.strip
|
21
21
|
else
|
22
|
-
|
22
|
+
fail ArgumentError, 'Input must be a string'
|
23
23
|
end
|
24
24
|
|
25
25
|
@currency = currency
|
@@ -47,13 +47,11 @@ module Monetize
|
|
47
47
|
RANGE_SPLIT = /-/
|
48
48
|
|
49
49
|
def split_list
|
50
|
-
|
51
|
-
.map(&:strip)
|
50
|
+
@input.split(LIST_SPLIT).map(&:strip)
|
52
51
|
end
|
53
52
|
|
54
53
|
def split_range
|
55
|
-
|
56
|
-
.map(&:strip)
|
54
|
+
@input.split(RANGE_SPLIT).map(&:strip)
|
57
55
|
end
|
58
56
|
end
|
59
57
|
end
|
data/lib/monetize.rb
CHANGED
@@ -1,37 +1,42 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
3
|
+
require 'money'
|
4
|
+
require 'monetize/core_extensions'
|
5
|
+
require 'monetize/version'
|
6
|
+
require 'collection'
|
7
7
|
|
8
8
|
module Monetize
|
9
|
-
|
10
9
|
CURRENCY_SYMBOLS = {
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
10
|
+
'$' => 'USD',
|
11
|
+
'€' => 'EUR',
|
12
|
+
'£' => 'GBP',
|
13
|
+
'₤' => 'GBP',
|
14
|
+
'R$' => 'BRL',
|
15
|
+
'R' => 'ZAR',
|
16
|
+
'¥' => 'JPY',
|
17
|
+
'C$' => 'CAD'
|
19
18
|
}
|
20
19
|
|
21
20
|
MULTIPLIER_SUFFIXES = {
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
'K' => 3,
|
22
|
+
'M' => 6,
|
23
|
+
'B' => 9,
|
24
|
+
'T' => 12
|
26
25
|
}
|
27
26
|
MULTIPLIER_SUFFIXES.default = 0
|
28
|
-
MULTIPLIER_REGEXP = Regexp.new('
|
27
|
+
MULTIPLIER_REGEXP = Regexp.new(format('^(.*?\d)(%s)\b([^\d]*)$', MULTIPLIER_SUFFIXES.keys.join('|')), 'i')
|
29
28
|
|
30
29
|
# Class methods
|
31
30
|
class << self
|
32
31
|
# @attr_accessor [true, false] assume_from_symbol Use this to enable the
|
33
32
|
# ability to assume the currency from a passed symbol
|
34
33
|
attr_accessor :assume_from_symbol
|
34
|
+
|
35
|
+
# Monetize uses the delimiters set in the currency to separate integers from
|
36
|
+
# decimals, and to ignore thousands separators. In some corner cases,
|
37
|
+
# though, it will try to determine the correct separator by itself. Set this
|
38
|
+
# to true to enforce the delimiters set in the currency all the time.
|
39
|
+
attr_accessor :enforce_currency_delimiters
|
35
40
|
end
|
36
41
|
|
37
42
|
def self.parse(input, currency = Money.default_currency, options = {})
|
@@ -61,7 +66,7 @@ module Monetize
|
|
61
66
|
|
62
67
|
def self.from_fixnum(value, currency = Money.default_currency)
|
63
68
|
currency = Money::Currency.wrap(currency)
|
64
|
-
value
|
69
|
+
value *= currency.subunit_to_unit
|
65
70
|
Money.new(value, currency)
|
66
71
|
end
|
67
72
|
|
@@ -72,7 +77,7 @@ module Monetize
|
|
72
77
|
|
73
78
|
def self.from_bigdecimal(value, currency = Money.default_currency)
|
74
79
|
currency = Money::Currency.wrap(currency)
|
75
|
-
value
|
80
|
+
value *= currency.subunit_to_unit
|
76
81
|
value = value.round unless Money.infinite_precision
|
77
82
|
Money.new(value, currency)
|
78
83
|
end
|
@@ -85,99 +90,47 @@ module Monetize
|
|
85
90
|
value = BigDecimal.new(value.to_s)
|
86
91
|
from_bigdecimal(value, currency)
|
87
92
|
else
|
88
|
-
|
93
|
+
fail ArgumentError, "'value' should be a type of Numeric"
|
89
94
|
end
|
90
95
|
end
|
91
96
|
|
92
97
|
def self.extract_cents(input, currency = Money.default_currency)
|
93
|
-
|
94
|
-
multiplier_exp = MULTIPLIER_SUFFIXES[multiplier_suffix]
|
98
|
+
multiplier_exp, input = extract_multiplier(input)
|
95
99
|
|
96
100
|
num = input.gsub(/[^\d.,'-]/, '')
|
97
101
|
|
98
|
-
negative = num
|
99
|
-
|
100
|
-
decimal_char = currency.decimal_mark
|
101
|
-
|
102
|
-
num = num.sub(/^-|-$/, '') if negative
|
103
|
-
|
104
|
-
if num.include?('-')
|
105
|
-
raise ArgumentError, "Invalid currency amount (hyphen)"
|
106
|
-
end
|
102
|
+
negative, num = extract_sign(num)
|
107
103
|
|
108
104
|
num.chop! if num.match(/[\.|,]$/)
|
109
105
|
|
110
|
-
|
106
|
+
major, minor = extract_major_minor(num, currency)
|
111
107
|
|
112
|
-
|
113
|
-
when 0
|
114
|
-
major, minor = num, 0
|
115
|
-
when 2
|
116
|
-
thousands_separator, decimal_mark = used_delimiters.uniq
|
108
|
+
cents = major.to_i * currency.subunit_to_unit
|
117
109
|
|
118
|
-
|
119
|
-
min = 0 unless min
|
120
|
-
when 1
|
121
|
-
decimal_mark = used_delimiters.first
|
110
|
+
cents, minor = apply_multiplier(multiplier_exp, cents, minor)
|
122
111
|
|
123
|
-
|
124
|
-
major, minor = num.split(decimal_char)
|
125
|
-
else
|
126
|
-
if num.scan(decimal_mark).length > 1 # multiple matches; treat as decimal_mark
|
127
|
-
major, minor = num.gsub(decimal_mark, ''), 0
|
128
|
-
else
|
129
|
-
possible_major, possible_minor = num.split(decimal_mark)
|
130
|
-
possible_major ||= "0"
|
131
|
-
possible_minor ||= "00"
|
132
|
-
|
133
|
-
if possible_minor.length != 3 # thousands_separator
|
134
|
-
major, minor = possible_major, possible_minor
|
135
|
-
else
|
136
|
-
if possible_major.length > 3
|
137
|
-
major, minor = possible_major, possible_minor
|
138
|
-
else
|
139
|
-
if decimal_mark == '.'
|
140
|
-
major, minor = possible_major, possible_minor
|
141
|
-
else
|
142
|
-
major, minor = "#{possible_major}#{possible_minor}", 0
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
else
|
149
|
-
raise ArgumentError, "Invalid currency amount"
|
150
|
-
end
|
112
|
+
cents += set_minor_precision(minor, currency)
|
151
113
|
|
152
|
-
cents
|
114
|
+
apply_sign(negative, cents)
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
153
118
|
|
154
|
-
|
119
|
+
def self.apply_multiplier(multiplier_exp, cents, minor)
|
120
|
+
cents *= (10**multiplier_exp)
|
155
121
|
minor = minor.to_s + ('0' * multiplier_exp)
|
156
|
-
shift = minor[0
|
122
|
+
shift = minor[0...multiplier_exp].to_i * 100
|
157
123
|
cents += shift
|
158
|
-
minor = (minor[multiplier_exp
|
159
|
-
|
160
|
-
|
161
|
-
(minor + ("0" * currency.decimal_places))[0,currency.decimal_places].to_i
|
162
|
-
elsif minor.size > currency.decimal_places
|
163
|
-
if minor[currency.decimal_places,1].to_i >= 5
|
164
|
-
minor[0,currency.decimal_places].to_i+1
|
165
|
-
else
|
166
|
-
minor[0,currency.decimal_places].to_i
|
167
|
-
end
|
168
|
-
else
|
169
|
-
minor.to_i
|
170
|
-
end
|
171
|
-
|
172
|
-
cents += minor
|
124
|
+
minor = (minor[multiplier_exp..-1] || '')
|
125
|
+
[cents, minor]
|
126
|
+
end
|
173
127
|
|
128
|
+
def self.apply_sign(negative, cents)
|
174
129
|
negative ? cents * -1 : cents
|
175
130
|
end
|
176
131
|
|
177
|
-
private
|
178
|
-
|
179
132
|
def self.contains_currency_symbol?(amount)
|
180
|
-
|
133
|
+
amount =~ currency_symbol_regex
|
181
134
|
end
|
182
135
|
|
183
136
|
def self.compute_currency(amount)
|
@@ -189,10 +142,87 @@ module Monetize
|
|
189
142
|
end
|
190
143
|
end
|
191
144
|
|
145
|
+
def self.extract_major_minor(num, currency)
|
146
|
+
used_delimiters = num.scan(/[^\d]/).uniq
|
147
|
+
|
148
|
+
case used_delimiters.length
|
149
|
+
when 0
|
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'
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.extract_major_minor_with_single_delimiter(num, currency, delimiter)
|
162
|
+
if delimiter == currency.decimal_mark
|
163
|
+
split_major_minor(num, delimiter)
|
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)
|
168
|
+
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
|
+
|
178
|
+
if possible_minor.length != 3 or possible_major.length > 3 or delimiter == '.'
|
179
|
+
# Doesn't look like thousands separator
|
180
|
+
[possible_major, possible_minor]
|
181
|
+
else
|
182
|
+
["#{possible_major}#{possible_minor}", '00']
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def self.extract_multiplier(input)
|
188
|
+
if (matches = MULTIPLIER_REGEXP.match(input))
|
189
|
+
multiplier_suffix = matches[2].upcase
|
190
|
+
[MULTIPLIER_SUFFIXES[multiplier_suffix], "#{$1}#{$3}"]
|
191
|
+
else
|
192
|
+
[0, input]
|
193
|
+
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
|
+
|
192
202
|
def self.regex_safe_symbols
|
193
|
-
CURRENCY_SYMBOLS.keys.map { |key|
|
194
|
-
|
195
|
-
|
203
|
+
CURRENCY_SYMBOLS.keys.map { |key| Regexp.escape(key) }.join('|')
|
204
|
+
end
|
205
|
+
|
206
|
+
def self.set_minor_precision(minor, currency)
|
207
|
+
if Money.infinite_precision
|
208
|
+
(BigDecimal.new(minor) / (10**minor.size)) * currency.subunit_to_unit
|
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
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def self.split_major_minor(num, delimiter)
|
223
|
+
major, minor = num.split(delimiter)
|
224
|
+
minor = '00' unless minor
|
225
|
+
[major, minor]
|
196
226
|
end
|
197
227
|
|
198
228
|
def self.currency_symbol_regex
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
3
|
+
require 'monetize/core_extensions/nil_class'
|
4
|
+
require 'monetize/core_extensions/numeric'
|
5
|
+
require 'monetize/core_extensions/string'
|
6
|
+
require 'monetize/core_extensions/symbol'
|
7
|
+
require 'monetize/core_extensions/hash'
|
data/lib/monetize/version.rb
CHANGED
data/monetize.gemspec
CHANGED
@@ -1,26 +1,27 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
lib = File.expand_path(
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
4
|
+
require 'monetize/version'
|
5
|
+
require 'English'
|
5
6
|
|
6
7
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
8
|
+
spec.name = 'monetize'
|
8
9
|
spec.version = Monetize::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
11
|
-
spec.description =
|
12
|
-
spec.summary =
|
13
|
-
spec.homepage =
|
14
|
-
spec.license =
|
10
|
+
spec.authors = ['Shane Emmons']
|
11
|
+
spec.email = ['shane@emmons.io']
|
12
|
+
spec.description = 'A library for converting various objects into `Money` objects.'
|
13
|
+
spec.summary = 'A library for converting various objects into `Money` objects.'
|
14
|
+
spec.homepage = 'https://github.com/RubyMoney/monetize'
|
15
|
+
spec.license = 'MIT'
|
15
16
|
|
16
|
-
spec.files = `git ls-files`.split(
|
17
|
+
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
17
18
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
19
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
-
spec.require_paths = [
|
20
|
+
spec.require_paths = ['lib']
|
20
21
|
|
21
|
-
spec.add_dependency
|
22
|
+
spec.add_dependency 'money', '~> 6.7'
|
22
23
|
|
23
|
-
spec.add_development_dependency
|
24
|
-
spec.add_development_dependency
|
25
|
-
spec.add_development_dependency
|
24
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
25
|
+
spec.add_development_dependency 'rake'
|
26
|
+
spec.add_development_dependency 'rspec', '~> 3.0.0.beta1'
|
26
27
|
end
|