minting 1.3.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 +31 -3
- data/Rakefile +4 -4
- data/doc/agents/AGENTS.md +25 -0
- data/doc/agents/copilot-instructions.md +75 -0
- data/doc/agents/gemini_gem_evaluation.md +245 -0
- data/doc/agents/recommendations.md +335 -0
- data/doc/agents/rubocop-issues.md +332 -0
- data/lib/minting/mint/currency.rb +9 -8
- data/lib/minting/mint/currency_store.rb +68 -0
- data/lib/minting/mint/refinements.rb +3 -1
- data/lib/minting/mint/registry.rb +21 -61
- 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 +9 -17
- data/lib/minting/money/formatting.rb +42 -8
- data/lib/minting/money/money.rb +60 -34
- 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
- data/minting.gemspec +1 -0
- metadata +23 -2
data/lib/minting/money/money.rb
CHANGED
|
@@ -1,27 +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
|
-
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)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
12
|
# Returns the ISO 3-letter currency code string.
|
|
26
13
|
#
|
|
27
14
|
# @return [String] the ISO currency code
|
|
@@ -34,14 +21,6 @@ module Mint
|
|
|
34
21
|
# @return [Integer] the calculated hash value
|
|
35
22
|
def hash = [amount, currency_code].hash
|
|
36
23
|
|
|
37
|
-
# Returns a new Money object with the specified amount, or self if unchanged
|
|
38
|
-
# @param new_amount [Numeric] The new amount
|
|
39
|
-
# @return [Money] A new Money object or self
|
|
40
|
-
def mint(new_amount)
|
|
41
|
-
new_amount = new_amount.to_r.round(currency.subunit)
|
|
42
|
-
new_amount == amount ? self : Money.new(new_amount, currency)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
24
|
# Returns a standard developer-oriented string inspection of the Money object.
|
|
46
25
|
#
|
|
47
26
|
# @return [String] the formatted inspect representation
|
|
@@ -51,19 +30,66 @@ module Mint
|
|
|
51
30
|
|
|
52
31
|
# Helper method to verify if another object has the identical currency.
|
|
53
32
|
#
|
|
54
|
-
# @param other [
|
|
33
|
+
# @param other [Currency] the target currency to compare
|
|
55
34
|
# @return [Boolean] true if currencies match, false otherwise
|
|
56
|
-
def same_currency?(other) = other.
|
|
35
|
+
def same_currency?(other) = other.currency == currency
|
|
36
|
+
|
|
37
|
+
# Constrains +self+ to the inclusive range [+min+, +max+].
|
|
38
|
+
#
|
|
39
|
+
# Bounds may be:
|
|
40
|
+
# - nil meaning no boundary
|
|
41
|
+
# - same-currency {Money} or Range
|
|
42
|
+
# - Numeric amount, or Range
|
|
43
|
+
#
|
|
44
|
+
# Numeric is interpreted as an amount in +self+'s currency, so the common
|
|
45
|
+
# pricing idiom +price.clamp(0, 100)+ reads as "0 to 100 in the same
|
|
46
|
+
# currency as +price+".
|
|
47
|
+
#
|
|
48
|
+
# When +self+ is already in range the receiver is returned (no new object
|
|
49
|
+
# allocated). When out of range, the nearest bound is returned as a new
|
|
50
|
+
# frozen {Money} in +self+'s currency.
|
|
51
|
+
#
|
|
52
|
+
# @param min_or_range [Money, Numeric, Range, nil] lower bound (inclusive), or range
|
|
53
|
+
# @param max [Money, Numeric, nil] upper bound (inclusive)
|
|
54
|
+
# @return [Money] +self+ if in range, otherwise the nearer bound
|
|
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
|
|
58
|
+
#
|
|
59
|
+
# @example In range
|
|
60
|
+
# Mint.money(5, 'USD').clamp(0, 10) #=> [USD 5.00] (returns self)
|
|
61
|
+
#
|
|
62
|
+
# @example Out of range, with Numeric bounds
|
|
63
|
+
# Mint.money(50, 'USD').clamp(0, 10) #=> [USD 10.00]
|
|
64
|
+
#
|
|
65
|
+
# @example Out of range, with Money bounds
|
|
66
|
+
# loss = Mint.money(-5, 'USD')
|
|
67
|
+
# floor = Mint.money(0, 'USD')
|
|
68
|
+
# ceil = Mint.money(10, 'USD')
|
|
69
|
+
# loss.clamp(floor, ceil) #=> [USD 0.00]
|
|
70
|
+
#
|
|
71
|
+
# @example Subunit-0 currency (JPY)
|
|
72
|
+
# Mint.money(500, 'JPY').clamp(0, 100) #=> [JPY 100]
|
|
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
|
|
76
|
+
|
|
77
|
+
min, max = min_or_range.minmax
|
|
78
|
+
else
|
|
79
|
+
min = min_or_range
|
|
80
|
+
end
|
|
81
|
+
mint(amount.clamp(normalize_boundary(min), normalize_boundary(max)))
|
|
82
|
+
end
|
|
57
83
|
|
|
58
84
|
private
|
|
59
85
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
67
93
|
end
|
|
68
94
|
end
|
|
69
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
data/minting.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
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
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: bigdecimal
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '4.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '4.0'
|
|
12
26
|
description: Library to manipulate currency values
|
|
13
27
|
email: []
|
|
14
28
|
executables: []
|
|
@@ -20,10 +34,16 @@ files:
|
|
|
20
34
|
- Rakefile
|
|
21
35
|
- bin/console
|
|
22
36
|
- bin/setup
|
|
37
|
+
- doc/agents/AGENTS.md
|
|
38
|
+
- doc/agents/copilot-instructions.md
|
|
39
|
+
- doc/agents/gemini_gem_evaluation.md
|
|
40
|
+
- doc/agents/recommendations.md
|
|
41
|
+
- doc/agents/rubocop-issues.md
|
|
23
42
|
- lib/minting.rb
|
|
24
43
|
- lib/minting/data/currencies.yaml
|
|
25
44
|
- lib/minting/mint.rb
|
|
26
45
|
- lib/minting/mint/currency.rb
|
|
46
|
+
- lib/minting/mint/currency_store.rb
|
|
27
47
|
- lib/minting/mint/refinements.rb
|
|
28
48
|
- lib/minting/mint/registry.rb
|
|
29
49
|
- lib/minting/money.rb
|
|
@@ -31,6 +51,7 @@ files:
|
|
|
31
51
|
- lib/minting/money/arithmetics.rb
|
|
32
52
|
- lib/minting/money/coercion.rb
|
|
33
53
|
- lib/minting/money/comparable.rb
|
|
54
|
+
- lib/minting/money/constructors.rb
|
|
34
55
|
- lib/minting/money/conversion.rb
|
|
35
56
|
- lib/minting/money/formatting.rb
|
|
36
57
|
- lib/minting/money/money.rb
|