minting 1.9.4 → 1.9.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e784f15a78d9890f02c3b44c95ab975bd8a12647c27d87531059603fa0e5cab5
4
- data.tar.gz: 83e28899e8e8cedcd98a93fd2b787470485d8f440ba82f63138499dc1ba33c20
3
+ metadata.gz: 51f0a76084db98e53f29af4f82f1284f749c9691727a7a84a7f8cc758d1a9f67
4
+ data.tar.gz: f24a130df454ee80f923aa42fd995e4b3eaf2f4f16d1a6f3a9e84548c08a4988
5
5
  SHA512:
6
- metadata.gz: dc823a1170e16ae3ef0751d2e35200b681e89c42d8480d7dfb6117c9f7b6dbe8b6d5cec2fa47a57755e0f52eeb0324a5dd560a359729307aa19186625490f6fc
7
- data.tar.gz: a281aae01fc2e2472c0697ea02f6d68f21c64ecb9acb9c8704a70bba35a8e7dafa0523f8d6696c93c818c67488c81c6c2b3d117cc48a5f0720eeaaa31607dac1
6
+ metadata.gz: 42ed5e90ba4a936426344cfd1f8aad69755038704c53b67d8f714ced040661e2f37829aa44d2d67f12575596656b7d9bd5b80058995b57875aaa46b2cf6dd291
7
+ data.tar.gz: a6490a3356b949df6889e72ee3fc885a4e10af8d14e09591d7bd615ea468371c30b1695b73e996cb91526ab824a610e2667797eb24e94144ec46ddd22dc42395
data/README.md CHANGED
@@ -57,9 +57,6 @@ require 'minting'
57
57
  # Create money
58
58
  ten = Mint.money(10, 'USD') #=> [USD 10.00]
59
59
 
60
- # Create money using Numeric refinements
61
- using Mint
62
-
63
60
  1.dollar == Mint.money(1, 'USD') #=> true
64
61
  ten = 10.dollars #=> [USD 10.00]
65
62
  4.to_money('USD') #=> [USD 4.00]
@@ -83,43 +80,43 @@ price = Mint.money(9.99, 'USD')
83
80
  loss = Mint.money(-1234.56, 'USD')
84
81
 
85
82
  # Built-in named presets
86
- loss.to_formatted_s(:accounting) #=> "($1,234.56)"
87
- Mint.money(1234.56, 'EUR').to_formatted_s(:european) #=> "1.234,56 €"
88
- price.to_formatted_s(:amount) #=> "9.99"
89
- price.to_formatted_s(:currency) #=> "USD 9.99"
83
+ loss.format(:accounting) #=> "($1,234.56)"
84
+ Mint.money(1234.56, 'EUR').format(:european) #=> "1.234,56 €"
85
+ price.format(:amount) #=> "9.99"
86
+ price.format(:currency) #=> "USD 9.99"
90
87
 
91
88
  # Presets can be overridden with explicit kwargs
92
- Mint.money(1234.56, 'EUR').to_formatted_s(:european, format: '%<amount>f %<currency>s')
89
+ Mint.money(1234.56, 'EUR').format(:european, format: '%<amount>f %<currency>s')
93
90
  #=> "1.234,56 EUR"
94
91
 
95
92
  # Or use direct format strings
96
- price.to_formatted_s #=> "$9.99",
97
- price.to_formatted_s(format: '%<amount>d') #=> "9",
98
- price.to_formatted_s(format: '%<symbol>s%<amount>f') #=> "$9.99",
99
- price.to_formatted_s(format: '%<symbol>s%<amount>+f') #=> "$+9.99",
100
- (-price).to_formatted_s(format: '%<amount>f') #=> "-9.99",
93
+ price.format #=> "$9.99",
94
+ price.format(format: '%<amount>d') #=> "9",
95
+ price.format(format: '%<symbol>s%<amount>f') #=> "$9.99",
96
+ price.format(format: '%<symbol>s%<amount>+f') #=> "$+9.99",
97
+ (-price).format(format: '%<amount>f') #=> "-9.99",
101
98
 
