minting 1.5.1 → 1.6.1
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 +6 -71
- data/Rakefile +8 -12
- data/doc/agents/AGENTS.md +1 -1
- data/doc/agents/copilot-instructions.md +4 -4
- data/doc/agents/recommendations.md +2 -2
- data/lib/minting/data/currencies.yaml +6 -0
- data/lib/minting/mint/currency.rb +22 -20
- data/lib/minting/mint/currency_store.rb +8 -35
- data/lib/minting/mint/mint.rb +30 -0
- data/lib/minting/mint/parser.rb +78 -0
- data/lib/minting/mint/refinements.rb +1 -3
- data/lib/minting/mint/registry.rb +7 -41
- data/lib/minting/mint/world_currencies.rb +17 -0
- data/lib/minting/mint.rb +11 -0
- data/lib/minting/money/allocation.rb +1 -1
- data/lib/minting/money/arithmetics.rb +1 -3
- data/lib/minting/money/coercion.rb +4 -0
- data/lib/minting/money/comparable.rb +5 -3
- data/lib/minting/money/constructors.rb +11 -4
- data/lib/minting/money/conversion.rb +11 -0
- data/lib/minting/money/money.rb +11 -1
- data/lib/minting/version.rb +1 -1
- data/lib/minting.rb +0 -1
- metadata +4 -3
- data/lib/minting/money/parse.rb +0 -79
- data/lib/minting/money.rb +0 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3c50c230bea20784e9834f9f0c2fd9968b73cb94cbd14aa06a0eb4e3a3e41d74
|
|
4
|
+
data.tar.gz: d57f1a4e5a4664c721a286e1ff93eba1558cbf24babeaca6615af755d53663eb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1a4d800520eaa79c57a0cb0111e8b40dcf603fe49fdc697e2b065e3b295c2fe7599285f7af1bc987c0cec3ac13a94a95ef836eb741e86f34986e915e4f904daa
|
|
7
|
+
data.tar.gz: fc8840f398dead653854665cae2afea1f333e973678eac67df401c045af45ee5370a50f365debdd85f42404b80c10cd68c7bba5dc9b086afa2e9c705d323bbe2
|
data/README.md
CHANGED
|
@@ -148,7 +148,7 @@ price.clamp(min_price, 100) #=> [USD 75.00]
|
|
|
148
148
|
|
|
149
149
|
**Zero equality** — Any zero amount is considered equal across currencies and to numeric zero `Mint.money(0, 'USD') == Mint.money(0, 'EUR')` is intentionally `true`. Non-zero amounts must match currency and value.
|
|
150
150
|
|
|
151
|
-
**Custom currencies** — `Mint.register_currency
|
|
151
|
+
**Custom currencies** — `Mint.register_currency`, Only registered currency codes and symbolos are recoginized by the parser.
|
|
152
152
|
|
|
153
153
|
**Built-in currencies** — ISO-style codes ship in `lib/minting/data/currencies.yaml` and load when the registry is first accessed.
|
|
154
154
|
|
|
@@ -188,10 +188,10 @@ gem install minting
|
|
|
188
188
|
## Parsing strings
|
|
189
189
|
|
|
190
190
|
```ruby
|
|
191
|
-
Mint
|
|
192
|
-
Mint
|
|
193
|
-
Mint
|
|
194
|
-
Mint
|
|
191
|
+
Mint.parse('$19.99') #=> [USD 19.99]
|
|
192
|
+
Mint.parse('19,99 €') #=> [EUR 19.99]
|
|
193
|
+
Mint.parse('1.234,56', 'EUR') #=> [EUR 1234.56]
|
|
194
|
+
Mint.parse('USD 1,234.56') #=> [USD 1234.56]
|
|
195
195
|
```
|
|
196
196
|
|
|
197
197
|
- Pass a currency code when the string has no symbol or code.
|
|
@@ -212,75 +212,10 @@ Bug reports and pull requests are welcome on GitHub at <https://github.com/gferr
|
|
|
212
212
|
|
|
213
213
|
1. Fork and create a feature branch
|
|
214
214
|
2. Run the test suite: `rake`
|
|
215
|
-
3. Run performance suites as needed: `
|
|
215
|
+
3. Run performance suites as needed: `rake bench:performance`
|
|
216
216
|
4. Open a PR with a clear description and benchmarks if relevant
|
|
217
217
|
|
|
218
218
|
|
|
219
|
-
## Performance
|
|
220
|
-
|
|
221
|
-
This gem includes a performance suite under `test/performance`:
|
|
222
|
-
|
|
223
|
-
- Core operations (creation, arithmetic, comparisons)
|
|
224
|
-
- Algorithm benchmarks (split, allocate)
|
|
225
|
-
- Memory and GC pressure tests
|
|
226
|
-
- Competitive benchmarks vs `money` gem
|
|
227
|
-
|
|
228
|
-
Run locally:
|
|
229
|
-
|
|
230
|
-
```bash
|
|
231
|
-
# All performance suites
|
|
232
|
-
BENCH=true rake bench:performance
|
|
233
|
-
|
|
234
|
-
# Competitive vs money gem
|
|
235
|
-
BENCH=true rake bench:competitive
|
|
236
|
-
|
|
237
|
-
# Regression checks
|
|
238
|
-
rake bench:regression
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
## Benchmark Summary: Minting vs Money Gem
|
|
242
|
-
|
|
243
|
-
Generated by Qwen from the latest benchmark run on Ruby 4.0.1. - 2026-05-30
|
|
244
|
-
|
|
245
|
-
### Key Takeaways
|
|
246
|
-
|
|
247
|
-
- **Mint is consistently faster** than the Money gem across all measured operations.
|
|
248
|
-
- **Mint is 2.28x faster** in the 50,000-transaction simulation.
|
|
249
|
-
- **Mint object creation is 2.76x faster** than `Money.from_amount`.
|
|
250
|
-
- In formatting and conversion, Mint is often **10+x **.
|
|
251
|
-
- Mint’s performance advantage is especially strong for numeric conversion, string formatting, comparisons, and high-volume transaction loops.
|
|
252
|
-
|
|
253
|
-
### Performance Highlights
|
|
254
|
-
|
|
255
|
-
| Category | Mint | Money | Approx. Ratio |
|
|
256
|
-
| --- | --- | --- | --- |
|
|
257
|
-
| High-volume transactions | 195,412 ops/sec | 85,882 ops/sec | 2.28x faster |
|
|
258
|
-
| `Mint.money` creation | 1.14M ops/sec | — | 2.76x faster than `Money.from_amount` |
|
|
259
|
-
| `some.dollars` creation | 990k ops/sec | — | 1.15x faster than `Mint.money` |
|
|
260
|
-
| `Money.new` creation | — | 715k ops/sec | Mint 1.59x faster |
|
|
261
|
-
| `to_f` formatting | 8.8M–9.3M ops/sec | 0.7M ops/sec | ~12x faster |
|
|
262
|
-
| `to_d` conversion | 2.1M–2.3M ops/sec | 0.73M–0.79M ops/sec | ~3x faster |
|
|
263
|
-
| `to_s` formatting | 300k–420k ops/sec | 109k–132k ops/sec | ~3x faster |
|
|
264
|
-
| `inspect` formatting | ~2.6–2.9M ops/sec | ~1.1–1.16M ops/sec | ~2.5x faster |
|
|
265
|
-
| `to_json` formatting | ~2.0–2.2M ops/sec | ~110k–126k ops/sec | ~17x faster |
|
|
266
|
-
| Currency lookup `Mint.currency('USD')` | 3.82M ops/sec | — | 1.60x faster than `Money::Currency.new` |
|
|
267
|
-
| Currency lookup `Money::Currency.find('USD')` | 3.63M ops/sec | 1.67M ops/sec | 2.29x faster |
|
|
268
|
-
| Addition | 1.11M ops/sec | 0.37M ops/sec | 3.0x faster |
|
|
269
|
-
| Subtraction | 1.11M ops/sec | 0.36M ops/sec | 3.0x faster |
|
|
270
|
-
| Multiplication | 1.28M ops/sec | 0.51M ops/sec | 2.5x faster |
|
|
271
|
-
| Division | 1.04M ops/sec | 0.37M ops/sec | 2.8x faster |
|
|
272
|
-
| Ratio division | 2.94M ops/sec | 0.39M ops/sec | 7.6x faster |
|
|
273
|
-
| Comparison (`==`, `<`, `>`) | 2.5M–4.1M ops/sec | 0.35M–0.38M ops/sec | 7x–10x faster |
|
|
274
|
-
| Allocation (`Mint.allocate`) | 279k ops/sec | 146k ops/sec | 1.9x faster |
|
|
275
|
-
| Split (`Mint.split`) | 215k ops/sec | 85k ops/sec | 3.3x faster |
|
|
276
|
-
|
|
277
|
-
### Commands Used
|
|
278
|
-
|
|
279
|
-
```sh
|
|
280
|
-
BENCH=true bundle exec ruby -Ilib:test -r ./test/test_helper.rb test/performance/competitive_performance_benchmark.rb
|
|
281
|
-
BENCH=true bundle exec ruby -Ilib:test -r ./test/test_helper.rb test/performance/competitive_memory_benchmark.rb
|
|
282
|
-
```
|
|
283
|
-
|
|
284
219
|
## License
|
|
285
220
|
|
|
286
221
|
MIT
|
data/Rakefile
CHANGED
|
@@ -12,33 +12,29 @@ Rake::TestTask.new(:test) do |t|
|
|
|
12
12
|
t.ruby_opts << '-rtest_helper.rb'
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
Rake::TestTask.new('bench') do |t|
|
|
15
|
+
Rake::TestTask.new('bench:all') do |t|
|
|
16
16
|
t.libs = %w[lib test]
|
|
17
|
-
t.pattern = 'test/performance
|
|
17
|
+
t.pattern = 'test/performance/**/*_benchmark.rb'
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
Rake::TestTask.new('bench:
|
|
20
|
+
Rake::TestTask.new('bench:core') do |t|
|
|
21
21
|
t.libs = %w[lib test]
|
|
22
|
-
t.pattern = 'test/performance/parse_benchmark.rb'
|
|
23
|
-
t.ruby_opts << '-r test_helper.rb'
|
|
22
|
+
t.pattern = 'test/performance/core/parse_benchmark.rb'
|
|
24
23
|
end
|
|
25
24
|
|
|
26
|
-
Rake::TestTask.new('bench:
|
|
25
|
+
Rake::TestTask.new('bench:memory') do |t|
|
|
27
26
|
t.libs = %w[lib test]
|
|
28
|
-
t.pattern = 'test/performance/
|
|
29
|
-
t.ruby_opts << '-r test_helper.rb'
|
|
27
|
+
t.pattern = 'test/performance/memory/*_benchmark.rb'
|
|
30
28
|
end
|
|
31
29
|
|
|
32
30
|
Rake::TestTask.new('bench:regression') do |t|
|
|
33
31
|
t.libs = %w[lib test]
|
|
34
|
-
t.pattern = 'test/performance/
|
|
35
|
-
t.ruby_opts << '-r test_helper.rb'
|
|
32
|
+
t.pattern = 'test/performance/regression/*_benchmark.rb'
|
|
36
33
|
end
|
|
37
34
|
|
|
38
35
|
Rake::TestTask.new('bench:competitive') do |t|
|
|
39
36
|
t.libs = %w[lib test]
|
|
40
|
-
t.pattern = 'test/performance/
|
|
41
|
-
t.ruby_opts << '-r test_helper.rb'
|
|
37
|
+
t.pattern = 'test/performance/competitive/**/*_benchmark.rb'
|
|
42
38
|
end
|
|
43
39
|
|
|
44
40
|
RuboCop::RakeTask.new(:cop)
|
data/doc/agents/AGENTS.md
CHANGED
|
@@ -20,6 +20,6 @@ Project highlights & conventions:
|
|
|
20
20
|
- Amounts stored as Rational; prefer rationals or decimal strings for precision
|
|
21
21
|
- Zero-equality: zeros equal across currencies; non-zero comparisons require same currency
|
|
22
22
|
- Currency codes must match /^[A-Z_]+$/
|
|
23
|
-
- Tests: Minitest; performance benches under test/performance
|
|
23
|
+
- Tests: Minitest; performance benches under test/performance
|
|
24
24
|
|
|
25
25
|
Edit guidance: keep this file minimal and link to existing docs; add repo-specific agent tips here.
|
|
@@ -21,9 +21,9 @@ Build, test and lint commands
|
|
|
21
21
|
- Run a single test method by name (Minitest -n regexp):
|
|
22
22
|
- ruby -Ilib:test -r ./test/test_helper.rb test/money/money_test.rb -n /test_creation/
|
|
23
23
|
|
|
24
|
-
- Performance suites
|
|
25
|
-
-
|
|
26
|
-
-
|
|
24
|
+
- Performance suites:
|
|
25
|
+
- rake bench:performance
|
|
26
|
+
- rake bench:competitive
|
|
27
27
|
- rake bench:regression
|
|
28
28
|
|
|
29
29
|
- Linting:
|
|
@@ -56,7 +56,7 @@ Key conventions and repo-specific rules
|
|
|
56
56
|
- Currency registration: use Mint.register_currency for idempotent registration; Mint.register_currency! raises on duplicates. Codes must match /^[A-Z_]+$/.
|
|
57
57
|
- Symbol parsing: parser resolves symbols by longest match then currency priority (see Mint.currency_symbols sorting).
|
|
58
58
|
- Tests: test_helper.rb configures coverage (SimpleCov) and loads minitest; when running tests outside rake, require test_helper (-r ./test/test_helper.rb).
|
|
59
|
-
- Benchmarks: set
|
|
59
|
+
- Benchmarks: set to enable benchmark-heavy tasks; benchmarks use Minitest::Benchmark patterns.
|
|
60
60
|
- Formatting: Money.to_s uses Kernel.format patterns; take care with %<amount>f vs %<amount>d depending on desired rounding/formatting.
|
|
61
61
|
|
|
62
62
|
Files and places to check first during edits
|
|
@@ -87,7 +87,7 @@ Qwen run on 2026-05-30 and aren't reproducible from CI.
|
|
|
87
87
|
**Concrete action:** in CI, run
|
|
88
88
|
|
|
89
89
|
```sh
|
|
90
|
-
|
|
90
|
+
ake bench:regression
|
|
91
91
|
```
|
|
92
92
|
|
|
93
93
|
and fail the build if any benchmark regresses by more than (e.g.)
|
|
@@ -208,7 +208,7 @@ property test:
|
|
|
208
208
|
|
|
209
209
|
```ruby
|
|
210
210
|
m = Mint.money(9.99, 'USD')
|
|
211
|
-
assert_equal m, Mint
|
|
211
|
+
assert_equal m, Mint.parse(m.inspect.delete_prefix('[').delete_suffix(']'))
|
|
212
212
|
```
|
|
213
213
|
|
|
214
214
|
Make it a one-liner or a property-based test with 10–20 random
|
|
@@ -1,34 +1,36 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Mint
|
|
4
|
-
# Represents a specific currency unit, identified by ISO 4217 alphabetic code
|
|
4
|
+
# Represents a specific currency unit, identified by ISO 4217 alphabetic code.
|
|
5
|
+
# Currency objects are immutable and define the properties of a monetary unit
|
|
6
|
+
# including its subunit precision, display symbol, and formatting rules.
|
|
5
7
|
#
|
|
6
8
|
# @see https://www.iso.org/iso-4217-currency-codes.html
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
# @attr_reader code [String] ISO 4217 currency code (e.g., "USD", "EUR")
|
|
10
|
+
# @attr_reader subunit [Integer] Number of decimal places (0 for JPY, 2 for USD, 3 for IQD)
|
|
11
|
+
# @attr_reader symbol [String] Display symbol (e.g., "$", "€", "R$")
|
|
12
|
+
# @attr_reader priority [Integer] Parser precedence for symbol detection
|
|
13
|
+
# @attr_reader country [String, nil] Associated country code
|
|
14
|
+
# @attr_reader name [String, nil] Currency name
|
|
15
|
+
# @attr_reader fractional_multiplier [Integer] 10^subunit, used for fractional conversions
|
|
16
|
+
# @attr_reader minimum_amount [Rational] Smallest representable amount (1/fractional_multiplier)
|
|
17
|
+
Currency = Data.define(:code, :subunit, :symbol, :priority, :country, :name,
|
|
18
|
+
:fractional_multiplier) do
|
|
19
|
+
def initialize(code:, symbol:, subunit: 0, priority: 0, country: nil, name: nil)
|
|
20
|
+
subunit = subunit.to_i
|
|
21
|
+
priority = priority.to_i
|
|
22
|
+
fractional_multiplier = 10**subunit
|
|
23
|
+
super(code:, subunit:, symbol:, priority:, country:, name:,
|
|
24
|
+
fractional_multiplier:)
|
|
25
|
+
end
|
|
12
26
|
|
|
13
27
|
def inspect = "<Currency:(#{code} #{symbol} #{subunit} #{name})>"
|
|
14
28
|
|
|
29
|
+
def minimum_amount = Rational(1, fractional_multiplier)
|
|
30
|
+
|
|
15
31
|
# Normalizes numeric amounts for this currency
|
|
16
32
|
# 1. Converts to Rational
|
|
17
33
|
# 2. Rounds to respect currency subunit
|
|
18
34
|
def normalize_amount(amount) = amount.to_r.round(subunit)
|
|
19
|
-
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
def initialize(code:, symbol:, subunit: 0, priority: 0, country: nil, name: nil)
|
|
23
|
-
@code = code
|
|
24
|
-
@subunit = subunit.to_i
|
|
25
|
-
@symbol = symbol
|
|
26
|
-
@priority = priority.to_i
|
|
27
|
-
@country = country
|
|
28
|
-
@name = name
|
|
29
|
-
@fractional_multiplier = 10**@subunit
|
|
30
|
-
@minimum_amount = Rational(1, fractional_multiplier)
|
|
31
|
-
freeze
|
|
32
|
-
end
|
|
33
35
|
end
|
|
34
36
|
end
|
|
@@ -4,25 +4,24 @@ require 'yaml'
|
|
|
4
4
|
|
|
5
5
|
# Mint currency store (internal)
|
|
6
6
|
module Mint
|
|
7
|
-
# Internal currency
|
|
7
|
+
# Internal currency registry
|
|
8
8
|
# Manages the registry cache and currency symbol lookups.
|
|
9
|
-
module
|
|
9
|
+
module Registry
|
|
10
|
+
module_function
|
|
11
|
+
|
|
10
12
|
# Returns the hash of all registered currencies.
|
|
11
13
|
#
|
|
12
14
|
# @return [Hash{String => Currency}] registered currencies mapped by code
|
|
13
15
|
# @api private
|
|
14
|
-
def
|
|
15
|
-
@currencies ||=
|
|
16
|
-
registry = { 'XXX' => Currency.new(code: 'XXX', name: 'No currency', symbol: '¤') }
|
|
17
|
-
load_currencies(registry)
|
|
18
|
-
end
|
|
16
|
+
def currencies
|
|
17
|
+
@currencies ||= Mint.world_currencies.dup
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
# Registered symbols sorted for detection: longest match wins, then parser priority.
|
|
22
21
|
#
|
|
23
22
|
# @return [Array<Array<String, Currency>>] sorted symbol-to-currency mappings
|
|
24
23
|
# @api private
|
|
25
|
-
def
|
|
24
|
+
def currency_symbols
|
|
26
25
|
@currency_symbols ||= begin
|
|
27
26
|
currencies.values
|
|
28
27
|
.reject { |currency| currency.symbol.empty? }
|
|
@@ -35,34 +34,8 @@ module Mint
|
|
|
35
34
|
# Called when currencies are registered.
|
|
36
35
|
#
|
|
37
36
|
# @api private
|
|
38
|
-
def
|
|
37
|
+
def invalidate_symbols_cache
|
|
39
38
|
@currency_symbols = nil
|
|
40
39
|
end
|
|
41
|
-
|
|
42
|
-
# Loads currencies from YAML file into the registry.
|
|
43
|
-
#
|
|
44
|
-
# @param registry [Hash] the registry hash to populate
|
|
45
|
-
# @return [Hash] the populated registry
|
|
46
|
-
# @api private
|
|
47
|
-
def self.load_currencies(registry)
|
|
48
|
-
base = File.expand_path('../data', __dir__)
|
|
49
|
-
path = File.join(base, 'currencies.yaml')
|
|
50
|
-
|
|
51
|
-
data = YAML.load_file(path)
|
|
52
|
-
data.each do |entry|
|
|
53
|
-
code = entry['code']
|
|
54
|
-
registry[code] = Currency.new(
|
|
55
|
-
code: code,
|
|
56
|
-
subunit: entry['subunit'],
|
|
57
|
-
symbol: entry['symbol'],
|
|
58
|
-
priority: entry['priority'],
|
|
59
|
-
country: entry['country'],
|
|
60
|
-
name: entry['name']
|
|
61
|
-
)
|
|
62
|
-
end
|
|
63
|
-
registry
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
private_class_method :load_currencies
|
|
67
40
|
end
|
|
68
41
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Mint currency registration and factory (public API)
|
|
4
|
+
module Mint
|
|
5
|
+
# Unknown currency excpetion
|
|
6
|
+
class UnknownCurrency < StandardError
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Creates a new {Money} instance with the given amount and currency code.
|
|
10
|
+
#
|
|
11
|
+
# @param amount [Numeric] the financial value
|
|
12
|
+
# @param currency_code [Currency, String] Currency code
|
|
13
|
+
# @return [Money] the instantiated Money object
|
|
14
|
+
# @raise [ArgumentError] if the currency code is not registered
|
|
15
|
+
def self.money(amount, currency_code) = Money.create(amount, currency_code)
|
|
16
|
+
|
|
17
|
+
# Finds a registered currency by its code, symbol,
|
|
18
|
+
# or retrieves it directly if already a Currency object.
|
|
19
|
+
#
|
|
20
|
+
# @param currency [String, Currency] the currency identifier or object
|
|
21
|
+
# @return [Currency, nil] the registered Currency instance or nil if not found
|
|
22
|
+
def self.currency(currency)
|
|
23
|
+
case currency
|
|
24
|
+
when nil then nil
|
|
25
|
+
when Currency then currency
|
|
26
|
+
when String then Registry.currencies[currency]
|
|
27
|
+
else raise ArgumentError, "currency must be [Currency] ot [String] (#{currency})"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Mint Money parsing
|
|
4
|
+
module Mint
|
|
5
|
+
extend self
|
|
6
|
+
|
|
7
|
+
# Parses a human-readable money string into a {Money} object.
|
|
8
|
+
#
|
|
9
|
+
# @param input [String] Amount input, optionally including a currency symbol or code
|
|
10
|
+
# @param currency [String, Symbol, Currency, nil] ISO code when not present in +input+
|
|
11
|
+
# @return [Money]
|
|
12
|
+
# @raise [ArgumentError] when +input+ is invalid or currency cannot be determined
|
|
13
|
+
#
|
|
14
|
+
# @example With explicit currency
|
|
15
|
+
# Money.parse('19.99', 'USD') #=> [USD 19.99]
|
|
16
|
+
# Money.parse('1.234,56', 'EUR') #=> [EUR 1234.56]
|
|
17
|
+
#
|
|
18
|
+
# @example With symbol or code in the string
|
|
19
|
+
# Money.parse('$19.99') #=> [USD 19.99]
|
|
20
|
+
# Money.parse('19,99 €') #=> [EUR 19.99]
|
|
21
|
+
# Money.parse('USD 1,234.56') #=> [USD 1234.56]
|
|
22
|
+
def parse(input, currency = nil)
|
|
23
|
+
raise ArgumentError, 'input must be a String' unless input.is_a?(String)
|
|
24
|
+
|
|
25
|
+
input = input.strip
|
|
26
|
+
raise ArgumentError, 'input cannot be empty' if input.empty?
|
|
27
|
+
|
|
28
|
+
currency = Mint.currency(currency) || parse_currency(input)
|
|
29
|
+
raise ArgumentError, "Currency [#{currency}] not registered" unless currency
|
|
30
|
+
|
|
31
|
+
amount = currency.normalize_amount(parse_amount(input))
|
|
32
|
+
Mint::Money.new(amount, currency)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
# Extracts a numeric value from input that should only contain an amount.
|
|
38
|
+
def parse_amount(input)
|
|
39
|
+
# Remove any charater that is not a digit, comma or period
|
|
40
|
+
numeric = input.scan(/[\d.,-]/).join
|
|
41
|
+
numeric = normalize_separators(numeric)
|
|
42
|
+
Rational(numeric)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Converts locale-specific decimal/thousand separators into a plain decimal string.
|
|
46
|
+
def normalize_separators(numeric)
|
|
47
|
+
case [numeric.count(','), numeric.count('.')]
|
|
48
|
+
in [0, 0] | [0, 1] then numeric # Nothing to normalize (e.g. "1500" or "34.21").
|
|
49
|
+
in [1, 0] then numeric.tr(',', '.') # Only one comma: decimal (e.g. 19,99 or 1,234).
|
|
50
|
+
in [c, p] if c > 1 && p > 1 # Both separators appear multiple times
|
|
51
|
+
raise ArgumentError, "could not distinguish decimal and thousand separators in '#{numeric}'"
|
|
52
|
+
in [c, p] if c > 0 && p > 0 # Commas and dots: the rightmost one is the decimal separator.
|
|
53
|
+
if numeric.rindex(',') > numeric.rindex('.')
|
|
54
|
+
numeric.delete('.').tr(',', '.')
|
|
55
|
+
else
|
|
56
|
+
numeric.delete(',')
|
|
57
|
+
end
|
|
58
|
+
else # Multiple of the same separator only (e.g. 1,234,567) — all are thousands.
|
|
59
|
+
numeric.delete(',.')
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def parse_currency(input)
|
|
64
|
+
case input
|
|
65
|
+
when nil then return nil
|
|
66
|
+
when String
|
|
67
|
+
# Prefer an explicit ISO 4217 code (e.g. "USD 1,234.56") over symbol matching.
|
|
68
|
+
currency = Mint.currency(input[/\b([A-Z_]+)\b/, 1])
|
|
69
|
+
return currency if currency
|
|
70
|
+
|
|
71
|
+
# Fall back to registered symbols, longest first (HK$ before $).
|
|
72
|
+
Mint.currency_symbols.each do |symbol, currency|
|
|
73
|
+
return currency if input.include?(symbol)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
raise ArgumentError, 'Currency could not be detected'
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -2,61 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
# Mint currency registration and factory (public API)
|
|
4
4
|
module Mint
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# @param amount [Numeric] the financial value
|
|
8
|
-
# @param currency_code [String] the ISO currency code
|
|
9
|
-
# @return [Money] the instantiated Money object
|
|
10
|
-
# @raise [ArgumentError] if the currency code is not registered
|
|
11
|
-
def self.money(amount, currency_code)
|
|
12
|
-
currency = currency(currency_code)
|
|
13
|
-
return Money.create(amount, currency) if currency
|
|
14
|
-
|
|
15
|
-
raise ArgumentError, "[#{currency.inspect}] is not a registered currency."
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# Finds a registered currency by its code, symbol,
|
|
19
|
-
# or retrieves it directly if already a Currency object.
|
|
20
|
-
#
|
|
21
|
-
# @param currency [String, Currency] the currency identifier or object
|
|
22
|
-
# @return [Currency, nil] the registered Currency instance or nil if not found
|
|
23
|
-
def self.currency(currency)
|
|
24
|
-
currency.is_a?(Currency) ? currency : CurrencyStore.currencies[currency]
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Registers a new currency if not already registered.
|
|
28
|
-
#
|
|
29
|
-
# @param code [String] the unique currency code (e.g. 'USD', 'EUR')
|
|
30
|
-
# @param subunit [Integer] the decimal subunit precision (defaults to 2)
|
|
31
|
-
# @param symbol [String] the display symbol (defaults to '')
|
|
32
|
-
# @param priority [Integer] parser precedence priority (defaults to 0)
|
|
33
|
-
# @return [Currency] the registered or existing Currency instance
|
|
34
|
-
# @raise [ArgumentError] if the code layout is invalid or register throws an error
|
|
35
|
-
def self.register_currency(code:, subunit: 0, symbol: '', priority: 0)
|
|
36
|
-
CurrencyStore.currencies[code] || register_currency!(code:, subunit:, symbol:, priority:)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Strictly registers a new currency, raising a KeyError if already registered.
|
|
5
|
+
# Registers a new currency, raising a KeyError if already registered.
|
|
40
6
|
#
|
|
41
7
|
# @param code [String] the unique currency code
|
|
42
|
-
# @param subunit [Integer] the decimal subunit precision
|
|
8
|
+
# @param subunit [Integer] the decimal subunit precision, defaults to 0
|
|
43
9
|
# @param symbol [String] the display symbol
|
|
44
10
|
# @param priority [Integer] parser precedence priority
|
|
45
11
|
# @return [Currency] the newly registered Currency instance
|
|
46
12
|
# @raise [ArgumentError] if the code contains invalid characters
|
|
47
13
|
# @raise [KeyError] if the currency code is already registered
|
|
48
|
-
def self.register_currency
|
|
14
|
+
def self.register_currency(code:, subunit: 0, symbol: '', priority: 0)
|
|
49
15
|
raise ArgumentError, 'Currency code must be String' unless code.is_a? String
|
|
50
16
|
unless code.match?(/^[A-Z_]+$/)
|
|
51
17
|
raise ArgumentError,
|
|
52
|
-
"Currency code must only letters or '_' ('USD',, 'MY_COIN')"
|
|
18
|
+
"Currency code must have only letters or '_' ('USD',, 'MY_COIN')"
|
|
53
19
|
end
|
|
54
20
|
|
|
55
|
-
currencies =
|
|
21
|
+
currencies = Registry.currencies
|
|
56
22
|
raise KeyError, "Currency: #{code} already registered" if currencies[code]
|
|
57
23
|
|
|
58
24
|
currency = currencies[code] = Currency.new(code:, subunit:, symbol:, priority:)
|
|
59
|
-
|
|
25
|
+
Registry.invalidate_symbols_cache
|
|
60
26
|
currency
|
|
61
27
|
end
|
|
62
28
|
|
|
@@ -66,6 +32,6 @@ module Mint
|
|
|
66
32
|
# @return [Array<Array<String, Currency>>] sorted symbol-to-currency mappings
|
|
67
33
|
# @api private
|
|
68
34
|
def self.currency_symbols
|
|
69
|
-
|
|
35
|
+
Registry.currency_symbols
|
|
70
36
|
end
|
|
71
37
|
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mint
|
|
4
|
+
module_function
|
|
5
|
+
|
|
6
|
+
# Loads ISO world currencies from YAML file into the registry.
|
|
7
|
+
#
|
|
8
|
+
# @return [Hash{String => Currency}] ISO-4217 world currencies mapped by code
|
|
9
|
+
# @api private
|
|
10
|
+
def world_currencies
|
|
11
|
+
@world_currencies ||= begin
|
|
12
|
+
path = File.join(File.expand_path('../data', __dir__), 'currencies.yaml')
|
|
13
|
+
|
|
14
|
+
YAML.load_file(path).to_h { |entry| [entry['code'], Currency.new(**entry.transform_keys(&:to_sym))] }
|
|
15
|
+
end.freeze
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/minting/mint.rb
CHANGED
|
@@ -2,5 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
require 'minting/mint/currency'
|
|
4
4
|
require 'minting/mint/currency_store'
|
|
5
|
+
require 'minting/mint/mint'
|
|
6
|
+
require 'minting/mint/parser'
|
|
5
7
|
require 'minting/mint/registry'
|
|
6
8
|
require 'minting/mint/refinements'
|
|
9
|
+
require 'minting/mint/world_currencies'
|
|
10
|
+
require 'minting/money/allocation'
|
|
11
|
+
require 'minting/money/arithmetics'
|
|
12
|
+
require 'minting/money/coercion'
|
|
13
|
+
require 'minting/money/comparable'
|
|
14
|
+
require 'minting/money/constructors'
|
|
15
|
+
require 'minting/money/conversion'
|
|
16
|
+
require 'minting/money/formatting'
|
|
17
|
+
require 'minting/money/money'
|
|
@@ -18,7 +18,7 @@ module Mint
|
|
|
18
18
|
raise ArgumentError, 'Proportions total must not be zero' if whole.zero?
|
|
19
19
|
|
|
20
20
|
subunit = currency.subunit
|
|
21
|
-
amounts = proportions.map { |rate| (amount * rate
|
|
21
|
+
amounts = proportions.map { |rate| Rational(amount * rate, whole).round(subunit) }
|
|
22
22
|
allocate_left_over!(amounts: amounts, left_over: amount - amounts.sum)
|
|
23
23
|
end
|
|
24
24
|
|
|
@@ -53,9 +53,7 @@ module Mint
|
|
|
53
53
|
# Unary negation operator. Returns a new {Money} instance with the inverted sign.
|
|
54
54
|
#
|
|
55
55
|
# @return [Money] negated Money instance
|
|
56
|
-
def -@
|
|
57
|
-
mint(-amount)
|
|
58
|
-
end
|
|
56
|
+
def -@ = mint(-amount)
|
|
59
57
|
|
|
60
58
|
# Performs multiplication of the monetary value by a standard scalar Numeric.
|
|
61
59
|
#
|
|
@@ -4,9 +4,13 @@ module Mint
|
|
|
4
4
|
# Implements the standard Ruby coercion protocol.
|
|
5
5
|
class Money
|
|
6
6
|
# Allows {Money} to interact seamlessly as the right-hand operand in Numeric arithmetic.
|
|
7
|
+
# This enables expressions like `5 + money` where `5` is a Numeric and `money` is a Money object.
|
|
7
8
|
#
|
|
8
9
|
# @param other [Numeric] the left-hand operand to coerce
|
|
9
10
|
# @return [Array(CoercedNumber, Money)] coerced operand array
|
|
11
|
+
# @example
|
|
12
|
+
# price = Mint.money(10, 'USD')
|
|
13
|
+
# 5 + price #=> [USD 15.00] (via coercion)
|
|
10
14
|
def coerce(other)
|
|
11
15
|
[CoercedNumber.new(other), self]
|
|
12
16
|
end
|
|
@@ -7,9 +7,11 @@ module Mint
|
|
|
7
7
|
|
|
8
8
|
# @return true if both are zero, or both have same amount and same currency
|
|
9
9
|
def ==(other)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
case other
|
|
11
|
+
when 0 then zero?
|
|
12
|
+
when Mint::Money then amount == other.amount && currency == other.currency
|
|
13
|
+
else false
|
|
14
|
+
end
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
def eql?(other)
|
|
@@ -5,7 +5,7 @@ module Mint
|
|
|
5
5
|
class Money
|
|
6
6
|
# Creates a new Money immutable object with the specified amount and currency
|
|
7
7
|
# @param amount [Numeric] The monetary amount
|
|
8
|
-
# @param currency [Currency] The currency object
|
|
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
10
|
def self.create(amount, currency)
|
|
11
11
|
raise ArgumentError, 'amount must be Numeric' unless amount.is_a?(Numeric)
|
|
@@ -44,9 +44,16 @@ module Mint
|
|
|
44
44
|
new(amount, checked_currency)
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
# Returns a new Money object with the specified amount, or self if unchanged
|
|
48
|
-
#
|
|
49
|
-
#
|
|
47
|
+
# Returns a new Money object with the specified amount, or self if unchanged.
|
|
48
|
+
# This is the primary method for creating a modified copy of a Money instance
|
|
49
|
+
# while preserving immutability.
|
|
50
|
+
#
|
|
51
|
+
# @param new_amount [Numeric] The new monetary amount
|
|
52
|
+
# @return [Money] A new Money object with the new amount, or self if the amount is unchanged
|
|
53
|
+
# @example
|
|
54
|
+
# price = Mint.money(10.00, 'USD')
|
|
55
|
+
# price.mint(15.00) #=> [USD 15.00]
|
|
56
|
+
# price.mint(10.00) #=> [USD 10.00] (returns self)
|
|
50
57
|
def mint(new_amount)
|
|
51
58
|
new_amount = currency.normalize_amount(new_amount)
|
|
52
59
|
new_amount == amount ? self : Money.new(new_amount, currency)
|
|
@@ -9,6 +9,8 @@ module Mint
|
|
|
9
9
|
# Converts the monetary amount to a BigDecimal object.
|
|
10
10
|
#
|
|
11
11
|
# @return [BigDecimal] the decimal representation of the money amount
|
|
12
|
+
# @example
|
|
13
|
+
# Mint.money(9.99, 'USD').to_d #=> 0.999e1
|
|
12
14
|
def to_d = amount.to_d 0
|
|
13
15
|
|
|
14
16
|
# Converts the monetary amount to a standard float.
|
|
@@ -31,8 +33,17 @@ module Mint
|
|
|
31
33
|
# Truncates and converts the monetary amount to an Integer.
|
|
32
34
|
#
|
|
33
35
|
# @return [Integer] the integer representation of the money amount
|
|
36
|
+
# @example
|
|
37
|
+
# Mint.money(9.99, 'USD').to_i #=> 9
|
|
38
|
+
# Mint.money(-9.99, 'USD').to_i #=> -9
|
|
34
39
|
def to_i = amount.to_i
|
|
35
40
|
|
|
41
|
+
# Returns a Hash representation of the money instance.
|
|
42
|
+
#
|
|
43
|
+
# @return [Hash] hash with :currency (String) and :amount (String) keys
|
|
44
|
+
# @example
|
|
45
|
+
# Mint.money(134120, 'BRL').to_hash
|
|
46
|
+
# #=> { currency: "BRL", amount: "134120.00" }
|
|
36
47
|
def to_hash
|
|
37
48
|
{ currency: currency_code, amount: Kernel.format("%0.#{currency.subunit}f", amount) }
|
|
38
49
|
end
|
data/lib/minting/money/money.rb
CHANGED
|
@@ -11,9 +11,19 @@ module Mint
|
|
|
11
11
|
|
|
12
12
|
# Returns the ISO 3-letter currency code string.
|
|
13
13
|
#
|
|
14
|
-
# @return [String] the ISO currency code
|
|
14
|
+
# @return [String] the ISO currency code (e.g., "USD", "EUR", "BRL")
|
|
15
|
+
# @example
|
|
16
|
+
# Mint.money(100, 'USD').currency_code #=> "USD"
|
|
15
17
|
def currency_code = currency.code
|
|
16
18
|
|
|
19
|
+
# Returns the monetary amount expressed in the currency's smallest unit (fractional units).
|
|
20
|
+
# For example, cents for USD (subunit 2), yen for JPY (subunit 0), fils for IQD (subunit 3).
|
|
21
|
+
#
|
|
22
|
+
# @return [Integer] the amount in fractional units
|
|
23
|
+
# @example
|
|
24
|
+
# Mint.money(1234.56, 'USD').fractional #=> 123456
|
|
25
|
+
# Mint.money(1000, 'JPY').fractional #=> 1000
|
|
26
|
+
# Mint.money(123.456, 'IQD').fractional #=> 123456
|
|
17
27
|
def fractional = (amount * currency.fractional_multiplier).to_i
|
|
18
28
|
|
|
19
29
|
# Generates a stable hash key for Money instances.
|
data/lib/minting/version.rb
CHANGED
data/lib/minting.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.6.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gilson Ferraz
|
|
@@ -44,9 +44,11 @@ files:
|
|
|
44
44
|
- lib/minting/mint.rb
|
|
45
45
|
- lib/minting/mint/currency.rb
|
|
46
46
|
- lib/minting/mint/currency_store.rb
|
|
47
|
+
- lib/minting/mint/mint.rb
|
|
48
|
+
- lib/minting/mint/parser.rb
|
|
47
49
|
- lib/minting/mint/refinements.rb
|
|
48
50
|
- lib/minting/mint/registry.rb
|
|
49
|
-
- lib/minting/
|
|
51
|
+
- lib/minting/mint/world_currencies.rb
|
|
50
52
|
- lib/minting/money/allocation.rb
|
|
51
53
|
- lib/minting/money/arithmetics.rb
|
|
52
54
|
- lib/minting/money/coercion.rb
|
|
@@ -55,7 +57,6 @@ files:
|
|
|
55
57
|
- lib/minting/money/conversion.rb
|
|
56
58
|
- lib/minting/money/formatting.rb
|
|
57
59
|
- lib/minting/money/money.rb
|
|
58
|
-
- lib/minting/money/parse.rb
|
|
59
60
|
- lib/minting/version.rb
|
|
60
61
|
- minting.gemspec
|
|
61
62
|
homepage: https://github.com/gferraz/minting
|
data/lib/minting/money/parse.rb
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Mint
|
|
4
|
-
# Money parser
|
|
5
|
-
class Money
|
|
6
|
-
# Parses a human-readable money string into a {Money} object.
|
|
7
|
-
#
|
|
8
|
-
# @param input [String] Amount input, optionally including a currency symbol or code
|
|
9
|
-
# @param currency [String, Symbol, Currency, nil] ISO code when not present in +input+
|
|
10
|
-
# @return [Money]
|
|
11
|
-
# @raise [ArgumentError] when +input+ is invalid or currency cannot be determined
|
|
12
|
-
#
|
|
13
|
-
# @example With explicit currency
|
|
14
|
-
# Money.parse('19.99', 'USD') #=> [USD 19.99]
|
|
15
|
-
# Money.parse('1.234,56', 'EUR') #=> [EUR 1234.56]
|
|
16
|
-
#
|
|
17
|
-
# @example With symbol or code in the string
|
|
18
|
-
# Money.parse('$19.99') #=> [USD 19.99]
|
|
19
|
-
# Money.parse('19,99 €') #=> [EUR 19.99]
|
|
20
|
-
# Money.parse('USD 1,234.56') #=> [USD 1234.56]
|
|
21
|
-
def self.parse(input, currency = nil)
|
|
22
|
-
raise ArgumentError, 'input must be a String' unless input.is_a?(String)
|
|
23
|
-
|
|
24
|
-
input = input.strip
|
|
25
|
-
raise ArgumentError, 'input cannot be empty' if input.empty?
|
|
26
|
-
|
|
27
|
-
currency = parse_currency(currency) || parse_currency(input)
|
|
28
|
-
raise ArgumentError, "Currency [#{currency}] not registered" unless currency
|
|
29
|
-
|
|
30
|
-
amount = currency.normalize_amount(parse_amount(input))
|
|
31
|
-
new(amount, currency)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Extracts a numeric value from input that should only contain an amount.
|
|
35
|
-
def self.parse_amount(input)
|
|
36
|
-
# Remove any charater that is not a digit, comma or period
|
|
37
|
-
numeric = input.scan(/[\d.,-]/).join
|
|
38
|
-
numeric = normalize_separators(numeric)
|
|
39
|
-
Rational(numeric)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Converts locale-specific decimal/thousand separators into a plain decimal string.
|
|
43
|
-
def self.normalize_separators(numeric)
|
|
44
|
-
case [numeric.count(','), numeric.count('.')]
|
|
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
|
-
in [c, p] if c > 1 && p > 1 # Both separators appear multiple times
|
|
48
|
-
raise ArgumentError, "could not distinguish decimal and thousand separators in '#{numeric}'"
|
|
49
|
-
in [c, p] if c > 0 && p > 0 # Commas and dots: the rightmost one is the decimal separator.
|
|
50
|
-
if numeric.rindex(',') > numeric.rindex('.')
|
|
51
|
-
numeric.delete('.').tr(',', '.')
|
|
52
|
-
else
|
|
53
|
-
numeric.delete(',')
|
|
54
|
-
end
|
|
55
|
-
else # Multiple of the same separator only (e.g. 1,234,567) — all are thousands.
|
|
56
|
-
numeric.delete(',.')
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def self.parse_currency(input)
|
|
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])
|
|
66
|
-
return currency if currency
|
|
67
|
-
|
|
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
|
|
72
|
-
end
|
|
73
|
-
raise ArgumentError, 'currency could not be detected; pass a currency code as the second argument'
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
private_class_method :parse_amount, :normalize_separators,
|
|
77
|
-
:parse_currency
|
|
78
|
-
end
|
|
79
|
-
end
|
data/lib/minting/money.rb
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'minting/money/parse'
|
|
4
|
-
require 'minting/money/allocation'
|
|
5
|
-
require 'minting/money/arithmetics'
|
|
6
|
-
require 'minting/money/coercion'
|
|
7
|
-
require 'minting/money/comparable'
|
|
8
|
-
require 'minting/money/constructors'
|
|
9
|
-
require 'minting/money/conversion'
|
|
10
|
-
require 'minting/money/formatting'
|
|
11
|
-
require 'minting/money/money'
|