minting 1.1.2 → 1.3.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
  SHA256:
3
- metadata.gz: 9506e71f742812607fef378d4df90eaea6d2457f0d818b55b6e06b6167fa6876
4
- data.tar.gz: 99b200de879dfb9ed4eaec21b02067a886c01d48116fcb669ca70b5dbf314264
3
+ metadata.gz: a7530401c0a722596508aab5aa587bc53a548698308f9d09ae34fac2ec671784
4
+ data.tar.gz: abe0501b11d4f9c77e2054443c12e1d4a5e2707fabbfac71f5bd4b8a7f5a3fb5
5
5
  SHA512:
6
- metadata.gz: 01b120551584f7f6327f2db19731367dd091bfb3e7b9db91d98cd2ff0b7e1db41d4c190acc66d8cd611fe7689ddb8872b89bebe9db6506e267df121c60fbe6f1
7
- data.tar.gz: 8cc6a7ffc780a555276298432de7c2929c5ffddadc4fc28b379a8c58245b70672a777e610e464b6ea485583a8b65eb7463b37e29ff422b9e065ecd1f565cb85a
6
+ metadata.gz: 31db7b1c31b0f1329342162cb8f868e46d14e703df5dd36898dfeb74281d674bc0f7ba0b9b367d8568f0751c09f3a219dec172ed87cc8cc4d906606550492034
7
+ data.tar.gz: 7090942000722ce81b302237a6b71850ef8a8ec477ded927bb5914f1e198552512ae11b0b1f46d38dd44ae0d6b4a2b1e7d34dab3ec35ea107b800c38e60c7a94
data/README.md CHANGED
@@ -91,6 +91,11 @@ price_in_euros.to_s(format: '%<symbol>2s%<amount>+10f') #=> " € +12.34"
91
91
 
92
92
  price.to_json # => "{ "currency": "USD", "amount": "9.99" }"
93
93
 
94
+ # Hash conversion
95
+
96
+ price.to_hash #=> {currency: "USD", amount: "9.99"}
97
+
98
+
94
99
  # Proportional allocation and split
95
100
 
96
101
  ten.split(3) #=> [[USD 3.34], [USD 3.33], [USD 3.33]]
data/Rakefile CHANGED
@@ -12,18 +12,23 @@ Rake::TestTask.new(:test) do |t|
12
12
  t.ruby_opts << '-rtest_helper.rb'
13
13
  end
14
14
 
15
- Rake::TestTask.new(:bench) do |t|
15
+ Rake::TestTask.new('bench') do |t|
16
16
  t.libs = %w[lib test]
17
17
  t.pattern = 'test/performance/*_benchmark.rb'
18
18
  end
19
19
 
20
+ Rake::TestTask.new('bench:parse') do |t|
21
+ t.libs = %w[lib test]
22
+ t.pattern = 'test/performance/parse_benchmark.rb'
23
+ t.ruby_opts << '-r test_helper.rb'
24
+ end
25
+
20
26
  Rake::TestTask.new('bench:edge') do |t|
21
27
  t.libs = %w[lib test]
22
28
  t.pattern = 'test/performance/algorithm_benchmark.rb'
23
29
  t.ruby_opts << '-r test_helper.rb'
24
30
  end
25
31
 
26
-
27
32
  Rake::TestTask.new('bench:regression') do |t|
28
33
  t.libs = %w[lib test]
29
34
  t.pattern = 'test/performance/regression_benchmark.rb'
@@ -3,12 +3,19 @@ module Mint
3
3
  #
4
4
  # @see https://www.iso.org/iso-4217-currency-codes.html
5
5
  class Currency
6
- attr_reader :code, :subunit, :symbol, :priority, :minimum_amount, :country, :name
6
+ attr_reader :code, :subunit, :symbol,
7
+ :country,
8
+ :fractional_multiplier, :minimum_amount,
9
+ :name, :priority
7
10
 
8
11
  def inspect
9
12
  "<Currency:(#{code} #{symbol} #{subunit})>"
10
13
  end
11
14
 
15
+ def normalize_amount(amount)
16
+ amount.to_r.round(subunit)
17
+ end
18
+
12
19
  private
13
20
 
14
21
  def initialize(code:, symbol:, subunit: 0, priority: 0, country: nil, name: nil)
@@ -18,7 +25,8 @@ module Mint
18
25
  @priority = priority.to_i
19
26
  @country = country
20
27
  @name = name
21
- @minimum_amount = 10r**-@subunit
28
+ @fractional_multiplier = 10**@subunit
29
+ @minimum_amount = 1r / fractional_multiplier
22
30
  freeze
23
31
  end
24
32
  end
@@ -9,11 +9,14 @@ module Mint
9
9
  # @raise [ArgumentError] if the currency code is not registered
10
10
  def self.money(amount, currency_code)
11
11
  currency = currency(currency_code)
12
- return Money.new(amount, currency) if currency
12
+ return Money.create(amount, currency) if currency
13
13
 
