minting 1.9.6 → 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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +244 -121
  3. data/Rakefile +17 -26
  4. data/bin/bench_check +2 -2
  5. data/doc/agents/api_review-2026-06-15.md +1 -1
  6. data/doc/agents/copilot-instructions.md +5 -5
  7. data/doc/agents/expired/copilot-instructions.md +2 -2
  8. data/doc/agents/expired/gemini_gem_evaluation.md +2 -2
  9. data/lib/minting/aliases.rb +22 -0
  10. data/lib/minting/currency/currency.rb +87 -12
  11. data/lib/minting/data/crypto-currencies.yaml +126 -0
  12. data/lib/minting/data/world-currencies.yaml +75 -23
  13. data/lib/minting/mint/i18n.rb +79 -29
  14. data/lib/minting/mint/mint.rb +1 -26
  15. data/lib/minting/mint/registry/crypto.rb +59 -0
  16. data/lib/minting/mint/registry/registration.rb +1 -2
  17. data/lib/minting/mint/registry/symbols.rb +37 -30
  18. data/lib/minting/mint/rounding.rb +10 -7
  19. data/lib/minting/mint.rb +1 -2
  20. data/lib/minting/money/allocation/allocation.rb +2 -2
  21. data/lib/minting/money/allocation/split.rb +1 -1
  22. data/lib/minting/money/arithmetics/operators.rb +10 -13
  23. data/lib/minting/money/clamp.rb +6 -6
  24. data/lib/minting/money/coercion.rb +1 -1
  25. data/lib/minting/money/comparable.rb +3 -3
  26. data/lib/minting/money/constructors.rb +3 -42
  27. data/lib/minting/money/conversion.rb +23 -18
  28. data/lib/minting/money/format/format.rb +100 -0
  29. data/lib/minting/money/format/formatter.rb +102 -0
  30. data/lib/minting/money/format/to_s.rb +34 -99
  31. data/lib/minting/money/format/validator.rb +34 -0
  32. data/lib/minting/money/money.rb +25 -13
  33. data/lib/minting/money/parse.rb +127 -0
  34. data/lib/minting/money/rounding.rb +27 -0
  35. data/lib/minting/version.rb +1 -1
  36. data/lib/minting.rb +17 -8
  37. metadata +9 -28
  38. data/doc/Mint/Currency.html +0 -1943
  39. data/doc/Mint/CurrencyRegistry.html +0 -511
  40. data/doc/Mint/Money.html +0 -4607
  41. data/doc/Mint/RangeStepPatch.html +0 -277
  42. data/doc/Mint/Registry.html +0 -863
  43. data/doc/Mint/Rounding.html +0 -506
  44. data/doc/Mint/UnknownCurrency.html +0 -136
  45. data/doc/Mint.html +0 -1004
  46. data/doc/Minting.html +0 -142
  47. data/doc/_index.html +0 -180
  48. data/doc/class_list.html +0 -54
  49. data/doc/css/common.css +0 -1
  50. data/doc/css/full_list.css +0 -206
  51. data/doc/css/style.css +0 -1089
  52. data/doc/file.README.html +0 -277
  53. data/doc/file_list.html +0 -59
  54. data/doc/frames.html +0 -22
  55. data/doc/index.html +0 -277
  56. data/doc/js/app.js +0 -801
  57. data/doc/js/full_list.js +0 -334
  58. data/doc/js/jquery.js +0 -4
  59. data/doc/method_list.html +0 -686
  60. data/doc/top-level-namespace.html +0 -151
  61. data/lib/minting/mint/aliases.rb +0 -16
  62. data/lib/minting/mint/parser/parser.rb +0 -97
  63. data/lib/minting/mint/parser/separators.rb +0 -41
  64. data/lib/minting/money/format/formatting.rb +0 -97
@@ -2,9 +2,9 @@ Repository overview
2
2
 
3
3
  - Language: Ruby gem (minting)
4
4
  - Location of main code: lib/minting and its subfolders (mint/, money/)
5
- - Public API surface: Mint (factory/helpers), Mint::Money, Mint::Currency
5
+ - Public API surface: Mint (factory/helpers), Mint::Money, Money::Currency
6
6
  - Data: built-in currencies in lib/minting/data/currencies.yaml
