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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bdb994a311b7519b56c417e4496a36bd6e41c377
4
- data.tar.gz: f788b003d6412c6c3263b58736602f38be014c65
3
+ metadata.gz: 7f96f9f99c2fc04ab625a2977f370eff77d33696
4
+ data.tar.gz: 75817ce67a91ae35019f6ef3a3b290b937b64749
5
5
  SHA512:
6
- metadata.gz: 6c7435ede20d6b291da530a7c67876d347a2b484b46781ff5a7d040b4de6de6ce9c7f0378ef2179473e541c6b70223d6a793eb4c11911da0e38cc1e92a32e61a
7
- data.tar.gz: 12d53e41df08426527aceb44dd598bcf4dcdd926e6cf8d279ad8ce81e16f00b68a9b0503079df141dec6aa62de58e1f0ad7d3b4dee292a1b366f43df9c489571
6
+ metadata.gz: 657c4f4be74370f9a8b2b1f4fcb91e60a013e9d2e01f39f95258a48c47710c5418767770af32ca61c19d52b0a94ccaa875fd3a4b7d8f21023ff7f4b036700c90
7
+ data.tar.gz: 4cb6265937f209b0797cc4c0d2e78b4fa27a9f27f9f045cfac8d00812dcb2ccb86072672897b7fb36f7337dab52cbb62c3e58739b9cdcdc9ec32b73393bedd9a
@@ -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
@@ -1,14 +1,21 @@
1
1
  # Changelog
2
2
 
3
- ## Master
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
@@ -1,5 +1,5 @@
1
- source "https://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
- gem "coveralls", :require => false
3
+ gem 'coveralls', require: false
4
4
 
5
5
  gemspec
data/Rakefile CHANGED
@@ -1,6 +1,18 @@
1
- require "bundler/gem_tasks"
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
@@ -19,7 +19,7 @@ module Monetize
19
19
  if input.respond_to? :strip
20
20
  @input = input.clone.strip
21
21
  else
22
- raise ArgumentError 'Input must be a string'
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
- Array @input.split(LIST_SPLIT)
51
- .map(&:strip)
50
+ @input.split(LIST_SPLIT).map(&:strip)
52
51
  end
53
52
 
54
53
  def split_range
55
- Array @input.split(RANGE_SPLIT)
56
- .map(&:strip)
54
+ @input.split(RANGE_SPLIT).map(&:strip)
57
55
  end
58
56
  end
59
57
  end
@@ -1,37 +1,42 @@
1
1
  # encoding: utf-8
2
2
 
3
- require "money"
4
- require "monetize/core_extensions"
5
- require "monetize/version"
6
- require "collection"
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
- "$" => "USD",
12
- "" => "EUR",
13
- "£" => "GBP",
14
- "" => "GBP",
15
- "R$" => "BRL",
16
- "R" => "ZAR",
17
- "¥" => "JPY",
18
- "C$" => "CAD"
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
- "K" => 3,
23
- "M" => 6,
24
- "B" => 9,
25
- "T" => 12
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('\d(%s)\b[^\d]*$' % MULTIPLIER_SUFFIXES.keys.join('|'), 'i')
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 = value * currency.subunit_to_unit
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 = value * currency.subunit_to_unit
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
- raise ArgumentError, "'value' should be a type of Numeric"
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
- multiplier_suffix = (matches = MULTIPLIER_REGEXP.match(input)) ? matches[1].upcase : nil
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 =~ /^-|-$/ ? true : false
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
- used_delimiters = num.scan(/[^\d]/)
106
+ major, minor = extract_major_minor(num, currency)
111
107
 
112
- case used_delimiters.uniq.length
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
- major, minor = num.gsub(thousands_separator, '').split(decimal_mark)
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
- if decimal_char == decimal_mark
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 = major.to_i * currency.subunit_to_unit
114
+ apply_sign(negative, cents)
115
+ end
116
+
117
+ private
153
118
 
154
- cents *= (10 ** multiplier_exp)
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 ... multiplier_exp].to_i * 100
122
+ shift = minor[0...multiplier_exp].to_i * 100
157
123
  cents += shift
158
- minor = (minor[multiplier_exp .. -1] || '')
159
-
160
- minor = if minor.size < currency.decimal_places
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
- currency_symbol_regex === amount
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
- Regexp.escape(key)
195
- }.join('|')
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 "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"
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'
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Monetize
4
- VERSION = "1.3.1"
4
+ VERSION = '1.4.0'
5
5
  end
@@ -1,26 +1,27 @@
1
1
  # coding: utf-8
2
- lib = File.expand_path("../lib", __FILE__)
2
+ lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "monetize/version"
4
+ require 'monetize/version'
5
+ require 'English'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "monetize"
8
+ spec.name = 'monetize'
8
9
  spec.version = Monetize::VERSION
9
- spec.authors = ["Shane Emmons"]
10
- spec.email = ["shane@emmons.io"]
11
- spec.description = "A library for converting various objects into `Money` objects."
12
- spec.summary = "A library for converting various objects into `Money` objects."
13
- spec.homepage = "https://github.com/RubyMoney/monetize"
14
- spec.license = "MIT"
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 = ["lib"]
20
+ spec.require_paths = ['lib']
20
21
 
21
- spec.add_dependency "money", "~> 6.6"
22
+ spec.add_dependency 'money', '~> 6.7'
22
23
 
23
- spec.add_development_dependency "bundler", "~> 1.3"
24
- spec.add_development_dependency "rake"
25
- spec.add_development_dependency "rspec", "~> 3.0.0.beta1"
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