minting 1.4.0 → 1.5.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/README.md +14 -1
- data/Rakefile +1 -1
- data/doc/agents/recommendations.md +1 -1
- data/doc/agents/rubocop-issues.md +332 -0
- data/lib/minting/mint/currency.rb +8 -7
- data/lib/minting/mint/currency_store.rb +68 -0
- data/lib/minting/mint/refinements.rb +3 -1
- data/lib/minting/mint/registry.rb +15 -46
- data/lib/minting/mint.rb +4 -1
- data/lib/minting/money/allocation.rb +4 -1
- data/lib/minting/money/arithmetics.rb +25 -10
- data/lib/minting/money/coercion.rb +3 -1
- data/lib/minting/money/comparable.rb +5 -6
- data/lib/minting/money/constructors.rb +66 -0
- data/lib/minting/money/conversion.rb +4 -1
- data/lib/minting/money/formatting.rb +20 -19
- data/lib/minting/money/money.rb +30 -82
- data/lib/minting/money/parse.rb +16 -19
- data/lib/minting/money.rb +3 -0
- data/lib/minting/version.rb +3 -1
- data/lib/minting.rb +2 -0
- metadata +4 -19
- data/doc/Mint/Currency.html +0 -816
- data/doc/Mint/Money.html +0 -3471
- data/doc/Mint.html +0 -953
- data/doc/Minting.html +0 -142
- data/doc/_index.html +0 -136
- data/doc/class_list.html +0 -54
- data/doc/css/common.css +0 -1
- data/doc/css/full_list.css +0 -206
- data/doc/css/style.css +0 -1089
- data/doc/file.README.html +0 -379
- data/doc/file_list.html +0 -59
- data/doc/frames.html +0 -22
- data/doc/index.html +0 -379
- data/doc/js/app.js +0 -801
- data/doc/js/full_list.js +0 -334
- data/doc/js/jquery.js +0 -4
- data/doc/method_list.html +0 -470
- data/doc/top-level-namespace.html +0 -112
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Mint
|
|
4
|
+
# Money Arithmetics
|
|
2
5
|
class Money
|
|
3
6
|
# Returns the absolute value of the monetary amount as a new {Money} instance.
|
|
4
7
|
#
|
|
@@ -27,9 +30,10 @@ module Mint
|
|
|
27
30
|
# @return [Money] the sum of the addition
|
|
28
31
|
# @raise [TypeError] if addition involves a different currency or incompatible types
|
|
29
32
|
def +(addend)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
case addend
|
|
34
|
+
when 0 then return self
|
|
35
|
+
when Money then return mint(amount + addend.amount) if same_currency?(addend)
|
|
36
|
+
end
|
|
33
37
|
raise TypeError, "#{addend} can't be added to #{self}"
|
|
34
38
|
end
|
|
35
39
|
|
|
@@ -39,11 +43,10 @@ module Mint
|
|
|
39
43
|
# @return [Money] the difference of the subtraction
|
|
40
44
|
# @raise [TypeError] if subtraction involves a different currency or incompatible types
|
|
41
45
|
def -(subtrahend)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
case subtrahend
|
|
47
|
+
when 0 then return self
|
|
48
|
+
when Money then return mint(amount - subtrahend.amount) if same_currency?(subtrahend)
|
|
45
49
|
end
|
|
46
|
-
|
|
47
50
|
raise TypeError, "#{subtrahend} can't be subtracted from #{self}"
|
|
48
51
|
end
|
|
49
52
|
|
|
@@ -72,10 +75,22 @@ module Mint
|
|
|
72
75
|
# @raise [TypeError] if divisor is of incompatible type or different currency
|
|
73
76
|
# @raise [ZeroDivisionError] if division by zero is attempted
|
|
74
77
|
def /(divisor)
|
|
75
|
-
|
|
76
|
-
return amount / divisor
|
|
77
|
-
|
|
78
|
+
case divisor
|
|
79
|
+
when Numeric then return mint(amount / divisor)
|
|
80
|
+
when Money then return amount / divisor.amount if same_currency? divisor
|
|
81
|
+
end
|
|
78
82
|
raise TypeError, "#{self} can't be divided by #{divisor}"
|
|
79
83
|
end
|
|
84
|
+
|
|
85
|
+
# Performs exponentiation of the monetary value by a standard scalar Numeric.
|
|
86
|
+
#
|
|
87
|
+
# @param exponent [Numeric]
|
|
88
|
+
# @return [Money] reult of amount ** exponent
|
|
89
|
+
# @raise [TypeError] if exponent is not Numeric
|
|
90
|
+
def **(exponent)
|
|
91
|
+
return mint(amount**exponent) if exponent.is_a?(Numeric)
|
|
92
|
+
|
|
93
|
+
raise TypeError, "#{self} can't be powered by #{exponent}"
|
|
94
|
+
end
|
|
80
95
|
end
|
|
81
96
|
end
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Mint
|
|
4
|
+
# Implements the standard Ruby coercion protocol.
|
|
2
5
|
class Money
|
|
3
|
-
# Implements the standard Ruby coercion protocol.
|
|
4
6
|
# Allows {Money} to interact seamlessly as the right-hand operand in Numeric arithmetic.
|
|
5
7
|
#
|
|
6
8
|
# @param other [Numeric] the left-hand operand to coerce
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Mint
|
|
2
|
-
# :nodoc
|
|
3
4
|
# Comparison methods
|
|
4
5
|
class Money
|
|
5
6
|
include Comparable
|
|
@@ -28,12 +29,10 @@ module Mint
|
|
|
28
29
|
#
|
|
29
30
|
def <=>(other)
|
|
30
31
|
case other
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return amount <=> other.amount if currency == other.currency
|
|
32
|
+
in 0 then amount <=> other
|
|
33
|
+
in Mint::Money if same_currency?(other) then amount <=> other.amount
|
|
34
|
+
else raise TypeError, "#{inspect} can't be compared to #{other.inspect}"
|
|
35
35
|
end
|
|
36
|
-
raise TypeError, "#{inspect} can't be compared to #{other.inspect}"
|
|
37
36
|
end
|
|
38
37
|
|
|
39
38
|
def nonzero? = amount.nonzero?
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mint
|
|
4
|
+
# Money constructors
|
|
5
|
+
class Money
|
|
6
|
+
# Creates a new Money immutable object with the specified amount and currency
|
|
7
|
+
# @param amount [Numeric] The monetary amount
|
|
8
|
+
# @param currency [Currency] The currency object
|
|
9
|
+
# @raise [ArgumentError] If amount is not numeric or currency is invalid
|
|
10
|
+
def self.create(amount, currency)
|
|
11
|
+
raise ArgumentError, 'amount must be Numeric' unless amount.is_a?(Numeric)
|
|
12
|
+
|
|
13
|
+
checked_currency = Mint.currency(currency)
|
|
14
|
+
raise ArgumentError, "Currency not found (#{currency})" unless checked_currency
|
|
15
|
+
|
|
16
|
+
new(checked_currency.normalize_amount(amount), checked_currency)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Builds a Money from a fractional (smallest-unit) Integer amount.
|
|
20
|
+
# This is the inverse of {#fractional}: for USD, the fractional unit is
|
|
21
|
+
# 1 cent; for JPY it is 1 yen; for IQD it is 1 dinar (subunit 3).
|
|
22
|
+
#
|
|
23
|
+
# @param fractional [Integer] the amount expressed in the currency's
|
|
24
|
+
# smallest unit (e.g. cents). Must be an Integer to preserve exactness.
|
|
25
|
+
# @param currency [String, Symbol, Currency] the currency identifier
|
|
26
|
+
# @return [Money] the resulting Money instance
|
|
27
|
+
# @raise [ArgumentError] if +fractional+ is not an Integer or +currency+
|
|
28
|
+
# is not registered
|
|
29
|
+
#
|
|
30
|
+
# @example USD cents
|
|
31
|
+
# Money.from_fractional(123_456, 'USD') #=> [USD 1234.56]
|
|
32
|
+
# @example JPY (subunit 0)
|
|
33
|
+
# Money.from_fractional(1234, 'JPY') #=> [JPY 1234]
|
|
34
|
+
# @example Round trip
|
|
35
|
+
# m = Mint.money(9.99, 'USD')
|
|
36
|
+
# Money.from_fractional(m.fractional, 'USD') == m #=> true
|
|
37
|
+
def self.from_fractional(fractional, currency)
|
|
38
|
+
raise ArgumentError, 'fractional must be an Integer' unless fractional.is_a?(Integer)
|
|
39
|
+
|
|
40
|
+
checked_currency = Mint.currency(currency)
|
|
41
|
+
raise ArgumentError, "Currency not found (#{currency})" unless checked_currency
|
|
42
|
+
|
|
43
|
+
amount = Rational(fractional, checked_currency.fractional_multiplier)
|
|
44
|
+
new(amount, checked_currency)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns a new Money object with the specified amount, or self if unchanged
|
|
48
|
+
# @param new_amount [Numeric] The new amount
|
|
49
|
+
# @return [Money] A new Money object or self
|
|
50
|
+
def mint(new_amount)
|
|
51
|
+
new_amount = currency.normalize_amount(new_amount)
|
|
52
|
+
new_amount == amount ? self : Money.new(new_amount, currency)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# Initializes a new Money object with the given amount and currency.
|
|
58
|
+
# @param amount [Numeric] The monetary amount
|
|
59
|
+
# @param currency [Currency] The currency object
|
|
60
|
+
def initialize(amount, currency)
|
|
61
|
+
@amount = amount
|
|
62
|
+
@currency = currency
|
|
63
|
+
freeze
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bigdecimal'
|
|
1
4
|
require 'erb'
|
|
2
5
|
|
|
3
6
|
module Mint
|
|
4
7
|
# Conversion and serialization logic for {Money} instances.
|
|
5
8
|
class Money
|
|
6
|
-
# Converts the monetary amount to a
|
|
9
|
+
# Converts the monetary amount to a BigDecimal object.
|
|
7
10
|
#
|
|
8
11
|
# @return [BigDecimal] the decimal representation of the money amount
|
|
9
12
|
def to_d = amount.to_d 0
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Mint
|
|
2
4
|
# Formatting functionality for Money objects
|
|
3
5
|
class Money
|
|
4
|
-
# Keys accepted in the per-sign Hash form of `to_s(format:)`.
|
|
5
|
-
SIGN_FORMAT_KEYS = %i[positive negative zero].freeze
|
|
6
|
-
|
|
7
6
|
# Formats money as a string with customizable format, thousand delimiter, and decimal
|
|
8
7
|
#
|
|
9
8
|
# @param format [String, Hash] Either a Format string with placeholders
|
|
@@ -45,9 +44,12 @@ module Mint
|
|
|
45
44
|
# money.to_s(format: '%<symbol>s%<amount>010.2f') #=> "$0001234.56"
|
|
46
45
|
#
|
|
47
46
|
def to_s(format: '%<symbol>s%<amount>f', decimal: '.', thousand: ',', width: nil)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
validate_format_hash!(format)
|
|
47
|
+
case format
|
|
48
|
+
when {}, '', nil then raise ArgumentError, 'format must not be empty or null'
|
|
49
|
+
when Hash then validate_format_hash!(format)
|
|
50
|
+
when String # noop
|
|
51
|
+
else raise ArgumentError, 'Invalid format'
|
|
52
|
+
end
|
|
51
53
|
|
|
52
54
|
formatted = format_amount(format)
|
|
53
55
|
|
|
@@ -66,27 +68,26 @@ module Mint
|
|
|
66
68
|
private
|
|
67
69
|
|
|
68
70
|
def validate_format_hash!(format)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
unknown = format.keys - SIGN_FORMAT_KEYS
|
|
72
|
-
return if unknown.empty?
|
|
71
|
+
unknown = format.keys - %i[positive negative zero]
|
|
73
72
|
|
|
74
|
-
raise ArgumentError,
|
|
75
|
-
"Unknown format Hash key(s): #{unknown.inspect}. " \
|
|
76
|
-
"Valid keys are #{SIGN_FORMAT_KEYS.inspect}"
|
|
73
|
+
raise ArgumentError, "Unknown format parameter(s): #{unknown.inspect}. " unless unknown.empty?
|
|
77
74
|
end
|
|
78
75
|
|
|
79
76
|
def format_amount(format)
|
|
80
77
|
format = { positive: format } if format.is_a?(String)
|
|
81
|
-
|
|
78
|
+
positive_format = format[:positive]
|
|
79
|
+
negative_format = format[:negative]
|
|
80
|
+
zero_format = format[:zero]
|
|
82
81
|
|
|
83
|
-
if amount.negative? &&
|
|
84
|
-
format =
|
|
82
|
+
if amount.negative? && negative_format
|
|
83
|
+
format = negative_format
|
|
85
84
|
value = -amount
|
|
86
|
-
elsif amount.zero? &&
|
|
87
|
-
format =
|
|
85
|
+
elsif amount.zero? && zero_format
|
|
86
|
+
format = zero_format
|
|
87
|
+
value = amount
|
|
88
88
|
else
|
|
89
|
-
format =
|
|
89
|
+
format = positive_format
|
|
90
|
+
value = amount
|
|
90
91
|
end
|
|
91
92
|
format ||= '%<symbol>s%<amount>f'
|
|
92
93
|
|
data/lib/minting/money/money.rb
CHANGED
|
@@ -1,52 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Mint
|
|
4
|
+
# Money constructors
|
|
2
5
|
class Money
|
|
3
6
|
# The default display format pattern for formatting monetary values.
|
|
4
7
|
# Uses `%<symbol>s` for the currency symbol and `%<amount>f` for the rounded amount.
|
|
5
|
-
DEFAULT_FORMAT = '%<symbol>s%<amount>f'
|
|
8
|
+
DEFAULT_FORMAT = '%<symbol>s%<amount>f'
|
|
6
9
|
|
|
7
10
|
attr_reader :amount, :currency
|
|
8
11
|
|
|
9
|
-
# Creates a new Money immutable object with the specified amount and currency
|
|
10
|
-
# @param amount [Numeric] The monetary amount
|
|
11
|
-
# @param currency [Currency] The currency object
|
|
12
|
-
# @raise [ArgumentError] If amount is not numeric or currency is invalid
|
|
13
|
-
def self.create(amount, currency)
|
|
14
|
-
raise ArgumentError, 'amount must be Numeric' unless amount.is_a?(Numeric)
|
|
15
|
-
|
|
16
|
-
checked_currency = Mint.currency(currency)
|
|
17
|
-
raise ArgumentError, "Currency not found (#{currency})" unless checked_currency
|
|
18
|
-
|
|
19
|
-
new(checked_currency.normalize_amount(amount), checked_currency)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# Builds a Money from a fractional (smallest-unit) Integer amount.
|
|
23
|
-
# This is the inverse of {#fractional}: for USD, the fractional unit is
|
|
24
|
-
# 1 cent; for JPY it is 1 yen; for IQD it is 1 dinar (subunit 3).
|
|
25
|
-
#
|
|
26
|
-
# @param fractional [Integer] the amount expressed in the currency's
|
|
27
|
-
# smallest unit (e.g. cents). Must be an Integer to preserve exactness.
|
|
28
|
-
# @param currency [String, Symbol, Currency] the currency identifier
|
|
29
|
-
# @return [Money] the resulting Money instance
|
|
30
|
-
# @raise [ArgumentError] if +fractional+ is not an Integer or +currency+
|
|
31
|
-
# is not registered
|
|
32
|
-
#
|
|
33
|
-
# @example USD cents
|
|
34
|
-
# Money.from_fractional(123_456, 'USD') #=> [USD 1234.56]
|
|
35
|
-
# @example JPY (subunit 0)
|
|
36
|
-
# Money.from_fractional(1234, 'JPY') #=> [JPY 1234]
|
|
37
|
-
# @example Round trip
|
|
38
|
-
# m = Mint.money(9.99, 'USD')
|
|
39
|
-
# Money.from_fractional(m.fractional, 'USD') == m #=> true
|
|
40
|
-
def self.from_fractional(fractional, currency)
|
|
41
|
-
raise ArgumentError, 'fractional must be an Integer' unless fractional.is_a?(Integer)
|
|
42
|
-
|
|
43
|
-
checked_currency = Mint.currency(currency)
|
|
44
|
-
raise ArgumentError, "Currency not found (#{currency})" unless checked_currency
|
|
45
|
-
|
|
46
|
-
amount = Rational(fractional, checked_currency.fractional_multiplier)
|
|
47
|
-
new(amount, checked_currency)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
12
|
# Returns the ISO 3-letter currency code string.
|
|
51
13
|
#
|
|
52
14
|
# @return [String] the ISO currency code
|
|
@@ -59,14 +21,6 @@ module Mint
|
|
|
59
21
|
# @return [Integer] the calculated hash value
|
|
60
22
|
def hash = [amount, currency_code].hash
|
|
61
23
|
|
|
62
|
-
# Returns a new Money object with the specified amount, or self if unchanged
|
|
63
|
-
# @param new_amount [Numeric] The new amount
|
|
64
|
-
# @return [Money] A new Money object or self
|
|
65
|
-
def mint(new_amount)
|
|
66
|
-
new_amount = currency.normalize_amount(new_amount)
|
|
67
|
-
new_amount == amount ? self : Money.new(new_amount, currency)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
24
|
# Returns a standard developer-oriented string inspection of the Money object.
|
|
71
25
|
#
|
|
72
26
|
# @return [String] the formatted inspect representation
|
|
@@ -76,13 +30,17 @@ module Mint
|
|
|
76
30
|
|
|
77
31
|
# Helper method to verify if another object has the identical currency.
|
|
78
32
|
#
|
|
79
|
-
# @param other [
|
|
33
|
+
# @param other [Currency] the target currency to compare
|
|
80
34
|
# @return [Boolean] true if currencies match, false otherwise
|
|
81
|
-
def same_currency?(other) = other.
|
|
35
|
+
def same_currency?(other) = other.currency == currency
|
|
82
36
|
|
|
83
37
|
# Constrains +self+ to the inclusive range [+min+, +max+].
|
|
84
38
|
#
|
|
85
|
-
#
|
|
39
|
+
# Bounds may be:
|
|
40
|
+
# - nil meaning no boundary
|
|
41
|
+
# - same-currency {Money} or Range
|
|
42
|
+
# - Numeric amount, or Range
|
|
43
|
+
#
|
|
86
44
|
# Numeric is interpreted as an amount in +self+'s currency, so the common
|
|
87
45
|
# pricing idiom +price.clamp(0, 100)+ reads as "0 to 100 in the same
|
|
88
46
|
# currency as +price+".
|
|
@@ -91,11 +49,12 @@ module Mint
|
|
|
91
49
|
# allocated). When out of range, the nearest bound is returned as a new
|
|
92
50
|
# frozen {Money} in +self+'s currency.
|
|
93
51
|
#
|
|
94
|
-
# @param
|
|
95
|
-
# @param max [Money, Numeric] upper bound (inclusive)
|
|
52
|
+
# @param min_or_range [Money, Numeric, Range, nil] lower bound (inclusive), or range
|
|
53
|
+
# @param max [Money, Numeric, nil] upper bound (inclusive)
|
|
96
54
|
# @return [Money] +self+ if in range, otherwise the nearer bound
|
|
97
|
-
# @raise [ArgumentError] if +min+ or +max+ is not a Money or
|
|
98
|
-
# a Money operand has a different currency; if +min+ > +max
|
|
55
|
+
# @raise [ArgumentError] if +min+ or +max+ is not a Money, Numeric or nil; if
|
|
56
|
+
# a Money operand has a different currency; if +min+ > +max+;
|
|
57
|
+
# if min is a Range, and max is not nil
|
|
99
58
|
#
|
|
100
59
|
# @example In range
|
|
101
60
|
# Mint.money(5, 'USD').clamp(0, 10) #=> [USD 5.00] (returns self)
|
|
@@ -111,37 +70,26 @@ module Mint
|
|
|
111
70
|
#
|
|
112
71
|
# @example Subunit-0 currency (JPY)
|
|
113
72
|
# Mint.money(500, 'JPY').clamp(0, 100) #=> [JPY 100]
|
|
114
|
-
def clamp(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
when Money
|
|
118
|
-
raise(ArgumentError, "min currency must be: #{currency_code}") unless same_currency?(min)
|
|
73
|
+
def clamp(min_or_range, max = nil)
|
|
74
|
+
if min_or_range.is_a?(Range)
|
|
75
|
+
raise(ArgumentError, "Either amount range alone or two amounts accepted: #{max}") if max
|
|
119
76
|
|
|
120
|
-
min =
|
|
121
|
-
else
|
|
77
|
+
min, max = min_or_range.minmax
|
|
78
|
+
else
|
|
79
|
+
min = min_or_range
|
|
122
80
|
end
|
|
123
|
-
|
|
124
|
-
case max
|
|
125
|
-
when Numeric
|
|
126
|
-
when Money
|
|
127
|
-
raise(ArgumentError, "max currency must be: #{currency_code}") unless same_currency?(max)
|
|
128
|
-
|
|
129
|
-
max = max.amount
|
|
130
|
-
else raise(ArgumentError, 'max must be Numeric or Money')
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
mint(amount.clamp(min, max))
|
|
81
|
+
mint(amount.clamp(normalize_boundary(min), normalize_boundary(max)))
|
|
134
82
|
end
|
|
135
83
|
|
|
136
84
|
private
|
|
137
85
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
86
|
+
def normalize_boundary(boundary)
|
|
87
|
+
case boundary
|
|
88
|
+
in NilClass | Numeric then boundary
|
|
89
|
+
in Money if same_currency?(boundary) then boundary.amount
|
|
90
|
+
in Money then raise ArgumentError, "oundary currency must be: #{currency_code}"
|
|
91
|
+
else raise ArgumentError, "Boundary must be Numeric or Money #{boundary}"
|
|
92
|
+
end
|
|
145
93
|
end
|
|
146
94
|
end
|
|
147
95
|
end
|
data/lib/minting/money/parse.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Mint
|
|
2
|
-
#
|
|
4
|
+
# Money parser
|
|
3
5
|
class Money
|
|
4
6
|
# Parses a human-readable money string into a {Money} object.
|
|
5
7
|
#
|
|
@@ -22,7 +24,7 @@ module Mint
|
|
|
22
24
|
input = input.strip
|
|
23
25
|
raise ArgumentError, 'input cannot be empty' if input.empty?
|
|
24
26
|
|
|
25
|
-
currency =
|
|
27
|
+
currency = parse_currency(currency) || parse_currency(input)
|
|
26
28
|
raise ArgumentError, "Currency [#{currency}] not registered" unless currency
|
|
27
29
|
|
|
28
30
|
amount = currency.normalize_amount(parse_amount(input))
|
|
@@ -40,10 +42,8 @@ module Mint
|
|
|
40
42
|
# Converts locale-specific decimal/thousand separators into a plain decimal string.
|
|
41
43
|
def self.normalize_separators(numeric)
|
|
42
44
|
case [numeric.count(','), numeric.count('.')]
|
|
43
|
-
in [0, 0] | [0, 1] # Nothing to normalize (e.g. "1500" or "34.21").
|
|
44
|
-
|
|
45
|
-
in [1, 0] # Only one comma: decimal (e.g. 19,99 or 1,234).
|
|
46
|
-
numeric.tr(',', '.')
|
|
45
|
+
in [0, 0] | [0, 1] then numeric # Nothing to normalize (e.g. "1500" or "34.21").
|
|
46
|
+
in [1, 0] then numeric.tr(',', '.') # Only one comma: decimal (e.g. 19,99 or 1,234).
|
|
47
47
|
in [c, p] if c > 1 && p > 1 # Both separators appear multiple times
|
|
48
48
|
raise ArgumentError, "could not distinguish decimal and thousand separators in '#{numeric}'"
|
|
49
49
|
in [c, p] if c > 0 && p > 0 # Commas and dots: the rightmost one is the decimal separator.
|
|
@@ -58,22 +58,19 @@ module Mint
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def self.parse_currency(input)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
case input
|
|
62
|
+
when NilClass, Mint::Currency then return input
|
|
63
|
+
when String
|
|
64
|
+
# Prefer an explicit ISO 4217 code (e.g. "USD 1,234.56") over symbol matching.
|
|
65
|
+
currency = Mint.currency(input[/\b([A-Z]+)\b/, 1])
|
|
65
66
|
return currency if currency
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Fall back to registered symbols, longest first (HK$ before $).
|
|
69
|
-
Mint.currency_symbols.each do |symbol, currency|
|
|
70
|
-
next if symbol.empty?
|
|
71
67
|
|
|
72
|
-
|
|
68
|
+
# Fall back to registered symbols, longest first (HK$ before $).
|
|
69
|
+
Mint.currency_symbols.each do |symbol, currency|
|
|
70
|
+
return currency if input.include?(symbol)
|
|
71
|
+
end
|
|
73
72
|
end
|
|
74
|
-
|
|
75
|
-
raise ArgumentError,
|
|
76
|
-
'currency could not be detected; pass a currency code as the second argument'
|
|
73
|
+
raise ArgumentError, 'currency could not be detected; pass a currency code as the second argument'
|
|
77
74
|
end
|
|
78
75
|
|
|
79
76
|
private_class_method :parse_amount, :normalize_separators,
|
data/lib/minting/money.rb
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'minting/money/parse'
|
|
2
4
|
require 'minting/money/allocation'
|
|
3
5
|
require 'minting/money/arithmetics'
|
|
4
6
|
require 'minting/money/coercion'
|
|
5
7
|
require 'minting/money/comparable'
|
|
8
|
+
require 'minting/money/constructors'
|
|
6
9
|
require 'minting/money/conversion'
|
|
7
10
|
require 'minting/money/formatting'
|
|
8
11
|
require 'minting/money/money'
|
data/lib/minting/version.rb
CHANGED
data/lib/minting.rb
CHANGED
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.
|
|
4
|
+
version: 1.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gilson Ferraz
|
|
@@ -34,32 +34,16 @@ files:
|
|
|
34
34
|
- Rakefile
|
|
35
35
|
- bin/console
|
|
36
36
|
- bin/setup
|
|
37
|
-
- doc/Mint.html
|
|
38
|
-
- doc/Mint/Currency.html
|
|
39
|
-
- doc/Mint/Money.html
|
|
40
|
-
- doc/Minting.html
|
|
41
|
-
- doc/_index.html
|
|
42
37
|
- doc/agents/AGENTS.md
|
|
43
38
|
- doc/agents/copilot-instructions.md
|
|
44
39
|
- doc/agents/gemini_gem_evaluation.md
|
|
45
40
|
- doc/agents/recommendations.md
|
|
46
|
-
- doc/
|
|
47
|
-
- doc/css/common.css
|
|
48
|
-
- doc/css/full_list.css
|
|
49
|
-
- doc/css/style.css
|
|
50
|
-
- doc/file.README.html
|
|
51
|
-
- doc/file_list.html
|
|
52
|
-
- doc/frames.html
|
|
53
|
-
- doc/index.html
|
|
54
|
-
- doc/js/app.js
|
|
55
|
-
- doc/js/full_list.js
|
|
56
|
-
- doc/js/jquery.js
|
|
57
|
-
- doc/method_list.html
|
|
58
|
-
- doc/top-level-namespace.html
|
|
41
|
+
- doc/agents/rubocop-issues.md
|
|
59
42
|
- lib/minting.rb
|
|
60
43
|
- lib/minting/data/currencies.yaml
|
|
61
44
|
- lib/minting/mint.rb
|
|
62
45
|
- lib/minting/mint/currency.rb
|
|
46
|
+
- lib/minting/mint/currency_store.rb
|
|
63
47
|
- lib/minting/mint/refinements.rb
|
|
64
48
|
- lib/minting/mint/registry.rb
|
|
65
49
|
- lib/minting/money.rb
|
|
@@ -67,6 +51,7 @@ files:
|
|
|
67
51
|
- lib/minting/money/arithmetics.rb
|
|
68
52
|
- lib/minting/money/coercion.rb
|
|
69
53
|
- lib/minting/money/comparable.rb
|
|
54
|
+
- lib/minting/money/constructors.rb
|
|
70
55
|
- lib/minting/money/conversion.rb
|
|
71
56
|
- lib/minting/money/formatting.rb
|
|
72
57
|
- lib/minting/money/money.rb
|