minting 1.7.0 → 1.7.3
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 +80 -112
- data/Rakefile +13 -1
- data/bin/bench_check +46 -0
- data/doc/Mint/Currency.html +446 -46
- data/doc/Mint/CurrencyRegistry.html +7 -7
- data/doc/Mint/Money.html +203 -177
- data/doc/Mint/RangeStepPatch.html +277 -0
- data/doc/Mint/Registry.html +842 -0
- data/doc/Mint/UnknownCurrency.html +2 -2
- data/doc/Mint.html +385 -66
- data/doc/Minting.html +3 -3
- data/doc/_index.html +24 -9
- data/doc/agents/api_review-2026-06-15.md +342 -0
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +97 -89
- data/doc/index.html +97 -89
- data/doc/method_list.html +97 -25
- data/doc/top-level-namespace.html +13 -5
- data/lib/minting/currency/currency.rb +75 -0
- data/lib/minting/mint/aliases.rb +3 -0
- data/lib/minting/mint/dsl/{refinements.rb → numeric.rb} +6 -5
- data/lib/minting/mint/dsl/range.rb +31 -18
- data/lib/minting/mint/dsl/string.rb +12 -0
- data/lib/minting/mint/dsl/top_level.rb +3 -0
- data/lib/minting/mint/locale_backend.rb +29 -0
- data/lib/minting/mint/mint.rb +28 -12
- data/lib/minting/mint/parser/parser.rb +62 -0
- data/lib/minting/mint/parser/separators.rb +39 -0
- 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 +18 -0
- data/lib/minting/mint.rb +13 -16
- data/lib/minting/money/allocation/allocation.rb +25 -0
- data/lib/minting/money/{allocation.rb → allocation/split.rb} +1 -19
- data/lib/minting/money/arithmetics/methods.rb +27 -0
- data/lib/minting/money/{arithmetics.rb → arithmetics/operators.rb} +0 -21
- data/lib/minting/money/clamp.rb +66 -0
- data/lib/minting/money/coercion.rb +10 -0
- data/lib/minting/money/comparable.rb +6 -0
- data/lib/minting/money/constructors.rb +14 -9
- data/lib/minting/money/format/formatting.rb +60 -0
- data/lib/minting/money/{formatting.rb → format/to_s.rb} +13 -36
- data/lib/minting/money/money.rb +12 -58
- data/lib/minting/version.rb +1 -1
- metadata +29 -19
- data/lib/minting/mint/currency/currency.rb +0 -36
- data/lib/minting/mint/currency/currency_registry.rb +0 -67
- data/lib/minting/mint/currency/world_currencies.rb +0 -16
- data/lib/minting/mint/parser.rb +0 -85
- /data/doc/agents/{AGENTS.md → expired/AGENTS.md} +0 -0
- /data/doc/agents/{copilot-instructions.md → expired/copilot-instructions.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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7ebe31c2ae1b9febafa3a6a4ccea1392cb089242746a933d530121b08f55cd4e
|
|
4
|
+
data.tar.gz: 80f1eb17f62b7a7924df8b2fedb79d864bcadd5ea5e8432e5149b14fb0d9af0c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 44147d3523485fc36491bb62f24df5f4040fe3ddda673201d5b6fc9a5e005bda94cd3300b4e84682f4378cb9eedb851ad16c247a2dfeb9de264fd24a6b1714ca
|
|
7
|
+
data.tar.gz: 5f41672831c8c722e47de8b8e94549bb633c48b677fc8e63eca9f10233062f32a0acd276b31271a4f0ac4417779b03db7f034a9b9afcfbd39c0cbadb16886dbf
|
data/README.md
CHANGED
|
@@ -3,23 +3,10 @@
|
|
|
3
3
|
Fast, precise, and developer-friendly money handling for Ruby.
|
|
4
4
|
|
|
5
5
|
[](https://badge.fury.io/rb/minting)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
**Need performance?** Minting is 2× faster than alternatives for high-volume operations (often 10×+ for formatting). See the [Performance](https://github.com/gferraz/minting/blob/master/test/performance/README.md) section for full benchmarks.
|
|
12
|
-
|
|
13
|
-
**Want a clean API?** Minting provides an intuitive interface with helpful error messages.
|
|
14
|
-
|
|
15
|
-
**Looking for a proven alternative?** Check out the established [Money gem](https://github.com/RubyMoney/money) with thousands of stars on GitHub.
|
|
16
|
-
|
|
17
|
-
**Rails**? Use the [minting-rails](https://github.com/gferraz/minting-rails) companion gem
|
|
18
|
-
|
|
19
|
-
## Resources
|
|
20
|
-
|
|
21
|
-
- [API Documentation](https://www.rubydoc.info/gems/minting/frames)
|
|
22
|
-
- [Git Repository](https://github.com/gferraz/minting)
|
|
6
|
+
[](https://github.com/gferraz/minting/actions/workflows/ci.yml)
|
|
7
|
+
[](https://github.com/gferraz/minting)
|
|
8
|
+
[](https://github.com/gferraz/minting)
|
|
9
|
+
[](https://www.rubydoc.info/gems/minting/frames)
|
|
23
10
|
|
|
24
11
|
## Quick start
|
|
25
12
|
|
|
@@ -34,16 +21,44 @@ total.to_s #=> "$21.59"
|
|
|
34
21
|
total.currency_code #=> "USD"
|
|
35
22
|
```
|
|
36
23
|
|
|
37
|
-
##
|
|
24
|
+
## Why Minting?
|
|
25
|
+
|
|
26
|
+
| | Minting |
|
|
27
|
+
|--------------------|-------------------------------------------|
|
|
28
|
+
| **Precision** | Rational-based, zero floating-point error |
|
|
29
|
+
| **Performance** | **2× faster** (10×+ formatting) |
|
|
30
|
+
| **Ruby support** | 3.3+ (including Ruby 4.0) |
|
|
31
|
+
| **Rails** | Dedicated companion gem |
|
|
32
|
+
| **Code quality** | 100% coverage, 93/100 RubyCritic |
|
|
33
|
+
|
|
34
|
+
### 🎯 Exact precision
|
|
35
|
+
Amounts are stored as `Rational` and rounded to the currency subunit. No floating-point surprises, ever.
|
|
36
|
+
|
|
37
|
+
### ⚡ Blazing performance
|
|
38
|
+
Minting is **2× faster** than the Money gem for everyday operations and **over 10× faster for formatting**. See full benchmarks in the [Performance Guide](test/performance/README.md).
|
|
39
|
+
|
|
40
|
+
### 🧼 Clean, modern API
|
|
41
|
+
Intuitive interface, descriptive error messages, and sensible defaults. Works the way you expect.
|
|
42
|
+
|
|
43
|
+
### 🚆 Rails-ready
|
|
44
|
+
Use with the [minting-rails](https://github.com/gferraz/minting-rails) companion gem for drop-in ActiveRecord type casting, validators, and form helpers.
|
|
38
45
|
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
### 🏆 Quality you can trust
|
|
47
|
+
- **100% test coverage** — every line exercised
|
|
48
|
+
- **93/100 RubyCritic score** — clean, maintainable code
|
|
49
|
+
- **CI-tested on Ruby 3.3 and 4.0**
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
```shell
|
|
54
|
+
bundle add minting
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Or add to your Gemfile:
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
gem 'minting'
|
|
61
|
+
```
|
|
47
62
|
|
|
48
63
|
## Usage
|
|
49
64
|
|
|
@@ -66,16 +81,15 @@ ten == Mint.money(10, 'EUR') #=> false
|
|
|
66
81
|
ten > Mint.money(9.99, 'USD') #=> true
|
|
67
82
|
|
|
68
83
|
# Zero equality semantics
|
|
69
|
-
# Any zero amount is treated as equal, regardless of currency
|
|
70
|
-
Mint.money(0, 'USD') == Mint.money(0, 'EUR')
|
|
84
|
+
# Any zero amount is treated as equal, regardless of currency
|
|
85
|
+
Mint.money(0, 'USD') == Mint.money(0, 'EUR') #=> true
|
|
71
86
|
Mint.money(0, 'USD') == 0 #=> true
|
|
72
87
|
Mint.money(0, 'USD') == 0.0 #=> true
|
|
73
|
-
Mint.money(0, 'USD') == 0r #=> true
|
|
74
88
|
|
|
75
89
|
# Non-zero numerics are not equal to Money objects
|
|
76
90
|
Mint.money(10, 'USD') == 10 #=> false
|
|
77
91
|
|
|
78
|
-
# Format (uses Kernel.format
|
|
92
|
+
# Format (uses Kernel.format syntax)
|
|
79
93
|
price = Mint.money(9.99, 'USD')
|
|
80
94
|
|
|
81
95
|
price.to_s #=> "$9.99",
|
|
@@ -141,128 +155,82 @@ price.clamp(min_price, 100) #=> [USD 75.00]
|
|
|
141
155
|
|
|
142
156
|
```
|
|
143
157
|
|
|
144
|
-
##
|
|
145
|
-
|
|
146
|
-
**Module names** — Require the `minting` gem; the public API lives under `Mint`.
|
|
147
|
-
|
|
148
|
-
**Exact amounts** — Amounts are stored as `Rational` and rounded to the currency subunit.
|
|
149
|
-
|
|
150
|
-
**Refinements** — `10.dollars` and similar helpers require `using Mint` in the current scope (see Usage above).
|
|
151
|
-
|
|
152
|
-
**Division** — `money / 5` returns new `Money`; `money / other_money` returns a numeric ratio, not money.
|
|
153
|
-
|
|
154
|
-
**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.
|
|
155
|
-
|
|
156
|
-
**Custom currencies** — `Mint.register_currency`, Only registered currency codes and symbolos are recoginized by the parser.
|
|
157
|
-
|
|
158
|
-
**Built-in currencies** — ISO-style codes ship in `lib/minting/data/currencies.yaml` and load when the registry is first accessed.
|
|
159
|
-
|
|
160
|
-
## Installation
|
|
161
|
-
|
|
162
|
-
Option 1: Via bundler command
|
|
163
|
-
|
|
164
|
-
```shell
|
|
165
|
-
bundle add minting
|
|
166
|
-
bundle install
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
Option 2: add the line below to your application's Gemfile:
|
|
158
|
+
## Parsing strings
|
|
170
159
|
|
|
171
160
|
```ruby
|
|
172
|
-
|
|
161
|
+
Mint.parse('$19.99') #=> [USD 19.99]
|
|
162
|
+
Mint.parse('19,99 €') #=> [EUR 19.99]
|
|
163
|
+
Mint.parse('1.234,56', 'EUR') #=> [EUR 1234.56]
|
|
164
|
+
Mint.parse('USD 1,234.56') #=> [USD 1234.56]
|
|
173
165
|
```
|
|
174
166
|
|
|
175
|
-
|
|
167
|
+
Notes:
|
|
168
|
+
- Pass a currency code when the string has no symbol or code.
|
|
169
|
+
- `1,234` means 1234, not 1.234 and `1,23` means 1.23, not 123
|
|
170
|
+
- `1,234.00` is unambiguous (thousands + decimal).
|
|
171
|
+
- Accounting negatives like `($1.23)` are unsupported for now.
|
|
172
|
+
- Ambiguous symbols like `$` resolve by currency priority (currently USD).
|
|
173
|
+
- The parser scans all uppercase words for registered codes, so spurious non-currency words before the real code are correctly ignored: `Mint.parse("MAX 10.00 USD")` yields `[USD 10.00]`.
|
|
174
|
+
|
|
175
|
+
## Currency lookup
|
|
176
176
|
|
|
177
177
|
```ruby
|
|
178
|
-
|
|
179
|
-
|
|
178
|
+
# By ISO code (direct hash lookup, string only)
|
|
179
|
+
Mint.currency_for_code('USD') #=> #<Currency code="USD" ...>
|
|
180
180
|
|
|
181
|
-
|
|
181
|
+
# By display symbol (highest-priority currency for ambiguous symbols)
|
|
182
|
+
Mint.currency_for_symbol('$') #=> #<Currency code="USD" ...>
|
|
183
|
+
Mint.currency_for_symbol('R$') #=> #<Currency code="BRL" ...>
|
|
184
|
+
Mint.currency_for_symbol('€') #=> #<Currency code="EUR" ...>
|
|
182
185
|
|
|
183
|
-
```shell
|
|
184
|
-
bundle install
|
|
185
186
|
```
|
|
186
187
|
|
|
187
|
-
|
|
188
|
+
## API notes
|
|
188
189
|
|
|
189
|
-
|
|
190
|
-
gem install minting
|
|
191
|
-
```
|
|
190
|
+
**Exact amounts** — Amounts are stored as `Rational` and rounded to the currency subunit.
|
|
192
191
|
|
|
193
|
-
|
|
192
|
+
**Refinements** — `10.dollars` and similar helpers require `using Mint` in the current scope (see Usage above).
|
|
194
193
|
|
|
195
|
-
|
|
194
|
+
**Division** — `money / 5` returns new `Money`; `money / other_money` returns a numeric ratio, not money.
|
|
196
195
|
|
|
197
|
-
|
|
196
|
+
**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.
|
|
198
197
|
|
|
199
|
-
|
|
200
|
-
price = Mint::Money.create(10, "USD")
|
|
201
|
-
currency = Mint::Currency.new(code: "USD", symbol: "$", subunit: 2, priority: 0)
|
|
202
|
-
```
|
|
198
|
+
**Zero helper** — `Mint.zero('USD')` returns a frozen zero-Money, useful as a default value for discounts, totals, or counters.
|
|
203
199
|
|
|
204
|
-
|
|
200
|
+
**Registered currencies** — `Mint.register_currency(code:, subunit:, symbol:, priority:)` adds custom currencies. Only registered codes and symbols are recognized by the parser.
|
|
205
201
|
|
|
206
|
-
|
|
202
|
+
**Built-in currencies** — 150+ ISO-4217 world currencies ship in `lib/minting/data/currencies.yaml` and load when the registry is first accessed.
|
|
207
203
|
|
|
208
|
-
|
|
204
|
+
## Optional top-level `Money` and `Currency`
|
|
209
205
|
|
|
210
|
-
|
|
206
|
+
By default, Minting keeps everything namespaced under `Mint` to coexist nicely with other gems. If you prefer shorter constants, opt in:
|
|
211
207
|
|
|
212
208
|
```ruby
|
|
213
209
|
require "minting"
|
|
214
210
|
require "minting/dsl" # opt‑in top‑level Money / Currency
|
|
215
211
|
```
|
|
216
212
|
|
|
217
|
-
|
|
213
|
+
Or at runtime:
|
|
218
214
|
|
|
219
215
|
```ruby
|
|
220
216
|
Minting.use_top_level_constants!
|
|
221
217
|
```
|
|
222
218
|
|
|
223
|
-
After
|
|
219
|
+
After opting in:
|
|
224
220
|
|
|
225
221
|
```ruby
|
|
226
222
|
price = Money.create(10, "USD") # equivalent to Mint::Money.create
|
|
227
|
-
tax = Money.money(2.50, "USD")
|
|
223
|
+
tax = Money.money(2.50, "USD")
|
|
228
224
|
cur = Currency.new(code: "EUR", symbol: "€", subunit: 2, priority: 0)
|
|
229
225
|
```
|
|
230
226
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
- **Good fit:** application code, especially in Rails apps, where `Money` reads nicely in models and views.
|
|
234
|
-
- **Not recommended:** reusable gems/libraries. In that case, stick to the namespaced API (`Mint::Money`, `Mint::Currency`) to avoid conflicts with other libraries.
|
|
235
|
-
|
|
236
|
-
## Parsing strings
|
|
237
|
-
|
|
238
|
-
```ruby
|
|
239
|
-
Mint.parse('$19.99') #=> [USD 19.99]
|
|
240
|
-
Mint.parse('19,99 €') #=> [EUR 19.99]
|
|
241
|
-
Mint.parse('1.234,56', 'EUR') #=> [EUR 1234.56]
|
|
242
|
-
Mint.parse('USD 1,234.56') #=> [USD 1234.56]
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
- Pass a currency code when the string has no symbol or code.
|
|
246
|
-
- 1,234 means 1.234, not 1234, because one comma is treated as decimal.
|
|
247
|
-
- 1,234.00 is unambiguous thousands-plus-decimal.
|
|
248
|
-
- accounting negatives like ($1.23) are unsupported.
|
|
249
|
-
- ambiguous symbols like $ resolve by priority, currently USD.
|
|
227
|
+
**Good fit:** Application code, especially Rails apps.
|
|
228
|
+
**Not recommended:** Reusable gems/libraries — stick to `Mint::Money` to avoid conflicts.
|
|
250
229
|
|
|
251
230
|
## Roadmap
|
|
252
231
|
|
|
253
|
-
- Add support to configure thousand and decimal separators in parse (evaluating)
|
|
254
232
|
- Localization (I18n-aware formatting)
|
|
255
|
-
-
|
|
256
|
-
|
|
257
|
-
## Contributing
|
|
258
|
-
|
|
259
|
-
Bug reports and pull requests are welcome on GitHub at <https://github.com/gferraz/minting>.
|
|
260
|
-
|
|
261
|
-
1. Fork and create a feature branch
|
|
262
|
-
2. Run the test suite: `rake`
|
|
263
|
-
3. Run performance suites as needed: `rake bench:performance`
|
|
264
|
-
4. Open a PR with a clear description and benchmarks if relevant
|
|
265
|
-
|
|
233
|
+
- Exchange-rate conversion infrastructure
|
|
266
234
|
|
|
267
235
|
## License
|
|
268
236
|
|
data/Rakefile
CHANGED
|
@@ -39,6 +39,19 @@ Rake::TestTask.new('bench:competitive') do |t|
|
|
|
39
39
|
t.pattern = 'test/performance/competitive/**/*_benchmark.rb'
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
+
desc 'Run core benchmarks and update the baseline'
|
|
43
|
+
task 'bench:baseline' do
|
|
44
|
+
platform = RUBY_PLATFORM
|
|
45
|
+
baseline = "test/performance/check/results/baseline-#{platform}.json"
|
|
46
|
+
sh "ruby test/performance/check/runner.rb #{baseline}"
|
|
47
|
+
puts "Baseline updated for #{platform}."
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
desc 'Run core benchmarks and check for regressions against the baseline'
|
|
51
|
+
task 'bench:check' do
|
|
52
|
+
ruby 'bin/bench_check'
|
|
53
|
+
end
|
|
54
|
+
|
|
42
55
|
Bundler::Audit::Task.new
|
|
43
56
|
|
|
44
57
|
RuboCop::RakeTask.new(:cop) do |task|
|
|
@@ -51,7 +64,6 @@ end
|
|
|
51
64
|
|
|
52
65
|
YARD::Rake::YardocTask.new do |t|
|
|
53
66
|
t.files = ['lib/**/*.rb']
|
|
54
|
-
#t.options = ['-o doc'] # Place the documentos in doc/api
|
|
55
67
|
t.stats_options = ['--list-undoc']
|
|
56
68
|
end
|
|
57
69
|
|
data/bin/bench_check
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'tempfile'
|
|
6
|
+
|
|
7
|
+
platform = RUBY_PLATFORM
|
|
8
|
+
results_dir = File.expand_path('../test/performance/check/results', __dir__)
|
|
9
|
+
baseline_path = File.join(results_dir, "baseline-#{platform}.json")
|
|
10
|
+
threshold = (ARGV[0] || 0.80).to_f
|
|
11
|
+
|
|
12
|
+
unless File.exist?(baseline_path)
|
|
13
|
+
puts "No baseline for #{platform} at #{baseline_path}. Skipping check."
|
|
14
|
+
exit 0
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
baseline = JSON.parse(File.read(baseline_path))
|
|
18
|
+
current_json = Tempfile.new(%w[bench_current .json])
|
|
19
|
+
|
|
20
|
+
unless system("bundle exec ruby test/performance/check/runner.rb #{current_json.path}")
|
|
21
|
+
puts 'Benchmark runner failed.'
|
|
22
|
+
exit 1
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
current = JSON.parse(File.read(current_json.path))
|
|
26
|
+
|
|
27
|
+
failures = []
|
|
28
|
+
baseline['results'].each do |key, b|
|
|
29
|
+
c = current.dig('results', key)
|
|
30
|
+
next unless c
|
|
31
|
+
|
|
32
|
+
ratio = c['ips'].to_f / b['ips']
|
|
33
|
+
if ratio < threshold
|
|
34
|
+
failures << format('%<key>-15s baseline: %<b>10d ips current: %<c>10d ips ratio: %<ratio>5.1f%%',
|
|
35
|
+
key:, b: b['ips'], c: c['ips'], ratio: ratio * 100)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
puts "\n--- Benchmark Regression Check ---"
|
|
40
|
+
if failures.empty?
|
|
41
|
+
puts 'All benchmarks within 20% of baseline.'
|
|
42
|
+
else
|
|
43
|
+
puts 'Regressions detected:'
|
|
44
|
+
failures.each { |f| puts " FAIL #{f}" }
|
|
45
|
+
exit 1
|
|
46
|
+
end
|