minting 1.7.0 → 1.7.2
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 +71 -118
- data/Rakefile +13 -1
- data/bin/bench_check +46 -0
- data/doc/Mint/Currency.html +178 -32
- data/doc/Mint/CurrencyRegistry.html +7 -7
- data/doc/Mint/Money.html +128 -125
- data/doc/Mint/RangeStepPatch.html +277 -0
- data/doc/Mint/UnknownCurrency.html +2 -2
- data/doc/Mint.html +47 -22
- data/doc/Minting.html +3 -3
- data/doc/_index.html +17 -2
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +86 -89
- data/doc/index.html +86 -89
- data/doc/method_list.html +29 -21
- data/doc/top-level-namespace.html +13 -5
- data/lib/minting/{mint/currency → currency}/currency.rb +8 -0
- data/lib/minting/{mint/currency → currency}/world_currencies.rb +1 -1
- 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/mint.rb +16 -2
- data/lib/minting/mint/parser/parser.rb +66 -0
- data/lib/minting/mint/parser/separators.rb +39 -0
- data/lib/minting/mint.rb +17 -8
- 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 +13 -3
- data/lib/minting/money/format/formatting.rb +44 -0
- data/lib/minting/money/{formatting.rb → format/to_s.rb} +0 -32
- data/lib/minting/money/money.rb +0 -58
- data/lib/minting/version.rb +1 -1
- metadata +19 -14
- data/lib/minting/mint/parser.rb +0 -85
- /data/lib/minting/{mint/currency → currency}/currency_registry.rb +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 849f4b46ee6f731be672f7e6adb274174233294d7be293c8c875eaa0ace5b744
|
|
4
|
+
data.tar.gz: b3f3a822c55e7fd9fdddcfcf7be59069b66ea441d73ab61e2e202563173a8675
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5f4d42d03f31c941fcd591e27c463341be2c31498c7d854c3b98f85445a8ca55dfa692c3124f187000c83b97b0abc21e6e7276ea5bb3cd1090e82619e6fcc451
|
|
7
|
+
data.tar.gz: '0854273f42d05dee2b2d91830110d9f59074821abd81c76a14860a0969c4bd2cd48c56164c5fd2b65fcdfe32f537860958b108cd53eadfc53a2c33215f35c5cd'
|
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 |
|
|
38
33
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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.
|
|
45
|
+
|
|
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,67 @@ 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:
|
|
170
|
-
|
|
171
|
-
```ruby
|
|
172
|
-
gem 'minting'
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
or, if you want latest development version from Github
|
|
158
|
+
## Parsing strings
|
|
176
159
|
|
|
177
160
|
```ruby
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
```shell
|
|
184
|
-
bundle install
|
|
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]
|
|
185
165
|
```
|
|
186
166
|
|
|
187
|
-
|
|
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]`.
|
|
188
174
|
|
|
189
|
-
|
|
190
|
-
gem install minting
|
|
191
|
-
```
|
|
175
|
+
## API notes
|
|
192
176
|
|
|
193
|
-
|
|
177
|
+
**Exact amounts** — Amounts are stored as `Rational` and rounded to the currency subunit.
|
|
194
178
|
|
|
195
|
-
|
|
179
|
+
**Refinements** — `10.dollars` and similar helpers require `using Mint` in the current scope (see Usage above).
|
|
196
180
|
|
|
197
|
-
|
|
181
|
+
**Division** — `money / 5` returns new `Money`; `money / other_money` returns a numeric ratio, not money.
|
|
198
182
|
|
|
199
|
-
|
|
200
|
-
price = Mint::Money.create(10, "USD")
|
|
201
|
-
currency = Mint::Currency.new(code: "USD", symbol: "$", subunit: 2, priority: 0)
|
|
202
|
-
```
|
|
183
|
+
**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.
|
|
203
184
|
|
|
204
|
-
|
|
185
|
+
**Registered currencies** — `Mint.register_currency`. Only registered currency codes and symbols are recognized by the parser.
|
|
205
186
|
|
|
206
|
-
|
|
187
|
+
**Built-in currencies** — 150+ ISO-4217 world currencies ship in `lib/minting/data/currencies.yaml` and load when the registry is first accessed.
|
|
207
188
|
|
|
208
|
-
|
|
189
|
+
## Optional top-level `Money` and `Currency`
|
|
209
190
|
|
|
210
|
-
|
|
191
|
+
By default, Minting keeps everything namespaced under `Mint` to coexist nicely with other gems. If you prefer shorter constants, opt in:
|
|
211
192
|
|
|
212
193
|
```ruby
|
|
213
194
|
require "minting"
|
|
214
195
|
require "minting/dsl" # opt‑in top‑level Money / Currency
|
|
215
196
|
```
|
|
216
197
|
|
|
217
|
-
|
|
198
|
+
Or at runtime:
|
|
218
199
|
|
|
219
200
|
```ruby
|
|
220
201
|
Minting.use_top_level_constants!
|
|
221
202
|
```
|
|
222
203
|
|
|
223
|
-
After
|
|
204
|
+
After opting in:
|
|
224
205
|
|
|
225
206
|
```ruby
|
|
226
207
|
price = Money.create(10, "USD") # equivalent to Mint::Money.create
|
|
227
|
-
tax = Money.money(2.50, "USD")
|
|
208
|
+
tax = Money.money(2.50, "USD")
|
|
228
209
|
cur = Currency.new(code: "EUR", symbol: "€", subunit: 2, priority: 0)
|
|
229
210
|
```
|
|
230
211
|
|
|
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.
|
|
212
|
+
**Good fit:** Application code, especially Rails apps.
|
|
213
|
+
**Not recommended:** Reusable gems/libraries — stick to `Mint::Money` to avoid conflicts.
|
|
250
214
|
|
|
251
215
|
## Roadmap
|
|
252
216
|
|
|
253
|
-
- Add support to configure thousand and decimal separators in parse (evaluating)
|
|
254
217
|
- 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
|
-
|
|
218
|
+
- Exchange-rate conversion infrastructure
|
|
266
219
|
|
|
267
220
|
## License
|
|
268
221
|
|
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
|