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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -3
  3. data/doc/Mint/Currency.html +826 -55
  4. data/doc/Mint/Money.html +715 -218
  5. data/doc/Mint/RangeStepPatch.html +1 -1
  6. data/doc/Mint/Registry.html +859 -0
  7. data/doc/Mint/Rounding.html +495 -0
  8. data/doc/Mint/UnknownCurrency.html +1 -1
  9. data/doc/Mint.html +307 -225
  10. data/doc/Minting.html +2 -2
  11. data/doc/_index.html +15 -8
  12. data/doc/agents/api_review-2026-06-15.md +329 -0
  13. data/doc/agents/copilot-instructions.md +0 -5
  14. data/doc/agents/expired/copilot-instructions.md +75 -0
  15. data/doc/class_list.html +1 -1
  16. data/doc/file.README.html +25 -4
  17. data/doc/index.html +25 -4
  18. data/doc/method_list.html +177 -25
  19. data/doc/top-level-namespace.html +1 -1
  20. data/lib/minting/currency/currency.rb +71 -1
  21. data/lib/minting/mint/dsl/range.rb +1 -0
  22. data/lib/minting/mint/locale_backend.rb +29 -0
  23. data/lib/minting/mint/mint.rb +13 -38
  24. data/lib/minting/mint/parser/parser.rb +50 -19
  25. data/lib/minting/mint/parser/separators.rb +10 -8
  26. data/lib/minting/mint/registry/registration.rb +33 -0
  27. data/lib/minting/mint/registry/registry.rb +38 -0
  28. data/lib/minting/mint/registry/symbols.rb +49 -0
  29. data/lib/minting/mint/registry/zeros.rb +20 -0
  30. data/lib/minting/mint/rounding.rb +51 -0
  31. data/lib/minting/mint.rb +12 -23
  32. data/lib/minting/money/allocation/allocation.rb +1 -2
  33. data/lib/minting/money/allocation/split.rb +1 -1
  34. data/lib/minting/money/arithmetics/methods.rb +2 -2
  35. data/lib/minting/money/arithmetics/operators.rb +6 -6
  36. data/lib/minting/money/clamp.rb +1 -1
  37. data/lib/minting/money/coercion.rb +1 -1
  38. data/lib/minting/money/comparable.rb +6 -0
  39. data/lib/minting/money/constructors.rb +63 -20
  40. data/lib/minting/money/format/formatting.rb +16 -0
  41. data/lib/minting/money/format/to_s.rb +13 -4
  42. data/lib/minting/money/money.rb +12 -6
  43. data/lib/minting/version.rb +1 -1
  44. metadata +15 -7
  45. data/lib/minting/currency/currency_registry.rb +0 -67
  46. data/lib/minting/currency/world_currencies.rb +0 -16
  47. /data/doc/agents/{AGENTS.md → expired/AGENTS.md} +0 -0
  48. /data/doc/agents/{gemini_gem_evaluation.md → expired/gemini_gem_evaluation.md} +0 -0
  49. /data/doc/agents/{recommendations.md → expired/recommendations.md} +0 -0
  50. /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.create(amount, currency)
10
+ def self.from(amount, currency)
11
11
  raise ArgumentError, 'amount must be Numeric' unless amount.is_a?(Numeric)
12
12
 
13
- checked_currency = Mint.currency(currency)
14
- raise ArgumentError, "Currency not found (#{currency})" unless checked_currency
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
- amount = checked_currency.normalize_amount(amount)
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
- amount.zero? ? Mint.zero(checked_currency) : new(amount, checked_currency)
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
- checked_currency = Mint.currency(currency)
43
- raise ArgumentError, "Currency not found (#{currency})" unless checked_currency
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 new_amount [Numeric] The new monetary amount
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.mint(15.00) #=> [USD 15.00]
59
- # price.mint(10.00) #=> [USD 10.00] (returns self)
60
- def mint(new_amount)
61
- new_amount = currency.normalize_amount(new_amount)
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 new_amount == amount
101
+ if amount == self.amount
64
102
  self
65
- elsif new_amount.zero?
66
- Mint.zero(currency)
103
+ elsif amount.zero?
104
+ currency.zero
67
105
  else
68
- Money.new(new_amount, currency)
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
- # @param thousand [String, false] Thousands delimiter (e.g., ',' for 1,000)
19
- # @param decimal [String] Decimal separator (e.g., '.' or ',')
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
- def to_s(format: '%<symbol>s%<amount>f', decimal: '.', thousand: ',', width: nil)
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)
@@ -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
@@ -3,5 +3,5 @@
3
3
  # Root namespace for the Minting library.
4
4
  module Minting
5
5
  # Current version of the Minting gem.
6
- VERSION = '1.7.2'
6
+ VERSION = '1.8.0'
7
7
  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.7.2
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/AGENTS.md
49
+ - doc/agents/api_review-2026-06-15.md
48
50
  - doc/agents/copilot-instructions.md
49
- - doc/agents/gemini_gem_evaluation.md
50
- - doc/agents/recommendations.md
51
- - doc/agents/rubocop-issues.md
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