102
99
  # Format with padding
103
100
  price_in_euros = Mint.money(12.34, 'EUR')
104
101
 
105
- price.to_formatted_s(format: '--%<amount>7d') #=> "-- 9"
106
- price.to_formatted_s(format: ' %<amount>10f %<currency>s') #=> " 9.99 USD"
107
- (-price).to_formatted_s(format: ' %<amount>10f') #=> " -9.99"
102
+ price.format(format: '--%<amount>7d') #=> "-- 9"
103
+ price.format(format: ' %<amount>10f %<currency>s') #=> " 9.99 USD"
104
+ (-price).format(format: ' %<amount>10f') #=> " -9.99"
108
105
 
109
- price_in_euros.to_formatted_s(format: '%<symbol>2s%<amount>+10f') #=> " € +12.34"
106
+ price_in_euros.format(format: '%<symbol>2s%<amount>+10f') #=> " € +12.34"
110
107
 
111
108
  # Integral & fractional parts
112
- price.to_formatted_s(format: '%<integral>d %<fractional>d/100') #=> "9 99/100"
113
- Mint.money(0.99, 'USD').to_fs(format: '%<integral>d dollars and %<fractional>02d cents')
109
+ price.format(format: '%<integral>d %<fractional>d/100') #=> "9 99/100"
110
+ Mint.money(0.99, 'USD').format(format: '%<integral>d dollars and %<fractional>02d cents')
114
111
  #=> "0 dollars and 99 cents"
115
112
 
116
113
  # Per-sign Hash format (e.g. accounting parentheses for losses)
117
114
  loss = Mint.money(-1234.56, 'USD')
118
- loss.to_formatted_s(format: { negative: '(%<symbol>s%<amount>f)' }) #=> "($1,234.56)"
119
- Mint.money(0, 'BRL').to_formatted_s(format: { zero: '--' }) #=> "--"
115
+ loss.format(format: { negative: '(%<symbol>s%<amount>f)' }) #=> "($1,234.56)"
116
+ Mint.money(0, 'BRL').format(format: { zero: '--' }) #=> "--"
120
117
  # All three keys at once:
121
118
  fmt = { positive: '%<symbol>s%<amount>f', negative: '(%<symbol>s%<amount>f)', zero: '--' }
122
- Mint.money(1234.56, 'USD').to_formatted_s(format: fmt) #=> "$1,234.56"
119
+ Mint.money(1234.56, 'USD').format(format: fmt) #=> "$1,234.56"
123
120
 
124
121
  # Json serialization
125
122
 
@@ -213,8 +210,6 @@ Modes: `:half_up` (default), `:half_down`, `:floor`, `:ceil`, `:truncate`, `:dow
213
210
 
214
211
  > **Performance note:** Rounding-mode support is not loaded by default — `require 'minting'` uses the fastest possible rounding (equivalent to `:half_up`) with zero dispatch overhead. The first call to `Mint.with_rounding` loads the rounding module and patches `Currency#normalize_amount`, adding ~10–35 ns per money creation or mutation. If your application never uses custom rounding modes (the common case), there is **no performance cost**.
215
212
 
216
- **Refinements** — `10.dollars` and similar helpers require `using Mint` in the current scope (see Usage above).
217
-
218
213
  **Division** — `money / 5` returns new `Money`; `money / other_money` returns a numeric ratio, not money.
219
214
 
220
215
  **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.
@@ -88,9 +88,11 @@ module Mint
88
88
  #
89
89
  # @param object [String, Currency, Money, nil] a currency code, object, or +nil+
90
90
  # @return [Currency] the resolved Currency
91
- # @raise [ArgumentError] if +object+ cannot be resolved into a registered currency
91
+ # @raise [Mint::UnknownCurrency] if +object+ cannot be resolved into a
92
+ # registered currency. +Mint::UnknownCurrency+ inherits from +ArgumentError+,
93
+ # so existing +rescue ArgumentError+ handlers continue to work.
92
94
  def Currency.resolve!(object)
