minting 1.9.7 → 2.0.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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +234 -116
  3. data/Rakefile +2 -7
  4. data/doc/agents/api_review-2026-06-15.md +1 -1
  5. data/doc/agents/copilot-instructions.md +2 -2
  6. data/doc/agents/expired/copilot-instructions.md +2 -2
  7. data/doc/agents/expired/gemini_gem_evaluation.md +2 -2
  8. data/lib/minting/aliases.rb +22 -0
  9. data/lib/minting/currency/currency.rb +81 -9
  10. data/lib/minting/data/crypto-currencies.yaml +126 -0
  11. data/lib/minting/mint/i18n.rb +79 -29
  12. data/lib/minting/mint/mint.rb +1 -26
  13. data/lib/minting/mint/registry/crypto.rb +59 -0
  14. data/lib/minting/mint/registry/registration.rb +1 -2
  15. data/lib/minting/mint/registry/symbols.rb +37 -30
  16. data/lib/minting/mint/rounding.rb +9 -7
  17. data/lib/minting/mint.rb +1 -2
  18. data/lib/minting/money/allocation/allocation.rb +2 -2
  19. data/lib/minting/money/allocation/split.rb +1 -1
  20. data/lib/minting/money/arithmetics/operators.rb +10 -13
  21. data/lib/minting/money/clamp.rb +6 -6
  22. data/lib/minting/money/coercion.rb +1 -1
  23. data/lib/minting/money/comparable.rb +3 -3
  24. data/lib/minting/money/constructors.rb +3 -42
  25. data/lib/minting/money/conversion.rb +22 -18
  26. data/lib/minting/money/format/format.rb +100 -0
  27. data/lib/minting/money/format/formatter.rb +102 -0
  28. data/lib/minting/money/format/to_s.rb +20 -102
  29. data/lib/minting/money/format/validator.rb +34 -0
  30. data/lib/minting/money/money.rb +25 -9
  31. data/lib/minting/money/parse.rb +127 -0
  32. data/lib/minting/money/rounding.rb +27 -0
  33. data/lib/minting/version.rb +1 -1
  34. data/lib/minting.rb +17 -8
  35. metadata +9 -29
  36. data/doc/Mint/Currency.html +0 -2032
  37. data/doc/Mint/Money.html +0 -5139
  38. data/doc/Mint/RangeStepPatch.html +0 -277
  39. data/doc/Mint/Registry.html +0 -863
  40. data/doc/Mint/Rounding.html +0 -506
  41. data/doc/Mint/UnknownCurrency.html +0 -138
  42. data/doc/Mint.html +0 -931
  43. data/doc/Minting.html +0 -142
  44. data/doc/Numeric.html +0 -479
  45. data/doc/String.html +0 -241
  46. data/doc/_index.html +0 -206
  47. data/doc/class_list.html +0 -54
  48. data/doc/css/common.css +0 -1
  49. data/doc/css/full_list.css +0 -206
  50. data/doc/css/style.css +0 -1089
  51. data/doc/file.README.html +0 -291
  52. data/doc/file_list.html +0 -59
  53. data/doc/frames.html +0 -22
  54. data/doc/index.html +0 -291
  55. data/doc/js/app.js +0 -801
  56. data/doc/js/full_list.js +0 -334
  57. data/doc/js/jquery.js +0 -4
  58. data/doc/method_list.html +0 -758
  59. data/doc/top-level-namespace.html +0 -135
  60. data/lib/minting/mint/aliases.rb +0 -16
  61. data/lib/minting/mint/parser/parser.rb +0 -97
  62. data/lib/minting/mint/parser/separators.rb +0 -41
  63. data/lib/minting/money/format/formatting.rb +0 -130
@@ -8,9 +8,13 @@ require_relative 'clamp'
8
8
  require_relative 'coercion'
9
9
  require_relative 'comparable'
10
10
  require_relative 'constructors'
11
+ require_relative 'parse'
11
12
  require_relative 'conversion'
12
- require_relative 'format/formatting'
13
+ require_relative 'format/validator'
14
+ require_relative 'format/formatter'
13
15
  require_relative 'format/to_s'
16
+ require_relative 'format/format'
17
+ require_relative 'rounding'
14
18
 
15
19
  module Mint
16
20
  # Represents a monetary value paired with a currency.
@@ -19,11 +23,14 @@ module Mint
19
23
  class Money
20
24
  attr_reader :amount, :currency
21
25
 