14
- raise ArgumentError, "Currency [#{currency_code}] not registered. Check Mint.currencies"
14
+ raise ArgumentError, "[#{currency.inspect}] is not a registered currency. Check Mint.currencies"
15
15
  end
16
16
 
17
+ # Returns default zero, no currency money
18
+ def self.zero = @zero ||= Money.new(0, Mint.currency('XXX'))
19
+
17
20
  # Finds a registered currency by its code, symbol,
18
21
  # or retrieves it directly if already a Currency object.
19
22
  #
@@ -34,11 +37,11 @@ module Mint
34
37
  #
35
38
  # @param code [String, Symbol] the unique currency code (e.g. 'USD', :EUR)
36
39
  # @param subunit [Integer] the decimal subunit precision (defaults to 2)
37
- # @param symbol [String] the display symbol (defaults to '$')
40
+ # @param symbol [String] the display symbol (defaults to '')
38
41
  # @param priority [Integer] parser precedence priority (defaults to 0)
39
42
  # @return [Currency] the registered or existing Currency instance
40
43
  # @raise [ArgumentError] if the code layout is invalid or register throws an error
41
- def self.register_currency(code:, subunit: 2, symbol: '$', priority: 0)
44
+ def self.register_currency(code:, subunit: 2, symbol: '', priority: 0)
42
45
  code = code.to_s
43
46
  currencies[code] || register_currency!(code:, subunit:, symbol:, priority:)
44
47
  end
@@ -63,9 +66,9 @@ module Mint
63
66
  "Currency: #{code} already registered"
64
67
  end
65
68
 
66
- currencies[code] = Currency.new(code:, subunit:, symbol:, priority:)
69
+ currency = currencies[code] = Currency.new(code:, subunit:, symbol:, priority:)
67
70
  @currency_symbols = nil
68
- currencies[code]
71
+ currency
69
72
  end
70
73
 
71
74
  # Returns the hash of all registered currencies.
@@ -14,7 +14,8 @@ module Mint
14
14
  raise ArgumentError, 'Need at least 1 proportion element' if proportions.empty?
15
15
  raise ArgumentError, 'Proportions total must not be zero' if whole.zero?
16
16
 
17
- amounts = proportions.map { |rate| (amount * rate.to_r / whole).round(currency.subunit) }
17
+ subunit = currency.subunit
18
+ amounts = proportions.map { |rate| (amount * rate.to_r / whole).round(subunit) }
18
19
  allocate_left_over!(amounts: amounts, left_over: amount - amounts.sum)
19
20
  end
20
21
 
@@ -44,11 +45,12 @@ module Mint
44
45
 
45
46
  def allocate_left_over!(amounts:, left_over:)
46
47
  if left_over.nonzero?
47
- minimum = left_over.positive? ? currency.minimum_amount : -currency.minimum_amount
48
+ minimum = currency.minimum_amount
49
+ minimum = -minimum if left_over.negative?
48
50
  last_slot = (left_over / minimum).to_i - 1
49
51
  (0..last_slot).each { |slot| amounts[slot] += minimum }
50
52
  end
51
- amounts.map { mint it }
53
+ amounts.map { Money.new(it, currency) }
52
54
  end
53
55
  end
54
56
  end
@@ -3,7 +3,7 @@ module Mint
3
3
  # Returns the absolute value of the monetary amount as a new {Money} instance.
4
4
  #
5
5
  # @return [Money] the absolute value
6
- def abs = mint(amount.abs)
6
+ def abs = mint(amount.abs)
7
7
 
8
8
  # Returns true if the monetary amount is less than zero.
9
9
  #
@@ -60,7 +60,7 @@ module Mint
60
60
  # @return [Money] the multiplied Money instance
61
61
  # @raise [TypeError] if multiplier is not Numeric or is a Money object
62
62
  def *(multiplicand)
63
- return mint(amount * multiplicand.to_r) if multiplicand.is_a?(Numeric)
63
+ return mint(amount * multiplicand) if multiplicand.is_a?(Numeric)
64
64
 
65
65
  raise TypeError, "#{self} can't be multiplied by #{multiplicand}"
66
66
  end
@@ -13,8 +13,6 @@ module Mint
13
13
  # Coerced Number contains the arithmetic logic for numeric compatible ops.
14
14
  # @private
15
15
  class CoercedNumber
16
- include Comparable
17
-
18
16
  # @private
19
17
  def initialize(value)
20
18
  @value = value
@@ -44,19 +42,21 @@ module Mint
44
42
  raise_coercion_error(:/, other)
45
43
  end
46
44
 
47
- # @private
45
+ # Only zero is dimensionless and comparable to Money.
46
+ # e.g. 0 < price is meaningful; 0.5 < price is not (what currency is 0.5?).
48
47
  def <=>(other)
49
- return nil if @value.nil? || other.nil?
50
48
  return @value <=> other.amount if @value.zero? || other.zero?
51
49
 
52
50
  raise_coercion_error(:<=>, other)
53
51
  end
