minting 1.6.3 → 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 -113
- data/Rakefile +25 -3
- data/bin/bench_check +46 -0
- data/doc/Mint/Currency.html +1139 -0
- data/doc/Mint/CurrencyRegistry.html +511 -0
- data/doc/Mint/Money.html +3859 -0
- data/doc/Mint/RangeStepPatch.html +277 -0
- data/doc/Mint/UnknownCurrency.html +136 -0
- data/doc/Mint.html +911 -0
- data/doc/Minting.html +142 -0
- data/doc/_index.html +173 -0
- data/doc/class_list.html +54 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +206 -0
- data/doc/css/style.css +1089 -0
- data/doc/file.README.html +275 -0
- data/doc/file_list.html +59 -0
- data/doc/frames.html +22 -0
- data/doc/index.html +275 -0
- data/doc/js/app.js +801 -0
- data/doc/js/full_list.js +334 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +518 -0
- data/doc/top-level-namespace.html +151 -0
- data/lib/minting/{mint/currency → currency}/currency.rb +8 -0
- data/lib/minting/{mint/currency → currency}/currency_registry.rb +1 -1
- data/lib/minting/{mint/currency → currency}/world_currencies.rb +1 -1
- data/lib/minting/mint/aliases.rb +3 -0
- data/lib/minting/mint/dsl/numeric.rb +23 -0
- data/lib/minting/mint/dsl/range.rb +67 -0
- data/lib/minting/mint/dsl/string.rb +12 -0
- data/lib/minting/mint/{dsl.rb → dsl/top_level.rb} +3 -18
- data/lib/minting/mint/mint.rb +17 -3
- data/lib/minting/mint/{parser.rb → parser/parser.rb} +17 -29
- data/lib/minting/mint/parser/separators.rb +39 -0
- data/lib/minting/mint.rb +19 -8
- data/lib/minting/money/allocation/allocation.rb +25 -0
- data/lib/minting/money/allocation/split.rb +41 -0
- data/lib/minting/money/arithmetics/methods.rb +27 -0
- data/lib/minting/money/{arithmetics.rb → arithmetics/operators.rb} +5 -26
- data/lib/minting/money/clamp.rb +66 -0
- data/lib/minting/money/coercion.rb +18 -11
- 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} +5 -42
- data/lib/minting/money/money.rb +0 -58
- data/lib/minting/version.rb +1 -1
- data/minting.gemspec +5 -2
- metadata +42 -11
- data/lib/minting/money/allocation.rb +0 -59
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,18 +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
|
|
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)
|
|
18
10
|
|
|
19
11
|
## Quick start
|
|
20
12
|
|
|
@@ -29,16 +21,44 @@ total.to_s #=> "$21.59"
|
|
|
29
21
|
total.currency_code #=> "USD"
|
|
30
22
|
```
|
|
31
23
|
|
|
32
|
-
##
|
|
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.
|
|
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:
|
|
33
58
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- Serialization: `to_json`, `to_i`, `to_f`, `to_r`, `to_d`
|
|
38
|
-
- Allocation utilities: `split(quantity)`, `allocate([ratios])`,
|
|
39
|
-
- Utilities: `clamp(min, max)`
|
|
40
|
-
- Numeric Refinements for ergonomics: `10.dollars`, `3.euros`, `4.to_money('USD')`
|
|
41
|
-
- Currency registry with 117+ currencies and custom registration
|
|
59
|
+
```ruby
|
|
60
|
+
gem 'minting'
|
|
61
|
+
```
|
|
42
62
|
|
|
43
63
|
## Usage
|
|
44
64
|
|
|
@@ -61,16 +81,15 @@ ten == Mint.money(10, 'EUR') #=> false
|
|
|
61
81
|
ten > Mint.money(9.99, 'USD') #=> true
|
|
62
82
|
|
|
63
83
|
# Zero equality semantics
|
|
64
|
-
# Any zero amount is treated as equal, regardless of currency
|
|
65
|
-
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
|
|
66
86
|
Mint.money(0, 'USD') == 0 #=> true
|
|
67
87
|
Mint.money(0, 'USD') == 0.0 #=> true
|
|
68
|
-
Mint.money(0, 'USD') == 0r #=> true
|
|
69
88
|
|
|
70
89
|
# Non-zero numerics are not equal to Money objects
|
|
71
90
|
Mint.money(10, 'USD') == 10 #=> false
|
|
72
91
|
|
|
73
|
-
# Format (uses Kernel.format
|
|
92
|
+
# Format (uses Kernel.format syntax)
|
|
74
93
|
price = Mint.money(9.99, 'USD')
|
|
75
94
|
|
|
76
95
|
price.to_s #=> "$9.99",
|
|
@@ -136,128 +155,67 @@ price.clamp(min_price, 100) #=> [USD 75.00]
|
|
|
136
155
|
|
|
137
156
|
```
|
|
138
157
|
|
|
139
|
-
##
|
|
140
|
-
|
|
141
|
-
**Module names** — Require the `minting` gem; the public API lives under `Mint`.
|
|
142
|
-
|
|
143
|
-
**Exact amounts** — Amounts are stored as `Rational` and rounded to the currency subunit.
|
|
144
|
-
|
|
145
|
-
**Refinements** — `10.dollars` and similar helpers require `using Mint` in the current scope (see Usage above).
|
|
146
|
-
|
|
147
|
-
**Division** — `money / 5` returns new `Money`; `money / other_money` returns a numeric ratio, not money.
|
|
148
|
-
|
|
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
|
-
|
|
151
|
-
**Custom currencies** — `Mint.register_currency`, Only registered currency codes and symbolos are recoginized by the parser.
|
|
152
|
-
|
|
153
|
-
**Built-in currencies** — ISO-style codes ship in `lib/minting/data/currencies.yaml` and load when the registry is first accessed.
|
|
154
|
-
|
|
155
|
-
## Installation
|
|
156
|
-
|
|
157
|
-
Option 1: Via bundler command
|
|
158
|
-
|
|
159
|
-
```shell
|
|
160
|
-
bundle add minting
|
|
161
|
-
bundle install
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
Option 2: add the line below to your application's Gemfile:
|
|
165
|
-
|
|
166
|
-
```ruby
|
|
167
|
-
gem 'minting'
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
or, if you want latest development version from Github
|
|
158
|
+
## Parsing strings
|
|
171
159
|
|
|
172
160
|
```ruby
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
```shell
|
|
179
|
-
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]
|
|
180
165
|
```
|
|
181
166
|
|
|
182
|
-
|
|
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]`.
|
|
183
174
|
|
|
184
|
-
|
|
185
|
-
gem install minting
|
|
186
|
-
```
|
|
175
|
+
## API notes
|
|
187
176
|
|
|
188
|
-
|
|
177
|
+
**Exact amounts** — Amounts are stored as `Rational` and rounded to the currency subunit.
|
|
189
178
|
|
|
190
|
-
|
|
179
|
+
**Refinements** — `10.dollars` and similar helpers require `using Mint` in the current scope (see Usage above).
|
|
191
180
|
|
|
192
|
-
|
|
181
|
+
**Division** — `money / 5` returns new `Money`; `money / other_money` returns a numeric ratio, not money.
|
|
193
182
|
|
|
194
|
-
|
|
195
|
-
price = Mint::Money.create(10, "USD")
|
|
196
|
-
currency = Mint::Currency.new(code: "USD", symbol: "$", subunit: 2, priority: 0)
|
|
197
|
-
```
|
|
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.
|
|
198
184
|
|
|
199
|
-
|
|
185
|
+
**Registered currencies** — `Mint.register_currency`. Only registered currency codes and symbols are recognized by the parser.
|
|
200
186
|
|
|
201
|
-
|
|
187
|
+
**Built-in currencies** — 150+ ISO-4217 world currencies ship in `lib/minting/data/currencies.yaml` and load when the registry is first accessed.
|
|
202
188
|
|
|
203
|
-
|
|
189
|
+
## Optional top-level `Money` and `Currency`
|
|
204
190
|
|
|
205
|
-
|
|
191
|
+
By default, Minting keeps everything namespaced under `Mint` to coexist nicely with other gems. If you prefer shorter constants, opt in:
|
|
206
192
|
|
|
207
193
|
```ruby
|
|
208
194
|
require "minting"
|
|
209
195
|
require "minting/dsl" # opt‑in top‑level Money / Currency
|
|
210
196
|
```
|
|
211
197
|
|
|
212
|
-
|
|
198
|
+
Or at runtime:
|
|
213
199
|
|
|
214
200
|
```ruby
|
|
215
201
|
Minting.use_top_level_constants!
|
|
216
202
|
```
|
|
217
203
|
|
|
218
|
-
After
|
|
204
|
+
After opting in:
|
|
219
205
|
|
|
220
206
|
```ruby
|
|
221
207
|
price = Money.create(10, "USD") # equivalent to Mint::Money.create
|
|
222
|
-
tax = Money.money(2.50, "USD")
|
|
208
|
+
tax = Money.money(2.50, "USD")
|
|
223
209
|
cur = Currency.new(code: "EUR", symbol: "€", subunit: 2, priority: 0)
|
|
224
210
|
```
|
|
225
211
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
- **Good fit:** application code, especially in Rails apps, where `Money` reads nicely in models and views.
|
|
229
|
-
- **Not recommended:** reusable gems/libraries. In that case, stick to the namespaced API (`Mint::Money`, `Mint::Currency`) to avoid conflicts with other libraries.
|
|
230
|
-
|
|
231
|
-
## Parsing strings
|
|
232
|
-
|
|
233
|
-
```ruby
|
|
234
|
-
Mint.parse('$19.99') #=> [USD 19.99]
|
|
235
|
-
Mint.parse('19,99 €') #=> [EUR 19.99]
|
|
236
|
-
Mint.parse('1.234,56', 'EUR') #=> [EUR 1234.56]
|
|
237
|
-
Mint.parse('USD 1,234.56') #=> [USD 1234.56]
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
- Pass a currency code when the string has no symbol or code.
|
|
241
|
-
- 1,234 means 1.234, not 1234, because one comma is treated as decimal.
|
|
242
|
-
- 1,234.00 is unambiguous thousands-plus-decimal.
|
|
243
|
-
- accounting negatives like ($1.23) are unsupported.
|
|
244
|
-
- 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.
|
|
245
214
|
|
|
246
215
|
## Roadmap
|
|
247
216
|
|
|
248
|
-
- Improve formatting features
|
|
249
217
|
- Localization (I18n-aware formatting)
|
|
250
|
-
-
|
|
251
|
-
|
|
252
|
-
## Contributing
|
|
253
|
-
|
|
254
|
-
Bug reports and pull requests are welcome on GitHub at <https://github.com/gferraz/minting>.
|
|
255
|
-
|
|
256
|
-
1. Fork and create a feature branch
|
|
257
|
-
2. Run the test suite: `rake`
|
|
258
|
-
3. Run performance suites as needed: `rake bench:performance`
|
|
259
|
-
4. Open a PR with a clear description and benchmarks if relevant
|
|
260
|
-
|
|
218
|
+
- Exchange-rate conversion infrastructure
|
|
261
219
|
|
|
262
220
|
## License
|
|
263
221
|
|
data/Rakefile
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
require 'bundler/audit/task'
|
|
1
2
|
require 'bundler/gem_tasks'
|
|
2
|
-
require 'rubocop/rake_task'
|
|
3
3
|
require 'rake/testtask'
|
|
4
|
+
require 'rubocop/rake_task'
|
|
5
|
+
require 'rubycritic/rake_task'
|
|
4
6
|
require 'yard'
|
|
5
7
|
|
|
6
8
|
CLOBBER.include %w[doc/css doc/js doc/Mint doc/*.html tmp .yardoc]
|
|
@@ -37,11 +39,31 @@ Rake::TestTask.new('bench:competitive') do |t|
|
|
|
37
39
|
t.pattern = 'test/performance/competitive/**/*_benchmark.rb'
|
|
38
40
|
end
|
|
39
41
|
|
|
40
|
-
|
|
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
|
+
|
|
55
|
+
Bundler::Audit::Task.new
|
|
56
|
+
|
|
57
|
+
RuboCop::RakeTask.new(:cop) do |task|
|
|
58
|
+
task.patterns = ['lib']
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
RubyCritic::RakeTask.new do |task|
|
|
62
|
+
task.name = 'critic'
|
|
63
|
+
end
|
|
41
64
|
|
|
42
65
|
YARD::Rake::YardocTask.new do |t|
|
|
43
66
|
t.files = ['lib/**/*.rb']
|
|
44
|
-
t.options = ['-o doc/api'] # Place the documentos in doc/api
|
|
45
67
|
t.stats_options = ['--list-undoc']
|
|
46
68
|
end
|
|
47
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
|