26
+ # Money::Currency is the canonical way to access Currency class
27
+ Currency = Mint::Currency
28
+
22
29
  # Returns the ISO 3-letter currency code string.
23
30
  #
24
31
  # @return [String] the ISO currency code (e.g., "USD", "EUR", "BRL")
25
32
  # @example
26
- # Mint.money(100, 'USD').currency_code #=> "USD"
33
+ # Money.from(100, 'USD').currency_code #=> "USD"
27
34
  def currency_code = currency.code
28
35
 
29
36
  # Returns the monetary amount expressed in the currency's smallest unit (fractional units).
@@ -31,17 +38,26 @@ module Mint
31
38
  #
32
39
  # @return [Integer] the amount in fractional units
33
40
  # @example
34
- # Mint.money(1234.56, 'USD').subunits #=> 123456
35
- # Mint.money(1000, 'JPY').subunits #=> 1000
36
- # Mint.money(123.456, 'IQD').subunits #=> 123456
41
+ # Money.from(1234.56, 'USD').subunits #=> 123456
42
+ # Money.from(1000, 'JPY').subunits #=> 1000
43
+ # Money.from(123.456, 'IQD').subunits #=> 123456
37
44
  def subunits = (amount * currency.fractional_multiplier).to_i
38
45
 
46
+ # Returns the whole-unit (integral) part of the amount.
47
+ # @example
48
+ # Money.from(1234.56, 'USD').integral #=> 1234
49
+ # Money.from(1000, 'JPY').integral #=> 1000
50
+ # Money.from(-9.99, 'USD').integral #=> -9
51
+ def integral = amount.to_i
52
+
53
+ alias to_i integral
54
+
39
55
  # Returns the fractional part of the amount.
40
56
  # @example
41
- # Mint.money(1234.56, 'USD').fractional #=> 56
42
- # Mint.money(1000, 'JPY').fractional #=> 0
43
- # Mint.money(123.456, 'IQD').fractional #=> 456
44
- def fractional = ((amount.abs % 1) * currency.fractional_multiplier).to_i
57
+ # Money.from(1234.56, 'USD').fractional #=> 56
58
+ # Money.from(1000, 'JPY').fractional #=> 0
59
+ # Money.from(123.456, 'IQD').fractional #=> 456
60
+ def fractional = ((amount - amount.to_i) * currency.fractional_multiplier).to_i
45
61
 
46
62
  # Generates a stable hash key for Money instances.
