minting 1.7.2 → 1.8.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 +35 -3
- data/doc/Mint/Currency.html +826 -55
- data/doc/Mint/Money.html +715 -218
- data/doc/Mint/RangeStepPatch.html +1 -1
- data/doc/Mint/Registry.html +859 -0
- data/doc/Mint/Rounding.html +495 -0
- data/doc/Mint/UnknownCurrency.html +1 -1
- data/doc/Mint.html +307 -225
- data/doc/Minting.html +2 -2
- data/doc/_index.html +15 -8
- data/doc/agents/api_review-2026-06-15.md +329 -0
- data/doc/agents/copilot-instructions.md +0 -5
- data/doc/agents/expired/copilot-instructions.md +75 -0
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +25 -4
- data/doc/index.html +25 -4
- data/doc/method_list.html +177 -25
- data/doc/top-level-namespace.html +1 -1
- data/lib/minting/currency/currency.rb +71 -1
- data/lib/minting/mint/dsl/range.rb +1 -0
- data/lib/minting/mint/locale_backend.rb +29 -0
- data/lib/minting/mint/mint.rb +13 -38
- data/lib/minting/mint/parser/parser.rb +50 -19
- data/lib/minting/mint/parser/separators.rb +10 -8
- data/lib/minting/mint/registry/registration.rb +33 -0
- data/lib/minting/mint/registry/registry.rb +38 -0
- data/lib/minting/mint/registry/symbols.rb +49 -0
- data/lib/minting/mint/registry/zeros.rb +20 -0
- data/lib/minting/mint/rounding.rb +51 -0
- data/lib/minting/mint.rb +12 -23
- data/lib/minting/money/allocation/allocation.rb +1 -2
- data/lib/minting/money/allocation/split.rb +1 -1
- data/lib/minting/money/arithmetics/methods.rb +2 -2
- data/lib/minting/money/arithmetics/operators.rb +6 -6
- data/lib/minting/money/clamp.rb +1 -1
- data/lib/minting/money/coercion.rb +1 -1
- data/lib/minting/money/comparable.rb +6 -0
- data/lib/minting/money/constructors.rb +63 -20
- data/lib/minting/money/format/formatting.rb +16 -0
- data/lib/minting/money/format/to_s.rb +13 -4
- data/lib/minting/money/money.rb +12 -6
- data/lib/minting/version.rb +1 -1
- metadata +15 -7
- data/lib/minting/currency/currency_registry.rb +0 -67
- data/lib/minting/currency/world_currencies.rb +0 -16
- /data/doc/agents/{AGENTS.md → expired/AGENTS.md} +0 -0
- /data/doc/agents/{gemini_gem_evaluation.md → expired/gemini_gem_evaluation.md} +0 -0
- /data/doc/agents/{recommendations.md → expired/recommendations.md} +0 -0
- /data/doc/agents/{rubocop-issues.md → expired/rubocop-issues.md} +0 -0
|
@@ -7,15 +7,56 @@ module Mint
|
|
|
7
7
|
# @param amount [Numeric] The monetary amount
|
|
8
8
|
# @param currency [Currency, String] The currency code or currency object
|
|
9
9
|
# @raise [ArgumentError] If amount is not numeric or currency is invalid
|
|
10
|
-
def self.
|
|
10
|
+
def self.from(amount, currency)
|
|
11
11
|
raise ArgumentError, 'amount must be Numeric' unless amount.is_a?(Numeric)
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
currency = Currency.resolve!(currency)
|
|
14
|
+
amount = currency.normalize_amount(amount)
|
|
15
|
+
|
|
16
|
+
amount.zero? ? currency.zero : new(amount, currency)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Parses a human-readable money string into a {Money} object.
|
|
20
|
+
#
|
|
21
|
+
# Returns +nil+ when the input is invalid or currency cannot be determined.
|
|
22
|
+
#
|
|
23
|
+
# @param input [String] Amount input, optionally including a currency symbol or code
|
|
24
|
+
# @param currency [String, Symbol, Currency, nil] ISO code when not present in +input+
|
|
25
|
+
# @return [Money, nil]
|
|
26
|
+
#
|
|
27
|
+
# @example With explicit currency
|
|
28
|
+
# Money.parse('19.99', 'USD') #=> [USD 19.99]
|
|
29
|
+
# Money.parse('garbage', 'USD') #=> nil
|
|
30
|
+
#
|
|
31
|
+
# @example With symbol or code in the string
|
|
32
|
+
# Money.parse('$19.99') #=> [USD 19.99]
|
|
33
|
+
# Money.parse('USD 1,234.56') #=> [USD 1234.56]
|
|
34
|
+
def self.parse(input, currency = nil) = Mint.parse(input, currency)
|
|
35
|
+
|
|
36
|
+
# Like {.parse} but raises on failure.
|
|
37
|
+
#
|
|
38
|
+
# @param input [String] Amount input, optionally including a currency symbol or code
|
|
39
|
+
# @param currency [String, Symbol, Currency, nil] ISO code when not present in +input+
|
|
40
|
+
# @return [Money]
|
|
41
|
+
# @raise [ArgumentError] when +input+ is invalid or currency cannot be determined
|
|
42
|
+
#
|
|
43
|
+
# @example
|
|
44
|
+
# Money.parse!('19.99', 'USD') #=> [USD 19.99]
|
|
45
|
+
# Money.parse!('garbage', 'USD') #=> ArgumentError
|
|
46
|
+
def self.parse!(input, currency = nil) = Mint.parse!(input, currency)
|
|
15
47
|
|
|
16
|
-
|
|
48
|
+
# Returns a frozen zero Money in the given currency.
|
|
49
|
+
#
|
|
50
|
+
# @param currency [String, Currency] a currency code or object
|
|
51
|
+
# @return [Money] a frozen zero-Money
|
|
52
|
+
# @raise [ArgumentError] if the currency can't be resolved
|
|
53
|
+
def self.zero(currency) = Currency.resolve!(currency).zero
|
|
17
54
|
|
|
18
|
-
|
|
55
|
+
# Backwards-compatible alias for previous API
|
|
56
|
+
# TODO: deprecate in a future major release
|
|
57
|
+
def self.create(amount, currency)
|
|
58
|
+
warn 'Money.create is now deprecated. Use Money.from'
|
|
59
|
+
from(amount, currency)
|
|
19
60
|
end
|
|
20
61
|
|
|
21
62
|
# Builds a Money from a fractional (smallest-unit) Integer amount.
|
|
@@ -39,36 +80,38 @@ module Mint
|
|
|
39
80
|
def self.from_fractional(fractional, currency)
|
|
40
81
|
raise ArgumentError, 'fractional must be an Integer' unless fractional.is_a?(Integer)
|
|
41
82
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
amount = Rational(fractional, checked_currency.fractional_multiplier)
|
|
46
|
-
|
|
47
|
-
amount.zero? ? Mint.zero(checked_currency) : new(amount, checked_currency)
|
|
83
|
+
currency = Currency.resolve!(currency)
|
|
84
|
+
amount = Rational(fractional, currency.fractional_multiplier)
|
|
85
|
+
amount.zero? ? currency.zero : new(amount, currency)
|
|
48
86
|
end
|
|
49
87
|
|
|
50
88
|
# Returns a new Money object with the specified amount, or self if unchanged.
|
|
51
89
|
# This is the primary method for creating a modified copy of a Money instance
|
|
52
90
|
# while preserving immutability.
|
|
53
91
|
#
|
|
54
|
-
# @param
|
|
92
|
+
# @param amount [Numeric] The new monetary amount
|
|
55
93
|
# @return [Money] A new Money object with the new amount, or self if the amount is unchanged
|
|
56
94
|
# @example
|
|
57
95
|
# price = Mint.money(10.00, 'USD')
|
|
58
|
-
# price.
|
|
59
|
-
# price.
|
|
60
|
-
def
|
|
61
|
-
|
|
96
|
+
# price.copy_with(amount: 15.00) #=> [USD 15.00]
|
|
97
|
+
# price.copy_with(amount: 10.00) #=> [USD 10.00] (returns self)
|
|
98
|
+
def copy_with(amount:)
|
|
99
|
+
amount = currency.normalize_amount(amount)
|
|
62
100
|
|
|
63
|
-
if
|
|
101
|
+
if amount == self.amount
|
|
64
102
|
self
|
|
65
|
-
elsif
|
|
66
|
-
|
|
103
|
+
elsif amount.zero?
|
|
104
|
+
currency.zero
|
|
67
105
|
else
|
|
68
|
-
Money.new(
|
|
106
|
+
Money.new(amount, currency)
|
|
69
107
|
end
|
|
70
108
|
end
|
|
71
109
|
|
|
110
|
+
def mint(new_amount)
|
|
111
|
+
warn 'Money#mint is now deprecated and will be removed in v2'
|
|
112
|
+
copy_with(amount: new_amount)
|
|
113
|
+
end
|
|
114
|
+
|
|
72
115
|
private
|
|
73
116
|
|
|
74
117
|
# Initializes a new Money object with the given amount and currency.
|
|
@@ -5,6 +5,22 @@ module Mint
|
|
|
5
5
|
class Money
|
|
6
6
|
private
|
|
7
7
|
|
|
8
|
+
# Resolves format/decimal/thousand from locale_backend when not explicitly given.
|
|
9
|
+
# @private
|
|
10
|
+
def resolve_locale_for(format, decimal, thousand)
|
|
11
|
+
locale = locale_backend
|
|
12
|
+
[format || locale[:format] || '%<symbol>s%<amount>f',
|
|
13
|
+
decimal || locale[:decimal] || '.',
|
|
14
|
+
thousand || locale[:thousand] || ',']
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def locale_backend
|
|
18
|
+
bk = Mint.locale_backend
|
|
19
|
+
return {} unless bk.respond_to?(:call)
|
|
20
|
+
|
|
21
|
+
bk.call
|
|
22
|
+
end
|
|
23
|
+
|
|
8
24
|
# Selects the appropriate format template and value based on the amount's sign.
|
|
9
25
|
# @private
|
|
10
26
|
def select_format(format)
|
|
@@ -5,7 +5,7 @@ module Mint
|
|
|
5
5
|
class Money
|
|
6
6
|
# Formats money as a string with customizable format, thousand delimiter, and decimal
|
|
7
7
|
#
|
|
8
|
-
# @param format [String, Hash] Either a Format string with placeholders
|
|
8
|
+
# @param format [String, Hash, nil] Either a Format string with placeholders
|
|
9
9
|
# (%<symbol>s, %<amount>f, %<currency>s), or a Hash with per-sign keys
|
|
10
10
|
# (:positive, :negative, :zero) each holding a format string. A Hash
|
|
11
11
|
# is convenient for sign-aware formats such as accounting parentheses:
|
|
@@ -15,8 +15,12 @@ module Mint
|
|
|
15
15
|
# Missing keys fall back to the module default, so a Hash with only
|
|
16
16
|
# :negative will still format positives sensibly. The valid keys are
|
|
17
17
|
# :positive, :negative, :zero; anything else raises ArgumentError.
|
|
18
|
-
#
|
|
19
|
-
#
|
|
18
|
+
# When +nil+, falls back to +Mint.locale_backend+ if set, otherwise
|
|
19
|
+
# +"%<symbol>s%<amount>f"+.
|
|
20
|
+
# @param thousand [String, false, nil] Thousands delimiter (e.g., ',' for 1,000).
|
|
21
|
+
# When +nil+, falls back to +Mint.locale_backend+ if set, otherwise +","+.
|
|
22
|
+
# @param decimal [String, nil] Decimal separator (e.g., '.' or ',').
|
|
23
|
+
# When +nil+, falls back to +Mint.locale_backend+ if set, otherwise +"."+.
|
|
20
24
|
# @return [String] Formatted money string
|
|
21
25
|
#
|
|
22
26
|
# @raise [ArgumentError] if +format+ is not a String or Hash, the Hash
|
|
@@ -43,7 +47,12 @@ module Mint
|
|
|
43
47
|
# money.to_s(format: '%<amount>10.2f') #=> " 1234.56"
|
|
44
48
|
# money.to_s(format: '%<symbol>s%<amount>010.2f') #=> "$0001234.56"
|
|
45
49
|
#
|
|
46
|
-
|
|
50
|
+
# @example Locale-aware formatting (with Mint.locale_backend set)
|
|
51
|
+
# money.to_s # decimal and thousand come from locale_backend
|
|
52
|
+
#
|
|
53
|
+
def to_s(format: nil, decimal: nil, thousand: nil, width: nil)
|
|
54
|
+
format, decimal, thousand = resolve_locale_for(format, decimal, thousand)
|
|
55
|
+
|
|
47
56
|
case format
|
|
48
57
|
when {}, '' then raise ArgumentError, 'format must not be empty'
|
|
49
58
|
when Hash then validate_format_hash(format)
|
data/lib/minting/money/money.rb
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'allocation/allocation'
|
|
4
|
+
require_relative 'allocation/split'
|
|
5
|
+
require_relative 'arithmetics/methods'
|
|
6
|
+
require_relative 'arithmetics/operators'
|
|
7
|
+
require_relative 'clamp'
|
|
8
|
+
require_relative 'coercion'
|
|
9
|
+
require_relative 'comparable'
|
|
10
|
+
require_relative 'constructors'
|
|
11
|
+
require_relative 'conversion'
|
|
12
|
+
require_relative 'format/formatting'
|
|
13
|
+
require_relative 'format/to_s'
|
|
14
|
+
|
|
3
15
|
module Mint
|
|
4
16
|
# Money constructors
|
|
5
17
|
class Money
|
|
@@ -37,11 +49,5 @@ module Mint
|
|
|
37
49
|
def inspect
|
|
38
50
|
Kernel.format "[#{currency_code} %0.#{currency.subunit}f]", amount
|
|
39
51
|
end
|
|
40
|
-
|
|
41
|
-
# Helper method to verify if another object has the identical currency.
|
|
42
|
-
#
|
|
43
|
-
# @param other [Currency] the target currency to compare
|
|
44
|
-
# @return [Boolean] true if currencies match, false otherwise
|
|
45
|
-
def same_currency?(other) = other.currency == currency
|
|
46
52
|
end
|
|
47
53
|
end
|
data/lib/minting/version.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.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gilson Ferraz
|
|
@@ -41,14 +41,18 @@ files:
|
|
|
41
41
|
- doc/Mint/CurrencyRegistry.html
|
|
42
42
|
- doc/Mint/Money.html
|
|
43
43
|
- doc/Mint/RangeStepPatch.html
|
|
44
|
+
- doc/Mint/Registry.html
|
|
45
|
+
- doc/Mint/Rounding.html
|
|
44
46
|
- doc/Mint/UnknownCurrency.html
|
|
45
47
|
- doc/Minting.html
|
|
46
48
|
- doc/_index.html
|
|
47
|
-
- doc/agents/
|
|
49
|
+
- doc/agents/api_review-2026-06-15.md
|
|
48
50
|
- doc/agents/copilot-instructions.md
|
|
49
|
-
- doc/agents/
|
|
50
|
-
- doc/agents/
|
|
51
|
-
- doc/agents/
|
|
51
|
+
- doc/agents/expired/AGENTS.md
|
|
52
|
+
- doc/agents/expired/copilot-instructions.md
|
|
53
|
+
- doc/agents/expired/gemini_gem_evaluation.md
|
|
54
|
+
- doc/agents/expired/recommendations.md
|
|
55
|
+
- doc/agents/expired/rubocop-issues.md
|
|
52
56
|
- doc/class_list.html
|
|
53
57
|
- doc/css/common.css
|
|
54
58
|
- doc/css/full_list.css
|
|
@@ -64,8 +68,6 @@ files:
|
|
|
64
68
|
- doc/top-level-namespace.html
|
|
65
69
|
- lib/minting.rb
|
|
66
70
|
- lib/minting/currency/currency.rb
|
|
67
|
-
- lib/minting/currency/currency_registry.rb
|
|
68
|
-
- lib/minting/currency/world_currencies.rb
|
|
69
71
|
- lib/minting/data/world-currencies.yaml
|
|
70
72
|
- lib/minting/mint.rb
|
|
71
73
|
- lib/minting/mint/aliases.rb
|
|
@@ -73,9 +75,15 @@ files:
|
|
|
73
75
|
- lib/minting/mint/dsl/range.rb
|
|
74
76
|
- lib/minting/mint/dsl/string.rb
|
|
75
77
|
- lib/minting/mint/dsl/top_level.rb
|
|
78
|
+
- lib/minting/mint/locale_backend.rb
|
|
76
79
|
- lib/minting/mint/mint.rb
|
|
77
80
|
- lib/minting/mint/parser/parser.rb
|
|
78
81
|
- lib/minting/mint/parser/separators.rb
|
|
82
|
+
- lib/minting/mint/registry/registration.rb
|
|
83
|
+
- lib/minting/mint/registry/registry.rb
|
|
84
|
+
- lib/minting/mint/registry/symbols.rb
|
|
85
|
+
- lib/minting/mint/registry/zeros.rb
|
|
86
|
+
- lib/minting/mint/rounding.rb
|
|
79
87
|
- lib/minting/money/allocation/allocation.rb
|
|
80
88
|
- lib/minting/money/allocation/split.rb
|
|
81
89
|
- lib/minting/money/arithmetics/methods.rb
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'yaml'
|
|
4
|
-
|
|
5
|
-
# Mint currency store (internal)
|
|
6
|
-
module Mint
|
|
7
|
-
# Internal currency registry
|
|
8
|
-
# Manages the registry cache and currency symbol lookups.
|
|
9
|
-
module CurrencyRegistry
|
|
10
|
-
extend self
|
|
11
|
-
|
|
12
|
-
# Returns the hash of all registered currencies.
|
|
13
|
-
#
|
|
14
|
-
# @return [Hash{String => Currency}] registered currencies mapped by code
|
|
15
|
-
# @api private
|
|
16
|
-
def currencies
|
|
17
|
-
@currencies ||= Mint.world_currencies.dup
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
# Registered symbols sorted for detection: longest match wins, then parser priority.
|
|
21
|
-
#
|
|
22
|
-
# @return [Array<Array<String, Currency>>] sorted symbol-to-currency mappings
|
|
23
|
-
# @api private
|
|
24
|
-
def currency_symbols
|
|
25
|
-
@currency_symbols ||= begin
|
|
26
|
-
currencies.values
|
|
27
|
-
.reject { |c| c.symbol.empty? }
|
|
28
|
-
.map { |currency| [currency.symbol, currency] }
|
|
29
|
-
.sort_by { |symbol, currency| [-symbol.length, -currency.priority] }
|
|
30
|
-
end.freeze
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# Registers a new currency, raising a KeyError if already registered.
|
|
34
|
-
#
|
|
35
|
-
# @param code [String] the unique currency code
|
|
36
|
-
# @param subunit [Integer] the decimal subunit precision, defaults to 0
|
|
37
|
-
# @param symbol [String] the display symbol
|
|
38
|
-
# @param priority [Integer] parser precedence priority
|
|
39
|
-
# @return [Currency] the newly registered Currency instance
|
|
40
|
-
# @raise [ArgumentError] if the code contains invalid characters
|
|
41
|
-
# @raise [KeyError] if the currency code is already registered
|
|
42
|
-
def register(code:, subunit: 0, symbol: '', priority: 0)
|
|
43
|
-
raise ArgumentError, 'Currency code must be String' unless code.is_a? String
|
|
44
|
-
unless code.match?(/^[A-Z_]+$/)
|
|
45
|
-
raise ArgumentError,
|
|
46
|
-
"Currency code must have only letters or '_' ('USD',, 'MY_COIN')"
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
currencies = CurrencyRegistry.currencies
|
|
50
|
-
raise KeyError, "Currency: #{code} already registered" if currencies[code]
|
|
51
|
-
|
|
52
|
-
currency = currencies[code] = Currency.new(code:, subunit:, symbol:, priority:)
|
|
53
|
-
invalidate_symbols_cache
|
|
54
|
-
currency
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
private
|
|
58
|
-
|
|
59
|
-
# Clears and refreshes the currency symbol cache.
|
|
60
|
-
# Called when currencies are registered.
|
|
61
|
-
#
|
|
62
|
-
# @api private
|
|
63
|
-
def invalidate_symbols_cache
|
|
64
|
-
@currency_symbols = nil
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Mint list of world currencies
|
|
4
|
-
module Mint
|
|
5
|
-
# Loads ISO world currencies from YAML file into the registry.
|
|
6
|
-
#
|
|
7
|
-
# @return [Hash{String => Currency}] ISO-4217 world currencies mapped by code
|
|
8
|
-
# @api private
|
|
9
|
-
def self.world_currencies
|
|
10
|
-
@world_currencies ||= begin
|
|
11
|
-
path = File.join(File.expand_path('../data', __dir__), 'world-currencies.yaml')
|
|
12
|
-
|
|
13
|
-
YAML.load_file(path).to_h { |entry| [entry['code'], Currency.new(**entry.transform_keys(&:to_sym))] }
|
|
14
|
-
end.freeze
|
|
15
|
-
end
|
|
16
|
-
end
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|