54
52
 
55
- # @private
53
+ private
54
+
56
55
  def raise_coercion_error(operation, operand)
57
56
  raise TypeError,
58
- "#{self} #{operation} #{operand} : incompatible operands"
57
+ "#{@value} #{operation} #{operand} : incompatible operands"
59
58
  end
60
59
  end
60
+ private_constant :CoercedNumber
61
61
  end
62
62
  end
@@ -39,15 +39,17 @@ module Mint
39
39
  amount.to_i
40
40
  end
41
41
 
42
+ def to_hash
43
+ { currency: currency_code, amount: Kernel.format("%0.#{currency.subunit}f", amount) }
44
+ end
45
+
42
46
  # Serializes the money instance to a standard JSON object containing the amount and currency.
43
47
  # Highly optimized to run without external dependencies.
44
48
  #
45
49
  # @return [String] the JSON serialized string representation
46
50
  def to_json(*_args)
47
- subunit = currency.subunit
48
51
  Kernel.format(
49
- %({"currency": "#{currency_code}", "amount": "%0.#{subunit}f"}),
50
- amount
52
+ %({"currency": "#{currency_code}", "amount": "%0.#{currency.subunit}f"}), amount
51
53
  )
52
54
  end
53
55
 
@@ -10,13 +10,16 @@ module Mint
10
10
  # @param amount [Numeric] The monetary amount
11
11
  # @param currency [Currency] The currency object
12
12
  # @raise [ArgumentError] If amount is not numeric or currency is invalid
13
- def initialize(amount, currency)
13
+ def self.create(amount, currency)
14
14
  raise ArgumentError, 'amount must be Numeric' unless amount.is_a?(Numeric)
15
- raise ArgumentError, 'currency must be a Currency object' unless currency.is_a?(Currency)
16
15
 
17
- @amount = amount.to_r.round(currency.subunit)
18
- @currency = currency
19
- freeze
16
+ checked_currency = Mint.currency(currency)
17
+ unless checked_currency
18
+ raise ArgumentError,
19
+ "Currency not found (#{currency}). Check Mint.currencies"
20
+ end
21
+
22
+ new(checked_currency.normalize_amount(amount), checked_currency)
20
23
  end
21
24
 
22
25
  # Returns the ISO 3-letter currency code string.
@@ -24,6 +27,8 @@ module Mint
24
27
  # @return [String] the ISO currency code
25
28
  def currency_code = currency.code
26
29
 
30
+ def fractional = (amount * currency.fractional_multiplier).to_i
31
+
27
32
  # Generates a stable hash key for Money instances.
28
33
  #
29
34
  # @return [Integer] the calculated hash value
@@ -33,7 +38,8 @@ module Mint
33
38
  # @param new_amount [Numeric] The new amount
34
39
  # @return [Money] A new Money object or self
35
40
  def mint(new_amount)
36
- new_amount.to_r == amount ? self : Money.new(new_amount, currency)
41
+ new_amount = new_amount.to_r.round(currency.subunit)
42
+ new_amount == amount ? self : Money.new(new_amount, currency)
37
43
  end
38
44
 
39
45
  # Returns a standard developer-oriented string inspection of the Money object.
@@ -49,7 +55,15 @@ module Mint
49
55
  # @return [Boolean] true if currencies match, false otherwise
50
56
  def same_currency?(other) = other.respond_to?(:currency) && other.currency == currency
51
57
 
52
- # Returns default zero no currency money
53
- def self.zero = @zero ||= new(0, Mint.currencies('XXX'))
58
+ private
59
+
60
+ # Initializes a new Money object with the given amount and currency.
61
+ # @param amount [Numeric] The monetary amount
62
+ # @param currency [Currency] The currency object
63
+ def initialize(amount, currency)
64
+ @amount = amount
65
+ @currency = currency
66
+ freeze
67
+ end
54
68
  end
55
69
  end
@@ -25,14 +25,14 @@ module Mint
25
25
  currency = currency ? Mint.currency(currency) : parse_currency(input)
26
26
  raise ArgumentError, "Currency [#{currency}] not registered" unless currency
27
27
 
28
- amount = parse_amount(input)
28
+ amount = currency.normalize_amount(parse_amount(input))
29
29
  new(amount, currency)
30
30
  end
31
31
 
32
32
  # Extracts a numeric value from input that should only contain an amount.
33
33
  def self.parse_amount(input)
34
34
  # Remove any charater that is not a digit, comma or period
35
- numeric = input.scan(/[\d.\-,]/).join
35
+ numeric = input.scan(/[\d.,-]/).join
36
36
  numeric = normalize_separators(numeric)
37
37
  Rational(numeric)
38
38
  end
@@ -1,5 +1,5 @@
1
1
  # Root namespace for the Minting library.
2
2
  module Minting
3
3
  # Current version of the Minting gem.
4
- VERSION = '1.1.2'.freeze
4
+ VERSION = '1.3.0'.freeze
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minting
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gilson Ferraz