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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +71 -118
  3. data/Rakefile +13 -1
  4. data/bin/bench_check +46 -0
  5. data/doc/Mint/Currency.html +178 -32
  6. data/doc/Mint/CurrencyRegistry.html +7 -7
  7. data/doc/Mint/Money.html +128 -125
  8. data/doc/Mint/RangeStepPatch.html +277 -0
  9. data/doc/Mint/UnknownCurrency.html +2 -2
  10. data/doc/Mint.html +47 -22
  11. data/doc/Minting.html +3 -3
  12. data/doc/_index.html +17 -2
  13. data/doc/class_list.html +1 -1
  14. data/doc/file.README.html +86 -89
  15. data/doc/index.html +86 -89
  16. data/doc/method_list.html +29 -21
  17. data/doc/top-level-namespace.html +13 -5
  18. data/lib/minting/{mint/currency → currency}/currency.rb +8 -0
  19. data/lib/minting/{mint/currency → currency}/world_currencies.rb +1 -1
  20. data/lib/minting/mint/aliases.rb +3 -0
  21. data/lib/minting/mint/dsl/{refinements.rb → numeric.rb} +6 -5
  22. data/lib/minting/mint/dsl/range.rb +31 -18
  23. data/lib/minting/mint/dsl/string.rb +12 -0
  24. data/lib/minting/mint/dsl/top_level.rb +3 -0
  25. data/lib/minting/mint/mint.rb +16 -2
  26. data/lib/minting/mint/parser/parser.rb +66 -0
  27. data/lib/minting/mint/parser/separators.rb +39 -0
  28. data/lib/minting/mint.rb +17 -8
  29. data/lib/minting/money/allocation/allocation.rb +25 -0
  30. data/lib/minting/money/{allocation.rb → allocation/split.rb} +1 -19
  31. data/lib/minting/money/arithmetics/methods.rb +27 -0
  32. data/lib/minting/money/{arithmetics.rb → arithmetics/operators.rb} +0 -21
  33. data/lib/minting/money/clamp.rb +66 -0
  34. data/lib/minting/money/coercion.rb +10 -0
  35. data/lib/minting/money/comparable.rb +6 -0
  36. data/lib/minting/money/constructors.rb +13 -3
  37. data/lib/minting/money/format/formatting.rb +44 -0
  38. data/lib/minting/money/{formatting.rb → format/to_s.rb} +0 -32
  39. data/lib/minting/money/money.rb +0 -58
  40. data/lib/minting/version.rb +1 -1
  41. metadata +19 -14
  42. data/lib/minting/mint/parser.rb +0 -85
  43. /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: 005a740f595e3f1b579b2108feb4cd742706588a75b1df41179775d311fb002e
4
- data.tar.gz: b1c2d55e378cca46adde3e6edd7253bb0914707768c19fd00e568288921540ff
3
+ metadata.gz: 849f4b46ee6f731be672f7e6adb274174233294d7be293c8c875eaa0ace5b744
4
+ data.tar.gz: b3f3a822c55e7fd9fdddcfcf7be59069b66ea441d73ab61e2e202563173a8675
5
5
  SHA512:
6
- metadata.gz: 48f40d6cceeb16d9ffc357297dae0961ba0473f4d9ed1271ac6e144a0a468b42dd8a553b661485317eba56d88665b08b1302c4620af1a58bb6f07ab9aa996da3
7
- data.tar.gz: bc77e69b3e39d3bde0ebb47e60bb1225d0a9062e2a1f201924c3e32924ad65711c28ef492ac4c5d63b06bb376510d07e7d2d69a3efbc17805e89d703c3c15812
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
  [![Gem Version](https://badge.fury.io/rb/minting.svg)](https://badge.fury.io/rb/minting)
6
-
7
- ## Why Minting?
8
-
9
- **Tired of floating-point errors in financial calculations?** Minting uses Rational numbers for perfect precision.
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
+ [![CI](https://github.com/gferraz/minting/actions/workflows/ci.yml/badge.svg)](https://github.com/gferraz/minting/actions/workflows/ci.yml)
7
+ [![Test Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen)](https://github.com/gferraz/minting)
8
+ [![RubyCritic](https://img.shields.io/badge/RubyCritic-94gem /100-brightgreen)](https://github.com/gferraz/minting)
9
+ [![Documentation](https://img.shields.io/badge/docs-rubydoc.info-blue)](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
- ## Features
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
- - Arithmetic: `+ - * /`, unary minus, `abs`
40
- - Comparisons: `==`, `<=>`, `zero?`, `nonzero?`, `positive?`, `negative?`
41
- - Formatting: `to_s` with custom formats, thousand delimiters and decimal separators
42
- - Serialization: `to_json`, `to_i`, `to_f`, `to_r`, `to_d`
43
- - Allocation utilities: `split(quantity)`, `allocate([ratios])`,
44
- - Utilities: `clamp(min, max)`
45
- - Numeric Refinements for ergonomics: `10.dollars`, `3.euros`, `4.to_money('USD')`
46
- - Currency registry with 117+ currencies and custom registration
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') #=> true
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 internally)
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
- ## API notes
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
- gem 'minting', git: 'https://github.com/gferraz/minting.git'
179
- ```
180
-
181
- and execute:
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
- Option 3: Install it yourself with:
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
- ```shell
190
- gem install minting
191
- ```
175
+ ## API notes
192
176
 
193
- ## Configuration
177
+ **Exact amounts** — Amounts are stored as `Rational` and rounded to the currency subunit.
194
178
 
195
- ### Optional top‑level `Money` and `Currency`
179
+ **Refinements** `10.dollars` and similar helpers require `using Mint` in the current scope (see Usage above).
196
180
 
197
- By default, Minting keeps everything namespaced under `Mint`:
181
+ **Division** `money / 5` returns new `Money`; `money / other_money` returns a numeric ratio, not money.
198
182
 
199
- ```ruby
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
- To avoid polluting the global namespace (and to coexist nicely with other gems), **Minting dont automatically defines `Money` or `Currency` at the top level automatically**.
185
+ **Registered currencies** `Mint.register_currency`. Only registered currency codes and symbols are recognized by the parser.
205
186
 
206
- If you prefer the shorter `Money` / `Currency` constants in your application code, you can opt in explicitly.
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
- There are two ways to enable shorter constants:
189
+ ## Optional top-level `Money` and `Currency`
209
190
 
210
- 1. Require dsl in your app
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
- 2. Call a configuration method
198
+ Or at runtime:
218
199
 
219
200
  ```ruby
220
201
  Minting.use_top_level_constants!
221
202
  ```
222
203
 
223
- After this, you can use:
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") # via Mint.money, still available
208
+ tax = Money.money(2.50, "USD")
228
209
  cur = Currency.new(code: "EUR", symbol: "€", subunit: 2, priority: 0)
229
210
  ```
230
211
 
231
- #### When to use this
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
- - Basic exchange-rate conversions - infrastructure only, not integrations yet
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