minting 1.9.7 → 2.0.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 +234 -116
- data/Rakefile +2 -7
- data/doc/agents/api_review-2026-06-15.md +1 -1
- data/doc/agents/copilot-instructions.md +2 -2
- data/doc/agents/expired/copilot-instructions.md +2 -2
- data/doc/agents/expired/gemini_gem_evaluation.md +2 -2
- data/lib/minting/aliases.rb +22 -0
- data/lib/minting/currency/currency.rb +81 -9
- data/lib/minting/data/crypto-currencies.yaml +126 -0
- data/lib/minting/mint/i18n.rb +79 -29
- data/lib/minting/mint/mint.rb +1 -26
- data/lib/minting/mint/registry/crypto.rb +59 -0
- data/lib/minting/mint/registry/registration.rb +1 -2
- data/lib/minting/mint/registry/symbols.rb +37 -30
- data/lib/minting/mint/rounding.rb +9 -7
- data/lib/minting/mint.rb +1 -2
- data/lib/minting/money/allocation/allocation.rb +2 -2
- data/lib/minting/money/allocation/split.rb +1 -1
- data/lib/minting/money/arithmetics/operators.rb +10 -13
- data/lib/minting/money/clamp.rb +6 -6
- data/lib/minting/money/coercion.rb +1 -1
- data/lib/minting/money/comparable.rb +3 -3
- data/lib/minting/money/constructors.rb +3 -42
- data/lib/minting/money/conversion.rb +22 -18
- data/lib/minting/money/format/format.rb +100 -0
- data/lib/minting/money/format/formatter.rb +102 -0
- data/lib/minting/money/format/to_s.rb +20 -102
- data/lib/minting/money/format/validator.rb +34 -0
- data/lib/minting/money/money.rb +25 -9
- data/lib/minting/money/parse.rb +127 -0
- data/lib/minting/money/rounding.rb +27 -0
- data/lib/minting/version.rb +1 -1
- data/lib/minting.rb +17 -8
- metadata +9 -29
- data/doc/Mint/Currency.html +0 -2032
- data/doc/Mint/Money.html +0 -5139
- data/doc/Mint/RangeStepPatch.html +0 -277
- data/doc/Mint/Registry.html +0 -863
- data/doc/Mint/Rounding.html +0 -506
- data/doc/Mint/UnknownCurrency.html +0 -138
- data/doc/Mint.html +0 -931
- data/doc/Minting.html +0 -142
- data/doc/Numeric.html +0 -479
- data/doc/String.html +0 -241
- data/doc/_index.html +0 -206
- 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 -291
- data/doc/file_list.html +0 -59
- data/doc/frames.html +0 -22
- data/doc/index.html +0 -291
- 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 -758
- data/doc/top-level-namespace.html +0 -135
- data/lib/minting/mint/aliases.rb +0 -16
- data/lib/minting/mint/parser/parser.rb +0 -97
- data/lib/minting/mint/parser/separators.rb +0 -41
- data/lib/minting/money/format/formatting.rb +0 -130
data/lib/minting/money/money.rb
CHANGED
|
@@ -8,9 +8,13 @@ require_relative 'clamp'
|
|
|
8
8
|
require_relative 'coercion'
|
|
9
9
|
require_relative 'comparable'
|
|
10
10
|
require_relative 'constructors'
|
|
11
|
+
require_relative 'parse'
|
|
11
12
|
require_relative 'conversion'
|
|
12
|
-
require_relative 'format/
|
|
13
|
+
require_relative 'format/validator'
|
|
14
|
+
require_relative 'format/formatter'
|
|
13
15
|
require_relative 'format/to_s'
|
|
16
|
+
require_relative 'format/format'
|
|
17
|
+
require_relative 'rounding'
|
|
14
18
|
|
|
15
19
|
module Mint
|
|
16
20
|
# Represents a monetary value paired with a currency.
|
|
@@ -19,11 +23,14 @@ module Mint
|
|
|
19
23
|
class Money
|
|
20
24
|
attr_reader :amount, :currency
|
|
21
25
|
|
|
26
|
+
# Money::Currency is the canonical way to access Currency class
|
|
27
|
+
Currency = Mint::Currency
|
|
28
|
+
|
|
22
29
|
# Returns the ISO 3-letter currency code string.
|
|
23
30
|
#
|
|
24
31
|
# @return [String] the ISO currency code (e.g., "USD", "EUR", "BRL")
|
|
25
32
|
# @example
|
|
26
|
-
#
|
|
33
|
+
# Money.from(100, 'USD').currency_code #=> "USD"
|
|
27
34
|
def currency_code = currency.code
|
|
28
35
|
|
|
29
36
|
# Returns the monetary amount expressed in the currency's smallest unit (fractional units).
|
|
@@ -31,17 +38,26 @@ module Mint
|
|
|
31
38
|
#
|
|
32
39
|
# @return [Integer] the amount in fractional units
|
|
33
40
|
# @example
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
41
|
+
# Money.from(1234.56, 'USD').subunits #=> 123456
|
|
42
|
+
# Money.from(1000, 'JPY').subunits #=> 1000
|
|
43
|
+
# Money.from(123.456, 'IQD').subunits #=> 123456
|
|
37
44
|
def subunits = (amount * currency.fractional_multiplier).to_i
|
|
38
45
|
|
|
46
|
+
# Returns the whole-unit (integral) part of the amount.
|
|
47
|
+
# @example
|
|
48
|
+
# Money.from(1234.56, 'USD').integral #=> 1234
|
|
49
|
+
# Money.from(1000, 'JPY').integral #=> 1000
|
|
50
|
+
# Money.from(-9.99, 'USD').integral #=> -9
|
|
51
|
+
def integral = amount.to_i
|
|
52
|
+
|
|
53
|
+
alias to_i integral
|
|
54
|
+
|
|
39
55
|
# Returns the fractional part of the amount.
|
|
40
56
|
# @example
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
def fractional = ((amount
|
|
57
|
+
# Money.from(1234.56, 'USD').fractional #=> 56
|
|
58
|
+
# Money.from(1000, 'JPY').fractional #=> 0
|
|
59
|
+
# Money.from(123.456, 'IQD').fractional #=> 456
|
|
60
|
+
def fractional = ((amount - amount.to_i) * currency.fractional_multiplier).to_i
|
|
45
61
|
|
|
46
62
|
# Generates a stable hash key for Money instances.
|
|
47
63
|
#
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mint
|
|
4
|
+
# :nodoc:
|
|
5
|
+
class Money
|
|
6
|
+
# Parses a human-readable money string into a {Money} object.
|
|
7
|
+
#
|
|
8
|
+
# Returns +nil+ when the input is invalid or currency cannot be determined.
|
|
9
|
+
#
|
|
10
|
+
# @param input [String] Amount input, optionally including a currency symbol or code
|
|
11
|
+
# @param currency [String, Symbol, Currency, nil] ISO code when not present in +input+
|
|
12
|
+
# @return [Money, nil]
|
|
13
|
+
#
|
|
14
|
+
# @example With explicit currency
|
|
15
|
+
# Money.parse('19.99', 'USD') #=> [USD 19.99]
|
|
16
|
+
# Money.parse('garbage', 'USD') #=> nil
|
|
17
|
+
#
|
|
18
|
+
# @example With symbol or code in the string
|
|
19
|
+
# Money.parse('$19.99') #=> [USD 19.99]
|
|
20
|
+
# Money.parse('USD 1,234.56') #=> [USD 1234.56]
|
|
21
|
+
def self.parse(input, currency = nil)
|
|
22
|
+
return nil unless input.is_a?(String)
|
|
23
|
+
|
|
24
|
+
input = input.strip
|
|
25
|
+
return nil if input.empty?
|
|
26
|
+
|
|
27
|
+
currency = parse_currency(input, currency)
|
|
28
|
+
return nil unless currency
|
|
29
|
+
|
|
30
|
+
amount = parse_amount(input)
|
|
31
|
+
return nil unless amount
|
|
32
|
+
|
|
33
|
+
amount = currency.normalize_amount(amount)
|
|
34
|
+
new(amount, currency)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Like {.parse} but raises on failure.
|
|
38
|
+
#
|
|
39
|
+
# @param input [String] Amount input, optionally including a currency symbol or code
|
|
40
|
+
# @param currency [String, Symbol, Currency, nil] ISO code when not present in +input+
|
|
41
|
+
# @return [Money]
|
|
42
|
+
# @raise [ArgumentError] when +input+ is invalid or currency cannot be determined
|
|
43
|
+
#
|
|
44
|
+
# @example
|
|
45
|
+
# Money.parse!('19.99', 'USD') #=> [USD 19.99]
|
|
46
|
+
# Money.parse!('garbage', 'USD') #=> ArgumentError
|
|
47
|
+
def self.parse!(input, currency = nil)
|
|
48
|
+
raise ArgumentError, 'input must be a String' unless input.is_a?(String)
|
|
49
|
+
|
|
50
|
+
input = input.strip
|
|
51
|
+
raise ArgumentError, 'input cannot be empty' if input.empty?
|
|
52
|
+
|
|
53
|
+
currency = parse_currency(input, currency)
|
|
54
|
+
raise ArgumentError, "Currency [#{currency}] not found" unless currency
|
|
55
|
+
|
|
56
|
+
amount = parse_amount(input)
|
|
57
|
+
raise ArgumentError, "Could not parse [#{input}]" unless amount
|
|
58
|
+
|
|
59
|
+
amount = currency.normalize_amount(amount)
|
|
60
|
+
new(amount, currency)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class << self
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
# Extracts a numeric value from input that should only contain an amount.
|
|
67
|
+
def parse_amount(input)
|
|
68
|
+
accounting_negative = input.start_with?('(') && input.end_with?(')')
|
|
69
|
+
|
|
70
|
+
numeric_input = input.gsub(/[^\d.,-]/, '')
|
|
71
|
+
numeric = parse_separators(numeric_input)
|
|
72
|
+
return nil unless numeric
|
|
73
|
+
|
|
74
|
+
amount = Rational(numeric)
|
|
75
|
+
accounting_negative ? -amount : amount
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Extracts currency from a string by matching ISO code or symbol.
|
|
79
|
+
#
|
|
80
|
+
# Scans all uppercase words and returns the first registered code, falling
|
|
81
|
+
# back to symbol matching. This correctly handles inputs like
|
|
82
|
+
# "MAX 10.00 USD" where the first uppercase word isn't a currency code.
|
|
83
|
+
def parse_currency(input, currency = nil)
|
|
84
|
+
input.scan(/\b([A-Z_]+)\b/) do |(code)|
|
|
85
|
+
found = Currency.for_code(code)
|
|
86
|
+
return found if found
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
found = Registry.detect_currency(input)
|
|
90
|
+
return found if found
|
|
91
|
+
|
|
92
|
+
Currency.resolve(currency)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Converts locale-specific decimal/thousand separators into a plain decimal string.
|
|
96
|
+
def parse_separators(numeric)
|
|
97
|
+
return nil unless numeric.match?(/\d/)
|
|
98
|
+
|
|
99
|
+
case classify_separators(numeric)
|
|
100
|
+
when :decimal_period then numeric
|
|
101
|
+
when :decimal_comma then numeric.tr(',', '.')
|
|
102
|
+
when :thousands_comma then numeric.delete(',')
|
|
103
|
+
when :thousands then numeric.delete('.,')
|
|
104
|
+
when :invalid then nil
|
|
105
|
+
when :mixed
|
|
106
|
+
if numeric.rindex(',') > numeric.rindex('.')
|
|
107
|
+
numeric.delete('.').tr(',', '.')
|
|
108
|
+
else
|
|
109
|
+
numeric.delete(',')
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Classifies the separator pattern in a numeric string.
|
|
115
|
+
def classify_separators(numeric)
|
|
116
|
+
case [numeric.count('.'), numeric.count(',')]
|
|
117
|
+
in [0, 1] if numeric[-4] == ',' then :thousands_comma
|
|
118
|
+
in [0, 1] then :decimal_comma
|
|
119
|
+
in [0, 0] | [1, 0] then :decimal_period
|
|
120
|
+
in [p, c] if p > 1 && c > 1 then :invalid
|
|
121
|
+
in [p, c] if p > 0 && c > 0 then :mixed
|
|
122
|
+
else :thousands
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mint
|
|
4
|
+
# :nodoc:
|
|
5
|
+
class Money
|
|
6
|
+
# Executes a block with a specific rounding mode applied to all money
|
|
7
|
+
# construction, parsing, change, allocation, and split operations.
|
|
8
|
+
#
|
|
9
|
+
# Restores the previous mode (or default) when the block exits, even on
|
|
10
|
+
# exception.
|
|
11
|
+
#
|
|
12
|
+
# Rounding-mode support is loaded lazily on first call. Once loaded,
|
|
13
|
+
# +Currency#normalize_amount+ is patched to dispatch through the
|
|
14
|
+
# rounding module, adding ~10–35&ns of overhead to every money creation
|
|
15
|
+
# or mutation. When rounding modes are never used (the common case),
|
|
16
|
+
# the fast path incurs zero overhead.
|
|
17
|
+
#
|
|
18
|
+
# @param mode [Symbol] one of: +:half_up+, +:half_down+, +:floor+,
|
|
19
|
+
# +:ceil+, +:truncate+, +:down+
|
|
20
|
+
# @yield block to execute with the rounding mode active
|
|
21
|
+
# @raise [ArgumentError] if +mode+ is not a recognised rounding mode
|
|
22
|
+
def self.with_rounding(mode, &)
|
|
23
|
+
require_relative '../mint/rounding' unless defined?(Mint::Rounding)
|
|
24
|
+
Mint::Rounding.with_mode(mode, &)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
data/lib/minting/version.rb
CHANGED
data/lib/minting.rb
CHANGED
|
@@ -3,12 +3,21 @@
|
|
|
3
3
|
require 'minting/mint'
|
|
4
4
|
require 'minting/version'
|
|
5
5
|
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
# @!parse
|
|
7
|
+
# # Top-level constant auto-bound to {Mint::Money} for convenience.
|
|
8
|
+
# Money = Mint::Money
|
|
9
|
+
|
|
10
|
+
# Top-level constant auto-bound to {Mint::Money} for convenience.
|
|
11
|
+
#
|
|
12
|
+
# Set at require-time via `require 'minting'`. If {::Money} is already
|
|
13
|
+
# defined (e.g. by the `money` gem), a warning is emitted and the existing
|
|
14
|
+
# constant is preserved — use {Mint::Money} explicitly in that case.
|
|
15
|
+
#
|
|
16
|
+
# @see Mint::Money
|
|
17
|
+
# @note This is a **breaking change from v1.x** where both +Money+ and
|
|
18
|
+
# +Currency+ required explicit opt-in via +Mint.use_top_level_constants!+.
|
|
19
|
+
Money = Mint::Money unless defined?(Money)
|
|
20
|
+
|
|
21
|
+
if Money != Mint::Money
|
|
22
|
+
warn "minting: top-level Money was already defined (#{Money}); skipping auto-bind! Use Mint::Money."
|
|
14
23
|
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:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gilson Ferraz
|
|
@@ -36,17 +36,6 @@ files:
|
|
|
36
36
|
- bin/check-currencies
|
|
37
37
|
- bin/console
|
|
38
38
|
- bin/setup
|
|
39
|
-
- doc/Mint.html
|
|
40
|
-
- doc/Mint/Currency.html
|
|
41
|
-
- doc/Mint/Money.html
|
|
42
|
-
- doc/Mint/RangeStepPatch.html
|
|
43
|
-
- doc/Mint/Registry.html
|
|
44
|
-
- doc/Mint/Rounding.html
|
|
45
|
-
- doc/Mint/UnknownCurrency.html
|
|
46
|
-
- doc/Minting.html
|
|
47
|
-
- doc/Numeric.html
|
|
48
|
-
- doc/String.html
|
|
49
|
-
- doc/_index.html
|
|
50
39
|
- doc/agents/api_review-2026-06-15.md
|
|
51
40
|
- doc/agents/copilot-instructions.md
|
|
52
41
|
- doc/agents/expired/AGENTS.md
|
|
@@ -54,31 +43,18 @@ files:
|
|
|
54
43
|
- doc/agents/expired/gemini_gem_evaluation.md
|
|
55
44
|
- doc/agents/expired/recommendations.md
|
|
56
45
|
- doc/agents/expired/rubocop-issues.md
|
|
57
|
-
- doc/class_list.html
|
|
58
|
-
- doc/css/common.css
|
|
59
|
-
- doc/css/full_list.css
|
|
60
|
-
- doc/css/style.css
|
|
61
|
-
- doc/file.README.html
|
|
62
|
-
- doc/file_list.html
|
|
63
|
-
- doc/frames.html
|
|
64
|
-
- doc/index.html
|
|
65
|
-
- doc/js/app.js
|
|
66
|
-
- doc/js/full_list.js
|
|
67
|
-
- doc/js/jquery.js
|
|
68
|
-
- doc/method_list.html
|
|
69
|
-
- doc/top-level-namespace.html
|
|
70
46
|
- lib/minting.rb
|
|
47
|
+
- lib/minting/aliases.rb
|
|
71
48
|
- lib/minting/currency/currency.rb
|
|
49
|
+
- lib/minting/data/crypto-currencies.yaml
|
|
72
50
|
- lib/minting/data/world-currencies.yaml
|
|
73
51
|
- lib/minting/mint.rb
|
|
74
|
-
- lib/minting/mint/aliases.rb
|
|
75
52
|
- lib/minting/mint/dsl/numeric.rb
|
|
76
53
|
- lib/minting/mint/dsl/range.rb
|
|
77
54
|
- lib/minting/mint/dsl/string.rb
|
|
78
55
|
- lib/minting/mint/i18n.rb
|
|
79
56
|
- lib/minting/mint/mint.rb
|
|
80
|
-
- lib/minting/mint/
|
|
81
|
-
- lib/minting/mint/parser/separators.rb
|
|
57
|
+
- lib/minting/mint/registry/crypto.rb
|
|
82
58
|
- lib/minting/mint/registry/registration.rb
|
|
83
59
|
- lib/minting/mint/registry/registry.rb
|
|
84
60
|
- lib/minting/mint/registry/symbols.rb
|
|
@@ -93,9 +69,13 @@ files:
|
|
|
93
69
|
- lib/minting/money/comparable.rb
|
|
94
70
|
- lib/minting/money/constructors.rb
|
|
95
71
|
- lib/minting/money/conversion.rb
|
|
96
|
-
- lib/minting/money/format/
|
|
72
|
+
- lib/minting/money/format/format.rb
|
|
73
|
+
- lib/minting/money/format/formatter.rb
|
|
97
74
|
- lib/minting/money/format/to_s.rb
|
|
75
|
+
- lib/minting/money/format/validator.rb
|
|
98
76
|
- lib/minting/money/money.rb
|
|
77
|
+
- lib/minting/money/parse.rb
|
|
78
|
+
- lib/minting/money/rounding.rb
|
|
99
79
|
- lib/minting/version.rb
|
|
100
80
|
- minting.gemspec
|
|
101
81
|
homepage: https://github.com/gferraz/minting
|