47
63
  #
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mint
4
+ # :nodoc:
5
+ class Money
6
+ # Parses a human-readable money string into a {Money} object.
7
+ #
8
+ # Returns +nil+ when the input is invalid or currency cannot be determined.
9
+ #
10
+ # @param input [String] Amount input, optionally including a currency symbol or code
11
+ # @param currency [String, Symbol, Currency, nil] ISO code when not present in +input+
12
+ # @return [Money, nil]
13
+ #
14
+ # @example With explicit currency
15
+ # Money.parse('19.99', 'USD') #=> [USD 19.99]
16
+ # Money.parse('garbage', 'USD') #=> nil
17
+ #
18
+ # @example With symbol or code in the string
19
+ # Money.parse('$19.99') #=> [USD 19.99]
20
+ # Money.parse('USD 1,234.56') #=> [USD 1234.56]
21
+ def self.parse(input, currency = nil)
22
+ return nil unless input.is_a?(String)
23
+
24
+ input = input.strip
25
+ return nil if input.empty?
26
+
27
+ currency = parse_currency(input, currency)
28
+ return nil unless currency
29
+
30
+ amount = parse_amount(input)
31
+ return nil unless amount
32
+
33
+ amount = currency.normalize_amount(amount)
34
+ new(amount, currency)
35
+ end
36
+
37
+ # Like {.parse} but raises on failure.
38
+ #
39
+ # @param input [String] Amount input, optionally including a currency symbol or code
40
+ # @param currency [String, Symbol, Currency, nil] ISO code when not present in +input+
41
+ # @return [Money]
42
+ # @raise [ArgumentError] when +input+ is invalid or currency cannot be determined
43
+ #
44
+ # @example
45
+ # Money.parse!('19.99', 'USD') #=> [USD 19.99]
46
+ # Money.parse!('garbage', 'USD') #=> ArgumentError
47
+ def self.parse!(input, currency = nil)
48
+ raise ArgumentError, 'input must be a String' unless input.is_a?(String)
49
+
50
+ input = input.strip
51
+ raise ArgumentError, 'input cannot be empty' if input.empty?
52
+
53
+ currency = parse_currency(input, currency)
54
+ raise ArgumentError, "Currency [#{currency}] not found" unless currency
55
+
56
+ amount = parse_amount(input)
57
+ raise ArgumentError, "Could not parse [#{input}]" unless amount
58
+
59
+ amount = currency.normalize_amount(amount)
60
+ new(amount, currency)
61
+ end
62
+
63
+ class << self
64
+ private
65
+
66
+ # Extracts a numeric value from input that should only contain an amount.
67
+ def parse_amount(input)
68
+ accounting_negative = input.start_with?('(') && input.end_with?(')')
69
+
70
+ numeric_input = input.gsub(/[^\d.,-]/, '')
71
+ numeric = parse_separators(numeric_input)
72
+ return nil unless numeric
73
+
74
+ amount = Rational(numeric)
75
+ accounting_negative ? -amount : amount
76
+ end
77
+
78
+ # Extracts currency from a string by matching ISO code or symbol.
79
+ #
80
+ # Scans all uppercase words and returns the first registered code, falling
81
+ # back to symbol matching. This correctly handles inputs like
82
+ # "MAX 10.00 USD" where the first uppercase word isn't a currency code.
83
+ def parse_currency(input, currency = nil)
84
+ input.scan(/\b([A-Z_]+)\b/) do |(code)|
85
+ found = Currency.for_code(code)
86
+ return found if found
87
+ end
88
+
89
+ found = Registry.detect_currency(input)
90
+ return found if found
91
+
92
+ Currency.resolve(currency)
93
+ end
94
+
95
+ # Converts locale-specific decimal/thousand separators into a plain decimal string.
96
+ def parse_separators(numeric)
97
+ return nil unless numeric.match?(/\d/)
98
+
99
+ case classify_separators(numeric)
100
+ when :decimal_period then numeric
101
+ when :decimal_comma then numeric.tr(',', '.')
102
+ when :thousands_comma then numeric.delete(',')
103
+ when :thousands then numeric.delete('.,')
104
+ when :invalid then nil
105
+ when :mixed
106
+ if numeric.rindex(',') > numeric.rindex('.')
107
+ numeric.delete('.').tr(',', '.')
108
+ else
109
+ numeric.delete(',')
110
+ end
111
+ end
112
+ end
113
+
114
+ # Classifies the separator pattern in a numeric string.
115
+ def classify_separators(numeric)
116
+ case [numeric.count('.'), numeric.count(',')]
117
+ in [0, 1] if numeric[-4] == ',' then :thousands_comma
118
+ in [0, 1] then :decimal_comma
119
+ in [0, 0] | [1, 0] then :decimal_period
120
+ in [p, c] if p > 1 && c > 1 then :invalid
121
+ in [p, c] if p > 0 && c > 0 then :mixed
122
+ else :thousands
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mint
4
+ # :nodoc:
5
+ class Money
6
+ # Executes a block with a specific rounding mode applied to all money
7
+ # construction, parsing, change, allocation, and split operations.
8
+ #
9
+ # Restores the previous mode (or default) when the block exits, even on
10
+ # exception.
11
+ #
12
+ # Rounding-mode support is loaded lazily on first call. Once loaded,
13
+ # +Currency#normalize_amount+ is patched to dispatch through the
14
+ # rounding module, adding ~10–35&ns of overhead to every money creation
15
+ # or mutation. When rounding modes are never used (the common case),
16
+ # the fast path incurs zero overhead.
17
+ #
18
+ # @param mode [Symbol] one of: +:half_up+, +:half_down+, +:floor+,
19
+ # +:ceil+, +:truncate+, +:down+
20
+ # @yield block to execute with the rounding mode active
21
+ # @raise [ArgumentError] if +mode+ is not a recognised rounding mode
22
+ def self.with_rounding(mode, &)
23
+ require_relative '../mint/rounding' unless defined?(Mint::Rounding)
24
+ Mint::Rounding.with_mode(mode, &)
25
+ end
26
+ end
27
+ 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.7'
6
+ VERSION = '2.0.0'
7
7
  end
data/lib/minting.rb CHANGED
@@ -3,12 +3,21 @@
3
3
  require 'minting/mint'
4
4
  require 'minting/version'
5
5
 