93
- resolve(object) or raise ArgumentError, "Could not resolve (#{object}) into a currency"
95
+ resolve(object) or raise Mint::UnknownCurrency, "Could not resolve (#{object}) into a currency"
94
96
  end
95
97
 
96
98
  # Looks up a registered currency by its alpha code.
@@ -114,6 +116,6 @@ module Mint
114
116
  #
115
117
  # @param currency [String, Currency] a currency code or object
116
118
  # @return [Money] a frozen zero-Money
117
- # @raise [ArgumentError] if the currency can't be resolved
119
+ # @raise [Mint::UnknownCurrency] if the currency can't be resolved
118
120
  def Currency.zero(currency) = Registry.zero_for(Currency.resolve!(currency))
119
121
  end
@@ -1,23 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Mint Numeric refinements
4
- module Mint
5
- refine Numeric do
6
- # @return [Money] self interpreted as BRL
7
- def reais = Mint::Money.from(self, 'BRL')
3
+ # Core extension: adds money-conversion helpers to Numeric.
4
+ class Numeric
5
+ # @return [Money] self interpreted as BRL
6
+ def reais = Mint::Money.from(self, 'BRL')
8
7
 
9
- # @return [Money] self interpreted as USD
10
- def dollars = Mint::Money.from(self, 'USD')
8
+ # @return [Money] self interpreted as USD
9
+ def dollars = Mint::Money.from(self, 'USD')
11
10
 
12
- # @return [Money] self interpreted as EUR
13
- def euros = Mint::Money.from(self, 'EUR')
11
+ # @return [Money] self interpreted as EUR
12
+ def euros = Mint::Money.from(self, 'EUR')
14
13
 
15
- # @param currency [String, Symbol, Currency] target currency
16
- # @return [Money] self interpreted as the given currency
17
- def to_money(currency) = Mint::Money.from(self, currency)
14
+ # @param currency [String, Symbol, Currency] target currency
15
+ # @return [Money] self interpreted as the given currency
16
+ def to_money(currency) = Mint::Money.from(self, currency)
18
17
 
19
- alias_method :dollar, :dollars
20
- alias_method :euro, :euros
21
- alias_method :mint, :to_money
22
- end
18
+ alias dollar dollars
19
+ alias euro euros
20
+ alias mint to_money
23
21
  end
@@ -1,12 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Mint String refinement
4
- module Mint
5
- refine String do
6
- # Parses self as a numeric string and creates a Money in the given currency.
7
- #
8
- # @param currency [String, Symbol, Currency] target currency
9
- # @return [Money]
10
- def to_money(currency) = Mint::Money.from(to_r, currency)
11
- end
3
+ # Core extension: adds money-parsing helper to String.
4
+ class String
5
+ # Parses self as a numeric string and creates a Money in the given currency.
6
+ #
7
+ # @param currency [String, Symbol, Currency] target currency
8
+ # @return [Money]
9
+ def to_money(currency = nil) = Mint::Money.parse(self, currency)
12
10
  end
@@ -2,16 +2,24 @@
2
2
 
3
3
  # Mint currency registration and factory (public API)
4
4
  module Mint
5
- # Unknown currency excpetion
6
- class UnknownCurrency < StandardError
5
+ # Raised when a currency cannot be resolved from a code or object.
6
+ #
7
+ # Inherits from +ArgumentError+ so existing +rescue ArgumentError+ handlers
8
+ # continue to work; rescue +Mint::UnknownCurrency+ for the specific case.
9
+ class UnknownCurrency < ArgumentError
7
10
  end
8
11
 
9
12
  # Creates a new {Money} instance with the given amount and currency code.
10
13
  #
11
14
  # @param amount [Numeric] the financial value
