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 +4 -4
- data/README.md +19 -24
- data/lib/minting/currency/currency.rb +5 -3
- data/lib/minting/mint/dsl/numeric.rb +14 -16
- data/lib/minting/mint/dsl/string.rb +7 -9
- data/lib/minting/mint/mint.rb +12 -4
- data/lib/minting/money/constructors.rb +5 -11
- data/lib/minting/money/conversion.rb +1 -1
- data/lib/minting/money/format/formatting.rb +18 -11
- data/lib/minting/money/format/to_s.rb +22 -22
- data/lib/minting/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 51f0a76084db98e53f29af4f82f1284f749c9691727a7a84a7f8cc758d1a9f67
|
|
4
|
+
data.tar.gz: f24a130df454ee80f923aa42fd995e4b3eaf2f4f16d1a6f3a9e84548c08a4988
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
87
|
-
Mint.money(1234.56, 'EUR').
|
|
88
|
-
price.
|
|
89
|
-
price.
|
|
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').
|
|
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.
|
|
97
|
-
price.
|
|
98
|
-
price.
|
|
99
|
-
price.
|
|
100
|
-
(-price).
|
|
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.
|
|
106
|
-
price.
|
|
107
|
-
(-price).
|
|
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.
|
|
106
|
+
price_in_euros.format(format: '%<symbol>2s%<amount>+10f') #=> " € +12.34"
|
|
110
107
|
|
|
111
108
|
# Integral & fractional parts
|
|
112
|
-
price.
|
|
113
|
-
Mint.money(0.99, 'USD').
|
|
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.
|
|
119
|
-
Mint.money(0, 'BRL').
|
|
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').
|
|
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 [
|
|
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
|
|
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 [
|
|
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
|
-
#
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
10
|
-
|
|
8
|
+
# @return [Money] self interpreted as USD
|
|
9
|
+
def dollars = Mint::Money.from(self, 'USD')
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
# @return [Money] self interpreted as EUR
|
|
12
|
+
def euros = Mint::Money.from(self, 'EUR')
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
#
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
data/lib/minting/mint/mint.rb
CHANGED
|
@@ -2,16 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
# Mint currency registration and factory (public API)
|
|
4
4
|
module Mint
|
|
5
|
-
#
|
|
6
|
-
|
|
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,
|
|
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
|
|
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
|
|
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 [
|
|
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
|
|
83
|
-
#
|
|
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 =
|
|
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
|
|
37
|
-
when
|
|
38
|
-
when
|
|
39
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
45
|
-
# money.
|
|
46
|
-
# money.
|
|
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.
|
|
51
|
-
# money.
|
|
52
|
-
# money.
|
|
53
|
-
# money.
|
|
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.
|
|
57
|
-
# money.
|
|
58
|
-
# money.
|
|
59
|
-
# money.
|
|
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.
|
|
62
|
+
# money.format(format: '%<integral>d.%<fractional>02d') #=> "1234.56"
|
|
63
63
|
# price = Mint.money(0.99, 'USD')
|
|
64
|
-
# price.
|
|
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.
|
|
70
|
-
# Mint.money(0, 'BRL').
|
|
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.
|
|
74
|
-
# money.
|
|
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.
|
|
77
|
+
# money.format # decimal and thousand come from locale_backend
|
|
78
78
|
#
|
|
79
|
-
def
|
|
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 =
|
|
104
|
+
def to_s = format
|
|
105
105
|
|
|
106
|
-
alias to_fs
|
|
106
|
+
alias to_fs :format
|
|
107
107
|
end
|
|
108
108
|
end
|
data/lib/minting/version.rb
CHANGED