6
- # By default, expose Mint::Money as the top-level Money constant for
7
- # convenience. If Money is already defined (e.g. by the `money` gem), warn
8
- # and skip so both libraries can coexist in the same process without
9
- # corrupting either class.
10
- if defined?(Money) && Money != Mint::Money
11
- warn "minting: top-level Money is already defined (#{Money}); skipping auto-bind. Use Mint::Money."
12
- else
13
- Money = Mint::Money unless defined?(Money)
6
+ # @!parse
7
+ # # Top-level constant auto-bound to {Mint::Money} for convenience.
8
+ # Money = Mint::Money
9
+
10
+ # Top-level constant auto-bound to {Mint::Money} for convenience.
11
+ #
12
+ # Set at require-time via `require 'minting'`. If {::Money} is already
13
+ # defined (e.g. by the `money` gem), a warning is emitted and the existing
14
+ # constant is preserved — use {Mint::Money} explicitly in that case.
15
+ #
16
+ # @see Mint::Money
17
+ # @note This is a **breaking change from v1.x** where both +Money+ and
18
+ # +Currency+ required explicit opt-in via +Mint.use_top_level_constants!+.
19
+ Money = Mint::Money unless defined?(Money)
20
+
21
+ if Money != Mint::Money
22
+ warn "minting: top-level Money was already defined (#{Money}); skipping auto-bind! Use Mint::Money."
14
23
  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.7
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gilson Ferraz
@@ -36,17 +36,6 @@ files:
36
36
  - bin/check-currencies
37
37
  - bin/console
38
38
  - bin/setup
39
- - doc/Mint.html
40
- - doc/Mint/Currency.html
41
- - doc/Mint/Money.html
42
- - doc/Mint/RangeStepPatch.html
43
- - doc/Mint/Registry.html
44
- - doc/Mint/Rounding.html
45
- - doc/Mint/UnknownCurrency.html
46
- - doc/Minting.html
47
- - doc/Numeric.html
48
- - doc/String.html
49
- - doc/_index.html
50
39
  - doc/agents/api_review-2026-06-15.md
51
40
  - doc/agents/copilot-instructions.md
52
41
  - doc/agents/expired/AGENTS.md
@@ -54,31 +43,18 @@ files:
54
43
  - doc/agents/expired/gemini_gem_evaluation.md
55
44
  - doc/agents/expired/recommendations.md
56
45
  - doc/agents/expired/rubocop-issues.md
57
- - doc/class_list.html
58
- - doc/css/common.css
59
- - doc/css/full_list.css
60
- - doc/css/style.css
61
- - doc/file.README.html
62
- - doc/file_list.html
63
- - doc/frames.html
64
- - doc/index.html
65
- - doc/js/app.js
66
- - doc/js/full_list.js
67
- - doc/js/jquery.js
68
- - doc/method_list.html
69
- - doc/top-level-namespace.html
70
46
  - lib/minting.rb
47
+ - lib/minting/aliases.rb
71
48
  - lib/minting/currency/currency.rb
49
+ - lib/minting/data/crypto-currencies.yaml
72
50
  - lib/minting/data/world-currencies.yaml
73
51
  - lib/minting/mint.rb
74
- - lib/minting/mint/aliases.rb
75
52
  - lib/minting/mint/dsl/numeric.rb
76
53
  - lib/minting/mint/dsl/range.rb
77
54
  - lib/minting/mint/dsl/string.rb
78
55
  - lib/minting/mint/i18n.rb
79
56
  - lib/minting/mint/mint.rb
80
- - lib/minting/mint/parser/parser.rb
81
- - lib/minting/mint/parser/separators.rb
57
+ - lib/minting/mint/registry/crypto.rb
82
58
  - lib/minting/mint/registry/registration.rb
83
59
  - lib/minting/mint/registry/registry.rb
84
60
  - lib/minting/mint/registry/symbols.rb
@@ -93,9 +69,13 @@ files:
93
69
  - lib/minting/money/comparable.rb
94
70
  - lib/minting/money/constructors.rb
95
71
  - lib/minting/money/conversion.rb
96
- - lib/minting/money/format/formatting.rb
72
+ - lib/minting/money/format/format.rb
73
+ - lib/minting/money/format/formatter.rb
97
74
  - lib/minting/money/format/to_s.rb
75
+ - lib/minting/money/format/validator.rb
98
76
  - lib/minting/money/money.rb
77
+ - lib/minting/money/parse.rb
78
+ - lib/minting/money/rounding.rb
99
79
  - lib/minting/version.rb
100
80
  - minting.gemspec
101
81
  homepage: https://github.com/gferraz/minting