12
- # @param currency_code [Currency, String] Currency code
15
+ # @param currency_code [String, Currency, Money, nil] Currency code, object,
16
+ # Money whose currency to reuse, or +nil+. Passed through
17
+ # {Mint::Currency.resolve!} so all accepted types resolve to a registered
18
+ # currency.
13
19
  # @return [Money] the instantiated Money object
14
- # @raise [ArgumentError] if the currency code is not registered
20
+ # @raise [ArgumentError] if the amount is not a Numeric
21
+ # @raise [Mint::UnknownCurrency] if the currency code is not registered.
22
+ # +Mint::UnknownCurrency+ inherits from +ArgumentError+.
15
23
  def self.money(amount, currency_code) = Money.from(amount, currency_code)
16
24
 
17
25
  # @return [Hash{String => Currency}] the frozen world-currencies hash
@@ -7,7 +7,8 @@ module Mint
7
7
  # @param amount [Numeric] The monetary amount
8
8
  # @param currency [Currency, String] The currency code or currency object
9
9
  # @return [Money] the new Money instance
10
- # @raise [ArgumentError] If amount is not numeric or currency is invalid
10
+ # @raise [ArgumentError] If amount is not numeric
11
+ # @raise [Mint::UnknownCurrency] If currency cannot be resolved
11
12
  # @example
12
13
  # Money.from(10, 'USD') #=> [USD 10.00]
13
14
  def self.from(amount, currency)
@@ -61,16 +62,9 @@ module Mint
61
62
  #
62
63
  # @param currency [String, Currency] a currency code or object
63
64
  # @return [Money] a frozen zero-Money
64
- # @raise [ArgumentError] if the currency can't be resolved
65
+ # @raise [Mint::UnknownCurrency] if the currency can't be resolved
65
66
  def self.zero(currency) = Currency.resolve!(currency).zero
66
67
 
67
- # Backwards-compatible alias for previous API
68
- # TODO: deprecate in a future major release
69
- def self.create(amount, currency)
70
- warn 'Money.create is now deprecated. Use Money.from'
71
- from(amount, currency)
72
- end
73
-
74
68
  # Builds a Money from a subunit (smallest-unit) Integer amount.
75
69
  # This is the inverse of {#subunits}: for USD, the subunit is
76
70
  # 1 cent; for JPY it is 1 yen; for IQD it is 1 dinar (subunit 3).
@@ -79,8 +73,8 @@ module Mint
79
73
  # smallest unit (e.g. cents). Must be an Integer to preserve exactness.
80
74
  # @param currency [String, Symbol, Currency] the currency identifier
81
75
  # @return [Money] the resulting Money instance
82
- # @raise [ArgumentError] if +subunits+ is not an Integer or +currency+
83
- # is not registered
76
+ # @raise [ArgumentError] if +subunits+ is not an Integer
77
+ # @raise [Mint::UnknownCurrency] if +currency+ is not registered
84
78
  #
85
79
  # @example USD cents
86
80
  # Money.from_subunits(123_456, 'USD') #=> [USD 1234.56]
@@ -26,7 +26,7 @@ module Mint
26
26
  # @return [String] HTML5 `<data>` representation
27
27
  def to_html(format = DEFAULT_FORMAT)
28
28
  title = Kernel.format("#{currency_code} %0.#{currency.subunit}f", amount)