7
- - Tests: Minitest (unit) + performance benchmarks under test/performance
7
+ - Tests: Minitest (unit) + performance benchmarks under bench/
8
8
 
9
9
  Build, test and lint commands
10
10
 
@@ -43,7 +43,7 @@ High-level architecture (big picture)
43
43
  - Currency: lightweight value object (code, subunit, symbol, priority, minimum_amount).
44
44
  - Money: immutable value object stored as Rational and rounded to currency.subunit. Core concerns split across lib/minting/money/* (arithmetics, formatting, conversion, coercion, allocation, parsing, comparable).
45
45
  - Refinements: Numeric/String/Refinements in lib/minting/mint/refinements.rb expose helpers like 10.dollars, 4.to_money('USD'), and require `using Mint` in scope.
46
- - Performance tests: separate bench tasks under Rake; test/performance holds benchmark suites.
46
+ - Performance tests: separate bench tasks under Rake; bench/ holds benchmark suites.
47
47
 
48
48
  Key conventions and repo-specific rules
49
49
 
@@ -59,7 +59,7 @@ Files and places to check first during edits
59
59
  - lib/minting/money - core behavior and arithmetic
60
60
  - lib/minting/mint - currency registry, refinements, and factory API
61
61
  - lib/minting/data/currencies.yaml - canonical currency definitions
62
- - test/ - unit tests; test/performance - benchmark suites
62
+ - test/ - unit tests; bench/ - benchmark suites
63
63
 
64
64
  Notes for Copilot sessions
65
65
 
@@ -67,4 +67,4 @@ Notes for Copilot sessions
67
67
  - When changing numeric/rounding behavior, run both unit tests and relevant performance benchmarks.
68
68
  - Respect zero-equality semantics and currency code validation when modifying equality/hash logic.
69
69
 
70
- If you want, update this file with project-specific conventions to capture workflow choices (e.g., backport policy, DI patterns).
70
+ If you want, update this file with project-specific conventions to capture workflow choices (e.g., backport policy, DI patterns).
@@ -2,7 +2,7 @@ Repository overview
2
2
 
3
3
  - Language: Ruby gem (minting)
4
4
  - Location of main code: lib/minting and its subfolders (mint/, money/)
5
- - Public API surface: Mint (factory/helpers), Mint::Money, Mint::Currency
5
+ - Public API surface: Mint (factory/helpers), Mint::Money, Money::Currency
6
6
  - Data: built-in currencies in lib/minting/data/currencies.yaml
7
7
  - Tests: Minitest (unit) + performance benchmarks under test/performance
8
8
 
@@ -72,4 +72,4 @@ Notes for Copilot sessions
72
72
  - When changing numeric/rounding behavior, run both unit tests and relevant performance benchmarks.
73
73
  - Respect zero-equality semantics and currency code validation when modifying equality/hash logic.
74
74
 
75
- If you want, update this file with project-specific conventions to capture workflow choices (e.g., backport policy, DI patterns).
75
+ If you want, update this file with project-specific conventions to capture workflow choices (e.g., backport policy, DI patterns).
@@ -29,7 +29,7 @@ lib/
29
29
  │ ├── data/
30
30
  │ │ └── currencies.yaml # built-in ISO 4217 database
31
31
  │ ├── mint/
32
- │ │ ├── currency.rb # Mint::Currency model
32
+ │ │ ├── currency.rb # Money::Currency model
33
33
  │ │ ├── refinements.rb # Scoped Numeric/String refinements
34
34
  │ │ └── registry.rb # Global lookup & registration
35
35
  │ ├── money/
@@ -70,7 +70,7 @@ lib/
70
70
 
71
71
  The gem exhibits top-tier Ruby development practices:
72
72
 
73
- - **Immutability & Safety**: Both `Mint::Currency` and `Mint::Money` objects are frozen upon initialization:
73
+ - **Immutability & Safety**: Both `Money::Currency` and `Mint::Money` objects are frozen upon initialization:
74
74
  ```ruby
75
75
  def initialize(amount, currency)
76
76
  # ...
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @!parse
4
+ # # Top-level alias for {Money::Currency}, opt-in via +require 'minting/aliases'+.
5
+ # Currency = Money::Currency
6
+
7
+ # Top-level alias for {Money::Currency}, opt-in via +require 'minting/aliases'+.
8
+ #
9
+ # {::Currency} is **not** auto-bound by `require 'minting'` because
10
+ # application domain models are commonly named +Currency+ (e.g. a Rails
11
+ # model). Load this file explicitly to opt in.
12
+ #
13
+ # If {::Currency} is already defined, a warning is emitted and the existing
14
+ # constant is preserved — use {Money::Currency} explicitly in that case.
15
+ #
16
+ # @see Money::Currency
17
+
18
+ Currency = Money::Currency unless defined?(Currency)
19
+
20
+ if Currency != Money::Currency
21
+ warn "minting: top-level Currency was already defined (#{Currency}); skipping alias. Use Money::Currency"
22
+ end
@@ -15,20 +15,25 @@ module Mint
15
15
  # @attr_reader name [String, nil] Currency name
16
16
  # @attr_reader fractional_multiplier [Integer] 10^subunit, used for fractional conversions
17
17
  # @attr_reader minimum_amount [Rational] Smallest representable amount (1/fractional_multiplier)
18
+ # @attr_reader disambiguate_symbol [String, nil] A longer, code-prefixed variant to distinguish
19
+ # currencies that share the same primary symbol (e.g. "US$" for USD, "C$" for CAD).
18
20
  Currency = Data.define(:code, :subunit, :symbol, :priority, :country, :name,
19
- :fractional_multiplier) do
21
+ :fractional_multiplier, :disambiguate_symbol) do
20
22
  # @param code [String] ISO 4217 currency code
21
23
  # @param symbol [String] Display symbol
22
24
  # @param subunit [Integer] Number of decimal places (default 0)
23
25
  # @param priority [Integer] Parser precedence for symbol detection (default 0)
24
26
  # @param country [String, nil] Associated country code (default nil)
25
27
  # @param name [String, nil] Currency name (default nil)
26
- def initialize(code:, symbol:, subunit: 0, priority: 0, country: nil, name: nil)
28
+ def initialize(code:, symbol:, subunit: 0, priority: 0, country: nil, name: nil,
29
+ disambiguate_symbol: nil)
27
30
  subunit = subunit.to_i
28
31
  priority = priority.to_i
29
32
  fractional_multiplier = 10**subunit
33
+ symbol = nil if symbol && symbol.empty?
34
+ disambiguate_symbol = nil if [code, symbol].include? disambiguate_symbol
30
35
  super(code:, subunit:, symbol:, priority:, country:, name:,
31
- fractional_multiplier:)
36
+ fractional_multiplier:, disambiguate_symbol:)
32
37
  end
33
38
 
34
39
  # @return [String] debug representation
@@ -37,17 +42,29 @@ module Mint
37
42
  # @return [Rational] smallest representable amount (1/fractional_multiplier)
38
43
  def minimum_amount = Rational(1, fractional_multiplier)
39
44
 
40
- # Normalizes numeric amounts for this currency
41
- # 1. Converts to Rational
42
- # 2. Rounds to respect currency subunit
45
+ # Normalizes a numeric amount for this currency.
46
+ #
47
+ # @param amount [Numeric] the monetary amount to normalize
48
+ # @return [Rational] the amount converted to +Rational+ and rounded to
49
+ # the currency's subunit precision (half-up by default)
50
+ # @example
51
+ # usd = Money::Currency.for_code('USD')
52
+ # usd.normalize_amount(10.567) #=> (10567/1000)
53
+ # usd.normalize_amount("5.25") #=> (21/4)
54
+ #
55
+ # @see Mint::Rounding.apply Custom rounding modes via {Money.with_rounding}
43
56
  def normalize_amount(amount) = amount.to_r.round(subunit)
44
57
 
45
58
  # Returns the cached frozen zero-Money for this currency.
46
59
  #
47
60
  # @return [Money] a frozen zero-Money instance
48
61
  # @example
49
- # Mint::Currency.for_code('USD').zero #=> [USD 0.00]
62
+ # Money::Currency.for_code('USD').zero #=> [USD 0.00]
50
63
  def zero = Registry.zero_for(self)
64
+
65
+ def dsymbol
66
+ disambiguate_symbol || (Registry.symbol_shared?(symbol) ? code : symbol)
67
+ end
51
68
  end
52
69
 
53
70
  # Registers a new currency, raising a KeyError if already registered.
@@ -65,20 +82,37 @@ module Mint
65
82
 
66
83
  # Resolves an object into a {Currency}, returning +nil+ when it can't.
67
84
  #
68
- # Accepts +nil+, +String+, {Currency}, or {Money}.
69
- # Passing a {Money} extracts its currency
85
+ # Accepts +nil+, +String+, {Currency}, {Money}, or any object implementing
86
+ # +#to_currency+ (must return {Currency}) or +#currency_code+ (must return +String+).
70
87
  #
71
- # @param object [String, Currency, Money, nil] a currency code, object, or +nil+
88
+ # @param object [String, Currency, Money, nil, #to_currency, #currency_code]
89
+ # a currency code, object, or +nil+
72
90
  # @return [Currency, nil] the resolved Currency, or +nil+ if +object+ is +nil+
73
91
  # or the code is not registered
74
- # @raise [ArgumentError] if +object+ is an unsupported type (e.g. +Integer+)
92
+ # @raise [ArgumentError] if +object+ is an unsupported type, or if the method
93
+ # used to resolve it returns a value of the wrong type
75
94
  def Currency.resolve(object)
76
95
  case object
77
96
  when NilClass then nil
78
97
  when Currency then object
79
98
  when Money then object.currency
80
99
  when String then Currency.for_code object
81
- else raise ArgumentError, "currency must be [Currency], [Money], [String] or nil (#{object})"
100
+ else
101
+ if object.respond_to?(:to_currency)
102
+ result = object.to_currency
103
+ unless result.is_a?(Currency)
104
+ raise ArgumentError, "#to_currency must return a [Money::Currency], got #{result.class}"
105
+ end
106
+
107
+ result
108
+ elsif object.respond_to?(:currency_code)
109
+ result = object.currency_code
110
+ raise ArgumentError, "#currency_code must return a [String], got #{result.class}" unless result.is_a?(String)
111
+
112
+ Currency.for_code result
113
+ else
114
+ raise ArgumentError, "currency must be [Money::Currency], [Money], [String] or nil (#{object})"
115
+ end
82
116
  end
83
117
  end
84
118
 
@@ -95,6 +129,15 @@ module Mint
95
129
  resolve(object) or raise Mint::UnknownCurrency, "Could not resolve (#{object}) into a currency"
96
130
  end
97
131
 
132
+ # Returns all registered currencies as a frozen hash keyed by ISO code.
133
+ #
134
+ # @return [Hash{String => Currency}] frozen hash of all registered currencies
135
+ # @example Iterate over registered currencies
136
+ # Currency.registered_currencies.each { |code, currency| puts "#{code}: #{currency.name}" }
137
+ # @example Count of registered currencies
138
+ # Currency.registered_currencies.size #=> 154
139
+ def Currency.registered_currencies = Registry.currencies
140
+
98
141
  # Looks up a registered currency by its alpha code.
99
142
  #
100
143
  # @param code [String] the currency code
@@ -118,4 +161,36 @@ module Mint
118
161
  # @return [Money] a frozen zero-Money
119
162
  # @raise [Mint::UnknownCurrency] if the currency can't be resolved
120
163
  def Currency.zero(currency) = Registry.zero_for(Currency.resolve!(currency))
164
+
165
+ # Returns the frozen hash of all built-in ISO 4217 world currencies.
166
+ #
167
+ # @return [Hash{String => Currency}] ISO-4217 world currencies mapped by code
168
+ # @api private
169
+ def Currency.world_currencies = Registry.world_currencies
170
+
171
+ # Returns the list of built-in crypto currency definitions.
172
+ #
173
+ # These are not registered by default — call {.register_crypto} to opt in.
174
+ #
175
+ # @return [Array<Currency>] frozen array of crypto currency definitions
176
+ def Currency.crypto_currencies = Registry.crypto_currencies
177
+
178
+ # Registers one or more crypto currencies into the shared currency registry.
179
+ #
180
+ # Raises on duplicate registration or unknown code — use +rescue+ if
181
+ # idempotent bulk registration is needed.
182
+ #
183
+ # @param codes [Array<String>] one or more crypto currency codes
184
+ # @raise [ArgumentError] if a code is not a known crypto currency
185
+ # @raise [KeyError] if the currency code is already registered
186
+ # @return [Array<Currency>] the newly registered Currency objects
187
+ def Currency.register_crypto(...) = Registry.register_crypto(...)
188
+
189
+ # Registers all built-in crypto currencies at once.
190
+ #
191
+ # Raises on the first duplicate — call +rescue+ if idempotency is needed.
192
+ #
193
+ # @raise [KeyError] if any currency code is already registered
194
+ # @return [Array<Currency>] the newly registered Currency objects
195
+ def Currency.register_all_crypto = Registry.register_all_crypto
121
196
  end
@@ -0,0 +1,126 @@
1
+ ---
2
+ - code: BTC
3
+ subunit: 8
4
+ symbol: "₿"
5
+ priority: 1000
6
+ name: Bitcoin
7
+ - code: ETH
8
+ subunit: 18
9
+ symbol: "Ξ"
10
+ priority: 950
11
+ name: Ethereum
12
+ - code: USDT
13
+ subunit: 6
14
+ symbol: "$"
15
+ priority: 900
16
+ name: Tether
17
+ - code: SOL
18
+ subunit: 9
19
+ symbol: "◎"
20
+ priority: 850
21
+ name: Solana
22
+ - code: XRP
23
+ subunit: 6
24
+ symbol: "✕"
25
+ priority: 800
26
+ name: XRP
27
+ - code: BNB
28
+ subunit: 8
29
+ symbol: ""
30
+ priority: 750
31
+ name: BNB
32
+ - code: USDC
33
+ subunit: 6
34
+ symbol: "$"
35
+ priority: 740
36
+ name: USD Coin
37
+ - code: DOGE
38
+ subunit: 8
39
+ symbol: "Ð"
40
+ priority: 730
41
+ name: Dogecoin
42
+ - code: ADA
43
+ subunit: 6
44
+ symbol: "₳"
45
+ priority: 720
46
+ name: Cardano
47
+ - code: TRX
48
+ subunit: 6
49
+ symbol: ""
50
+ priority: 710
51
+ name: TRON
52
+ - code: AVAX
53
+ subunit: 9
54
+ symbol: ""
55
+ priority: 700
56
+ name: Avalanche
57
+ - code: DOT
58
+ subunit: 10
59
+ symbol: ""
60
+ priority: 680
61
+ name: Polkadot
62
+ - code: LINK
63
+ subunit: 8
64
+ symbol: ""
65
+ priority: 660
66
+ name: Chainlink
67
+ - code: MATIC
68
+ subunit: 6
69
+ symbol: "⧫"
70
+ priority: 640
71
+ name: Polygon
72
+ - code: SHIB
73
+ subunit: 8
74
+ symbol: ""
75
+ priority: 620
76
+ name: Shiba Inu
77
+ - code: UNI
78
+ subunit: 8
79
+ symbol: ""
80
+ priority: 600
81
+ name: Uniswap
82
+ - code: ATOM
83
+ subunit: 6
84
+ symbol: ""
85
+ priority: 580
86
+ name: Cosmos
87
+ - code: LTC
88
+ subunit: 8
89
+ symbol: "Ł"
90
+ priority: 560
91
+ name: Litecoin
92
+ - code: BCH
93
+ subunit: 8
94
+ symbol: ""
95
+ priority: 540
96
+ name: Bitcoin Cash
97
+ - code: FIL
98
+ subunit: 9
99
+ symbol: ""
100
+ priority: 520
101
+ name: Filecoin
102
+ - code: APT
103
+ subunit: 8
104
+ symbol: ""
105
+ priority: 500
106
+ name: Aptos
107
+ - code: SUI
108
+ subunit: 9
109
+ symbol: ""
110
+ priority: 480
111
+ name: Sui
112
+ - code: TON
113
+ subunit: 9
114
+ symbol: ""
115
+ priority: 460
116
+ name: Toncoin
117
+ - code: PEPE
118
+ subunit: 8
119
+ symbol: ""
120
+ priority: 440
121
+ name: Pepe
122
+ - code: STX
123
+ subunit: 6
124
+ symbol: ""
125
+ priority: 420
126
+ name: Stacks