minting 1.3.0 → 1.4.0
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 +17 -2
- data/Rakefile +3 -3
- data/doc/Mint/Currency.html +816 -0
- data/doc/Mint/Money.html +3471 -0
- data/doc/Mint.html +953 -0
- data/doc/Minting.html +142 -0
- data/doc/_index.html +136 -0
- data/doc/agents/AGENTS.md +25 -0
- data/doc/agents/copilot-instructions.md +75 -0
- data/doc/agents/gemini_gem_evaluation.md +245 -0
- data/doc/agents/recommendations.md +335 -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 +379 -0
- data/doc/file_list.html +59 -0
- data/doc/frames.html +22 -0
- data/doc/index.html +379 -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 +470 -0
- data/doc/top-level-namespace.html +112 -0
- data/lib/minting/mint/currency.rb +1 -1
- data/lib/minting/mint/registry.rb +9 -18
- data/lib/minting/money/conversion.rb +5 -16
- data/lib/minting/money/formatting.rb +34 -1
- data/lib/minting/money/money.rb +83 -5
- data/lib/minting/version.rb +1 -1
- data/minting.gemspec +1 -0
- metadata +38 -2
|
@@ -6,20 +6,13 @@ module Mint
|
|
|
6
6
|
# Converts the monetary amount to a {BigDecimal} object.
|
|
7
7
|
#
|
|
8
8
|
# @return [BigDecimal] the decimal representation of the money amount
|
|
9
|
-
|
|
10
|
-
def to_d
|
|
11
|
-
raise NoMethodError, 'decimal gem required' unless defined?(BigDecimal)
|
|
12
|
-
|
|
13
|
-
amount.to_d 0
|
|
14
|
-
end
|
|
9
|
+
def to_d = amount.to_d 0
|
|
15
10
|
|
|
16
11
|
# Converts the monetary amount to a standard float.
|
|
17
12
|
# Note: Using float conversion loses precision guarantees.
|
|
18
13
|
#
|
|
19
14
|
# @return [Float] the floating-point representation of the money amount
|
|
20
|
-
def to_f
|
|
21
|
-
amount.to_f
|
|
22
|
-
end
|
|
15
|
+
def to_f = amount.to_f
|
|
23
16
|
|
|
24
17
|
# Renders a safe HTML5 `<data>` element containing the formatted currency.
|
|
25
18
|
# Embeds the ISO currency description and raw value as the metadata `title` attribute.
|
|
@@ -29,15 +22,13 @@ module Mint
|
|
|
29
22
|
def to_html(format = DEFAULT_FORMAT)
|
|
30
23
|
title = Kernel.format("#{currency_code} %0.#{currency.subunit}f", amount)
|
|
31
24
|
body = to_s(format: format)
|
|
32
|
-
%(<data class='money' title='#{
|
|
25
|
+
%(<data class='money' title='#{title}'>#{ERB::Util.html_escape(body)}</data>)
|
|
33
26
|
end
|
|
34
27
|
|
|
35
28
|
# Truncates and converts the monetary amount to an Integer.
|
|
36
29
|
#
|
|
37
30
|
# @return [Integer] the integer representation of the money amount
|
|
38
|
-
def to_i
|
|
39
|
-
amount.to_i
|
|
40
|
-
end
|
|
31
|
+
def to_i = amount.to_i
|
|
41
32
|
|
|
42
33
|
def to_hash
|
|
43
34
|
{ currency: currency_code, amount: Kernel.format("%0.#{currency.subunit}f", amount) }
|
|
@@ -56,8 +47,6 @@ module Mint
|
|
|
56
47
|
# Returns the exact internal Rational representation of the monetary amount.
|
|
57
48
|
#
|
|
58
49
|
# @return [Rational] the rational representation of the money amount
|
|
59
|
-
def to_r
|
|
60
|
-
amount
|
|
61
|
-
end
|
|
50
|
+
def to_r = amount
|
|
62
51
|
end
|
|
63
52
|
end
|
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
module Mint
|
|
2
2
|
# Formatting functionality for Money objects
|
|
3
3
|
class Money
|
|
4
|
+
# Keys accepted in the per-sign Hash form of `to_s(format:)`.
|
|
5
|
+
SIGN_FORMAT_KEYS = %i[positive negative zero].freeze
|
|
6
|
+
|
|
4
7
|
# Formats money as a string with customizable format, thousand delimiter, and decimal
|
|
5
8
|
#
|
|
6
|
-
# @param format [String] Format string with placeholders
|
|
9
|
+
# @param format [String, Hash] Either a Format string with placeholders
|
|
10
|
+
# (%<symbol>s, %<amount>f, %<currency>s), or a Hash with per-sign keys
|
|
11
|
+
# (:positive, :negative, :zero) each holding a format string. A Hash
|
|
12
|
+
# is convenient for sign-aware formats such as accounting parentheses:
|
|
13
|
+
#
|
|
14
|
+
# money.to_s(format: { negative: '(%<symbol>s%<amount>f)' })
|
|
15
|
+
#
|
|
16
|
+
# Missing keys fall back to the module default, so a Hash with only
|
|
17
|
+
# :negative will still format positives sensibly. The valid keys are
|
|
18
|
+
# :positive, :negative, :zero; anything else raises ArgumentError.
|
|
7
19
|
# @param thousand [String, false] Thousands delimiter (e.g., ',' for 1,000)
|
|
8
20
|
# @param decimal [String] Decimal separator (e.g., '.' or ',')
|
|
9
21
|
# @return [String] Formatted money string
|
|
10
22
|
#
|
|
23
|
+
# @raise [ArgumentError] if +format+ is not a String or Hash, the Hash
|
|
24
|
+
# is empty, or the Hash contains an unrecognised key.
|
|
25
|
+
#
|
|
11
26
|
# @example Basic formatting
|
|
12
27
|
# money = Mint.money(1234.56, 'USD')
|
|
13
28
|
# money.to_s #=> "$1,234.56"
|
|
@@ -20,6 +35,11 @@ module Mint
|
|
|
20
35
|
# money.to_s(format: '%<amount>f %<symbol>s') #=> "1234.56 $"
|
|
21
36
|
# money.to_s(format: '%<symbol>s%<amount>+f') #=> "$+1234.56"
|
|
22
37
|
#
|
|
38
|
+
# @example Per-sign Hash format (accounting parentheses)
|
|
39
|
+
# loss = Mint.money(-1234.56, 'USD')
|
|
40
|
+
# loss.to_s(format: { negative: '(%<symbol>s%<amount>f)' }) #=> "($1,234.56)"
|
|
41
|
+
# Mint.money(0, 'BRL').to_s(format: { zero: '--' }) #=> "--"
|
|
42
|
+
#
|
|
23
43
|
# @example Padding and alignment
|
|
24
44
|
# money.to_s(format: '%<amount>10.2f') #=> " 1234.56"
|
|
25
45
|
# money.to_s(format: '%<symbol>s%<amount>010.2f') #=> "$0001234.56"
|
|
@@ -27,6 +47,8 @@ module Mint
|
|
|
27
47
|
def to_s(format: '%<symbol>s%<amount>f', decimal: '.', thousand: ',', width: nil)
|
|
28
48
|
raise ArgumentError, 'Invalid format' unless format.is_a?(String) || format.is_a?(Hash)
|
|
29
49
|
|
|
50
|
+
validate_format_hash!(format) if format.is_a?(Hash)
|
|
51
|
+
|
|
30
52
|
formatted = format_amount(format)
|
|
31
53
|
|
|
32
54
|
formatted.tr!('.', decimal) if decimal != '.'
|
|
@@ -43,6 +65,17 @@ module Mint
|
|
|
43
65
|
|
|
44
66
|
private
|
|
45
67
|
|
|
68
|
+
def validate_format_hash!(format)
|
|
69
|
+
raise ArgumentError, 'format Hash must not be empty' if format.empty?
|
|
70
|
+
|
|
71
|
+
unknown = format.keys - SIGN_FORMAT_KEYS
|
|
72
|
+
return if unknown.empty?
|
|
73
|
+
|
|
74
|
+
raise ArgumentError,
|
|
75
|
+
"Unknown format Hash key(s): #{unknown.inspect}. " \
|
|
76
|
+
"Valid keys are #{SIGN_FORMAT_KEYS.inspect}"
|
|
77
|
+
end
|
|
78
|
+
|
|
46
79
|
def format_amount(format)
|
|
47
80
|
format = { positive: format } if format.is_a?(String)
|
|
48
81
|
value = amount
|
data/lib/minting/money/money.rb
CHANGED
|
@@ -14,14 +14,39 @@ module Mint
|
|
|
14
14
|
raise ArgumentError, 'amount must be Numeric' unless amount.is_a?(Numeric)
|
|
15
15
|
|
|
16
16
|
checked_currency = Mint.currency(currency)
|
|
17
|
-
unless checked_currency
|
|
18
|
-
raise ArgumentError,
|
|
19
|
-
"Currency not found (#{currency}). Check Mint.currencies"
|
|
20
|
-
end
|
|
17
|
+
raise ArgumentError, "Currency not found (#{currency})" unless checked_currency
|
|
21
18
|
|
|
22
19
|
new(checked_currency.normalize_amount(amount), checked_currency)
|
|
23
20
|
end
|
|
24
21
|
|
|
22
|
+
# Builds a Money from a fractional (smallest-unit) Integer amount.
|
|
23
|
+
# This is the inverse of {#fractional}: for USD, the fractional unit is
|
|
24
|
+
# 1 cent; for JPY it is 1 yen; for IQD it is 1 dinar (subunit 3).
|
|
25
|
+
#
|
|
26
|
+
# @param fractional [Integer] the amount expressed in the currency's
|
|
27
|
+
# smallest unit (e.g. cents). Must be an Integer to preserve exactness.
|
|
28
|
+
# @param currency [String, Symbol, Currency] the currency identifier
|
|
29
|
+
# @return [Money] the resulting Money instance
|
|
30
|
+
# @raise [ArgumentError] if +fractional+ is not an Integer or +currency+
|
|
31
|
+
# is not registered
|
|
32
|
+
#
|
|
33
|
+
# @example USD cents
|
|
34
|
+
# Money.from_fractional(123_456, 'USD') #=> [USD 1234.56]
|
|
35
|
+
# @example JPY (subunit 0)
|
|
36
|
+
# Money.from_fractional(1234, 'JPY') #=> [JPY 1234]
|
|
37
|
+
# @example Round trip
|
|
38
|
+
# m = Mint.money(9.99, 'USD')
|
|
39
|
+
# Money.from_fractional(m.fractional, 'USD') == m #=> true
|
|
40
|
+
def self.from_fractional(fractional, currency)
|
|
41
|
+
raise ArgumentError, 'fractional must be an Integer' unless fractional.is_a?(Integer)
|
|
42
|
+
|
|
43
|
+
checked_currency = Mint.currency(currency)
|
|
44
|
+
raise ArgumentError, "Currency not found (#{currency})" unless checked_currency
|
|
45
|
+
|
|
46
|
+
amount = Rational(fractional, checked_currency.fractional_multiplier)
|
|
47
|
+
new(amount, checked_currency)
|
|
48
|
+
end
|
|
49
|
+
|
|
25
50
|
# Returns the ISO 3-letter currency code string.
|
|
26
51
|
#
|
|
27
52
|
# @return [String] the ISO currency code
|
|
@@ -38,7 +63,7 @@ module Mint
|
|
|
38
63
|
# @param new_amount [Numeric] The new amount
|
|
39
64
|
# @return [Money] A new Money object or self
|
|
40
65
|
def mint(new_amount)
|
|
41
|
-
new_amount =
|
|
66
|
+
new_amount = currency.normalize_amount(new_amount)
|
|
42
67
|
new_amount == amount ? self : Money.new(new_amount, currency)
|
|
43
68
|
end
|
|
44
69
|
|
|
@@ -55,6 +80,59 @@ module Mint
|
|
|
55
80
|
# @return [Boolean] true if currencies match, false otherwise
|
|
56
81
|
def same_currency?(other) = other.respond_to?(:currency) && other.currency == currency
|
|
57
82
|
|
|
83
|
+
# Constrains +self+ to the inclusive range [+min+, +max+].
|
|
84
|
+
#
|
|
85
|
+
# Both bounds may be either a same-currency {Money} or a {Numeric}. A
|
|
86
|
+
# Numeric is interpreted as an amount in +self+'s currency, so the common
|
|
87
|
+
# pricing idiom +price.clamp(0, 100)+ reads as "0 to 100 in the same
|
|
88
|
+
# currency as +price+".
|
|
89
|
+
#
|
|
90
|
+
# When +self+ is already in range the receiver is returned (no new object
|
|
91
|
+
# allocated). When out of range, the nearest bound is returned as a new
|
|
92
|
+
# frozen {Money} in +self+'s currency.
|
|
93
|
+
#
|
|
94
|
+
# @param min [Money, Numeric] lower bound (inclusive)
|
|
95
|
+
# @param max [Money, Numeric] upper bound (inclusive)
|
|
96
|
+
# @return [Money] +self+ if in range, otherwise the nearer bound
|
|
97
|
+
# @raise [ArgumentError] if +min+ or +max+ is not a Money or Numeric; if
|
|
98
|
+
# a Money operand has a different currency; if +min+ > +max+
|
|
99
|
+
#
|
|
100
|
+
# @example In range
|
|
101
|
+
# Mint.money(5, 'USD').clamp(0, 10) #=> [USD 5.00] (returns self)
|
|
102
|
+
#
|
|
103
|
+
# @example Out of range, with Numeric bounds
|
|
104
|
+
# Mint.money(50, 'USD').clamp(0, 10) #=> [USD 10.00]
|
|
105
|
+
#
|
|
106
|
+
# @example Out of range, with Money bounds
|
|
107
|
+
# loss = Mint.money(-5, 'USD')
|
|
108
|
+
# floor = Mint.money(0, 'USD')
|
|
109
|
+
# ceil = Mint.money(10, 'USD')
|
|
110
|
+
# loss.clamp(floor, ceil) #=> [USD 0.00]
|
|
111
|
+
#
|
|
112
|
+
# @example Subunit-0 currency (JPY)
|
|
113
|
+
# Mint.money(500, 'JPY').clamp(0, 100) #=> [JPY 100]
|
|
114
|
+
def clamp(min, max)
|
|
115
|
+
case min
|
|
116
|
+
when Numeric
|
|
117
|
+
when Money
|
|
118
|
+
raise(ArgumentError, "min currency must be: #{currency_code}") unless same_currency?(min)
|
|
119
|
+
|
|
120
|
+
min = min.amount
|
|
121
|
+
else raise(ArgumentError, 'min must be Numeric or Money')
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
case max
|
|
125
|
+
when Numeric
|
|
126
|
+
when Money
|
|
127
|
+
raise(ArgumentError, "max currency must be: #{currency_code}") unless same_currency?(max)
|
|
128
|
+
|
|
129
|
+
max = max.amount
|
|
130
|
+
else raise(ArgumentError, 'max must be Numeric or Money')
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
mint(amount.clamp(min, max))
|
|
134
|
+
end
|
|
135
|
+
|
|
58
136
|
private
|
|
59
137
|
|
|
60
138
|
# Initializes a new Money object with the given amount and currency.
|
data/lib/minting/version.rb
CHANGED
data/minting.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: minting
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gilson Ferraz
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: bigdecimal
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '4.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '4.0'
|
|
12
26
|
description: Library to manipulate currency values
|
|
13
27
|
email: []
|
|
14
28
|
executables: []
|
|
@@ -20,6 +34,28 @@ files:
|
|
|
20
34
|
- Rakefile
|
|
21
35
|
- bin/console
|
|
22
36
|
- bin/setup
|
|
37
|
+
- doc/Mint.html
|
|
38
|
+
- doc/Mint/Currency.html
|
|
39
|
+
- doc/Mint/Money.html
|
|
40
|
+
- doc/Minting.html
|
|
41
|
+
- doc/_index.html
|
|
42
|
+
- doc/agents/AGENTS.md
|
|
43
|
+
- doc/agents/copilot-instructions.md
|
|
44
|
+
- doc/agents/gemini_gem_evaluation.md
|
|
45
|
+
- doc/agents/recommendations.md
|
|
46
|
+
- doc/class_list.html
|
|
47
|
+
- doc/css/common.css
|
|
48
|
+
- doc/css/full_list.css
|
|
49
|
+
- doc/css/style.css
|
|
50
|
+
- doc/file.README.html
|
|
51
|
+
- doc/file_list.html
|
|
52
|
+
- doc/frames.html
|
|
53
|
+
- doc/index.html
|
|
54
|
+
- doc/js/app.js
|
|
55
|
+
- doc/js/full_list.js
|
|
56
|
+
- doc/js/jquery.js
|
|
57
|
+
- doc/method_list.html
|
|
58
|
+
- doc/top-level-namespace.html
|
|
23
59
|
- lib/minting.rb
|
|
24
60
|
- lib/minting/data/currencies.yaml
|
|
25
61
|
- lib/minting/mint.rb
|