monetize 1.3.1 → 1.4.0
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 +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
|