29
- body = to_formatted_s(format: format)
29
+ body = format(format: format)
30
30
  %(<data class='money' title='#{title}'>#{ERB::Util.html_escape(body)}</data>)
31
31
  end
32
32
 
@@ -33,10 +33,10 @@ module Mint
33
33
  # @private
34
34
  def validate_separators!(decimal:, thousand:)
35
35
  case decimal
36
- when '' then raise ArgumentError, 'decimal must be a non-empty'
37
- when nil # :noop
38
- when String
39
- raise ArgumentError, "decimal and thousand cannot be identical: #{decimal.inspect}" if decimal == thousand
36
+ when nil # :noop
37
+ when '' then raise ArgumentError, 'decimal must be a non-empty'
38
+ when thousand then raise ArgumentError, "decimal and thousand cannot be identical: #{decimal.inspect}"
39
+ when String # :noop
40
40
  else raise ArgumentError, "decimal must be a String, false, or nil, got #{decimal.inspect}"
41
41
  end
42
42
 
@@ -62,13 +62,7 @@ module Mint
62
62
  subunit = currency.subunit
63
63
  resolved_format, adjusted_amount = resolve_format(format)
64
64
 
65
- # Inject the currency's subunit precision into %<amount>f specifiers
66
- # e.g. '%<amount>f' becomes '%<amount>.2f' for USD
67
- resolved_format = resolved_format.gsub(/%<amount>(\s*\+?\d*)f/, "%<amount>\\1.#{subunit}f")
68
-
69
- # Zero-subunit currencies (e.g. JPY) have no fractional part —
70
- # strip %<fractional>d specifiers entirely since there's no valid integer for "nothing"
71
- resolved_format.gsub!(/%<fractional>[^%]*?d/, '') if subunit.zero?
65
+ resolved_format = inject_subunit_precision(resolved_format, subunit)
72
66
 
73
67
  result = Kernel.format(resolved_format, {
74
68
  amount: adjusted_amount,
@@ -86,5 +80,18 @@ module Mint
86
80
  # Apply thousands only to the integral portion, using the decimal as boundary
87
81
  apply_thousand_separator(result, decimal:, thousand:)
88
82
  end
83
+
84
+ # Injects the currency's subunit precision into %<amount>f specifiers
85
+ # (e.g. '%<amount>f' → '%<amount>.2f' for USD). For zero-subunit
86
+ # currencies (e.g. JPY), strips %<fractional>d specifiers.
87
+ # @private
88
+ def inject_subunit_precision(template, subunit)
89
+ cache ||= {}
90
+ cache[[template, subunit]] ||= begin
91
+ result = template.gsub(/%<amount>(\s*\+?\d*)f/, "%<amount>\\1.#{subunit}f")
92
+ result.gsub!(/%<fractional>[^%]*?d/, '') if subunit.zero?
93
+ result.freeze
94
+ end
95
+ end
89
96
  end
90
97
  end
@@ -22,7 +22,7 @@ module Mint
22
22
  # holding a format string. A Hash is convenient for sign-aware formats
23
23
  # such as accounting parentheses:
24
24
  #
25
- # money.to_formatted_s(format: { negative: '(%<symbol>s%<amount>f)' })
25
+ # money.format(format: { negative: '(%<symbol>s%<amount>f)' })
26
26
  #
27
27
  # Missing keys fall back to the module default, so a Hash with only
28
28
  # :negative will still format positives sensibly. The valid keys are
@@ -41,42 +41,42 @@ module Mint
41
41
  #
42
42
  # @example Basic formatting
43
43
  # money = Mint.money(1234.56, 'USD')
44
- # money.to_formatted_s #=> "$1,234.56"
45
- # money.to_formatted_s(thousand: '.', decimal: ',') #=> "$1.234,56"
46
- # money.to_formatted_s(decimal: ',', thousand: '') #=> "$1234,56"
44
+ # money.format #=> "$1,234.56"
45
+ # money.format(thousand: '.', decimal: ',') #=> "$1.234,56"
46
+ # money.format(decimal: ',', thousand: '') #=> "$1234,56"
47
47
  #
48
48
  # @example Preset formats
49
49
  # loss = Mint.money(-1234.56, 'USD')
50
- # loss.to_formatted_s(:accounting) #=> "($1,234.56)"
51
- # money.to_formatted_s(:european) #=> "1.234,56 €"
52
- # money.to_formatted_s(:amount) #=> "1234.56"
53
- # money.to_formatted_s(:currency) #=> "USD 1234.56"
50
+ # loss.format(:accounting) #=> "($1,234.56)"
51
+ # money.format(:european) #=> "1.234,56 €"
52
+ # money.format(:amount) #=> "1234.56"
53
+ # money.format(:currency) #=> "USD 1234.56"
54
54
  #
55
55
  # @example Custom formats
56
- # money.to_formatted_s(format: '%<amount>f') #=> "1234.56"
57
- # money.to_formatted_s(format: '%<currency>s %<amount>f') #=> "USD 1234.56"
58
- # money.to_formatted_s(format: '%<amount>f %<symbol>s') #=> "1234.56 $"
59
- # money.to_formatted_s(format: '%<symbol>s%<amount>+f') #=> "$+1234.56"
56
+ # money.format(format: '%<amount>f') #=> "1234.56"
57
+ # money.format(format: '%<currency>s %<amount>f') #=> "USD 1234.56"
58
+ # money.format(format: '%<amount>f %<symbol>s') #=> "1234.56 $"
59
+ # money.format(format: '%<symbol>s%<amount>+f') #=> "$+1234.56"
60
60
  #
61
61
  # @example Integral & fractional parts
62
- # money.to_formatted_s(format: '%<integral>d.%<fractional>02d') #=> "1234.56"
62
+ # money.format(format: '%<integral>d.%<fractional>02d') #=> "1234.56"
63
63
  # price = Mint.money(0.99, 'USD')
64
- # price.to_formatted_s(format: '%<integral>d dollars and %<fractional>02d cents')
64
+ # price.format(format: '%<integral>d dollars and %<fractional>02d cents')
65
65
  # #=> "0 dollars and 99 cents"
66
66
  #
67
67
  # @example Per-sign Hash format (accounting parentheses)
68
68
  # loss = Mint.money(-1234.56, 'USD')
69
- # loss.to_formatted_s(format: { negative: '(%<symbol>s%<amount>f)' }) #=> "($1,234.56)"
70
- # Mint.money(0, 'BRL').to_formatted_s(format: { zero: '--' }) #=> "--"
69
+ # loss.format(format: { negative: '(%<symbol>s%<amount>f)' }) #=> "($1,234.56)"
70
+ # Mint.money(0, 'BRL').format(format: { zero: '--' }) #=> "--"
71
71
  #
72
72
  # @example Padding and alignment
73
- # money.to_formatted_s(format: '%<amount>10.2f') #=> " 1234.56"
74
- # money.to_formatted_s(format: '%<symbol>s%<amount>010.2f') #=> "$0001234.56"
73
+ # money.format(format: '%<amount>10.2f') #=> " 1234.56"
74
+ # money.format(format: '%<symbol>s%<amount>010.2f') #=> "$0001234.56"
75
75
  #
76
76
  # @example Locale-aware formatting (with Mint.locale_backend set)
77
- # money.to_formatted_s # decimal and thousand come from locale_backend
77
+ # money.format # decimal and thousand come from locale_backend
78
78
  #
79
- def to_formatted_s(preset = nil, format: nil, decimal: nil, thousand: nil, width: nil)
79
+ def format(preset = nil, format: nil, decimal: nil, thousand: nil, width: nil)
80
80
  if preset
81
81
  config = PRESETS.fetch(preset) { raise ArgumentError, "Unknown format preset: #{preset.inspect}" }
82
82
  format ||= config[:format]
@@ -101,8 +101,8 @@ module Mint
101
101
  width ? formatted.rjust(width) : formatted
102
102
  end
103
103
 
104
- def to_s = to_formatted_s
104
+ def to_s = format
105
105
 
106
- alias to_fs to_formatted_s
106
+ alias to_fs :format
107
107
  end
108
108
  end
@@ -3,5 +3,5 @@
3
3
  # Root namespace for the Minting library.
4
4
  module Minting
5
5
  # Current version of the Minting gem.
6
- VERSION = '1.9.4'
6
+ VERSION = '1.9.6'
7
7
  end
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.9.4
4
+ version: 1.9.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gilson Ferraz