minting 1.7.0 → 1.7.3

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +80 -112
  3. data/Rakefile +13 -1
  4. data/bin/bench_check +46 -0
  5. data/doc/Mint/Currency.html +446 -46
  6. data/doc/Mint/CurrencyRegistry.html +7 -7
  7. data/doc/Mint/Money.html +203 -177
  8. data/doc/Mint/RangeStepPatch.html +277 -0
  9. data/doc/Mint/Registry.html +842 -0
  10. data/doc/Mint/UnknownCurrency.html +2 -2
  11. data/doc/Mint.html +385 -66
  12. data/doc/Minting.html +3 -3
  13. data/doc/_index.html +24 -9
  14. data/doc/agents/api_review-2026-06-15.md +342 -0
  15. data/doc/class_list.html +1 -1
  16. data/doc/file.README.html +97 -89
  17. data/doc/index.html +97 -89
  18. data/doc/method_list.html +97 -25
  19. data/doc/top-level-namespace.html +13 -5
  20. data/lib/minting/currency/currency.rb +75 -0
  21. data/lib/minting/mint/aliases.rb +3 -0
  22. data/lib/minting/mint/dsl/{refinements.rb → numeric.rb} +6 -5
  23. data/lib/minting/mint/dsl/range.rb +31 -18
  24. data/lib/minting/mint/dsl/string.rb +12 -0
  25. data/lib/minting/mint/dsl/top_level.rb +3 -0
  26. data/lib/minting/mint/locale_backend.rb +29 -0
  27. data/lib/minting/mint/mint.rb +28 -12
  28. data/lib/minting/mint/parser/parser.rb +62 -0
  29. data/lib/minting/mint/parser/separators.rb +39 -0
  30. data/lib/minting/mint/registry/registration.rb +33 -0
  31. data/lib/minting/mint/registry/registry.rb +38 -0
  32. data/lib/minting/mint/registry/symbols.rb +49 -0
  33. data/lib/minting/mint/registry/zeros.rb +18 -0
  34. data/lib/minting/mint.rb +13 -16
  35. data/lib/minting/money/allocation/allocation.rb +25 -0
  36. data/lib/minting/money/{allocation.rb → allocation/split.rb} +1 -19
  37. data/lib/minting/money/arithmetics/methods.rb +27 -0
  38. data/lib/minting/money/{arithmetics.rb → arithmetics/operators.rb} +0 -21
  39. data/lib/minting/money/clamp.rb +66 -0
  40. data/lib/minting/money/coercion.rb +10 -0
  41. data/lib/minting/money/comparable.rb +6 -0
  42. data/lib/minting/money/constructors.rb +14 -9
  43. data/lib/minting/money/format/formatting.rb +60 -0
  44. data/lib/minting/money/{formatting.rb → format/to_s.rb} +13 -36
  45. data/lib/minting/money/money.rb +12 -58
  46. data/lib/minting/version.rb +1 -1
  47. metadata +29 -19
  48. data/lib/minting/mint/currency/currency.rb +0 -36
  49. data/lib/minting/mint/currency/currency_registry.rb +0 -67
  50. data/lib/minting/mint/currency/world_currencies.rb +0 -16
  51. data/lib/minting/mint/parser.rb +0 -85
  52. /data/doc/agents/{AGENTS.md → expired/AGENTS.md} +0 -0
  53. /data/doc/agents/{copilot-instructions.md → expired/copilot-instructions.md} +0 -0
  54. /data/doc/agents/{gemini_gem_evaluation.md → expired/gemini_gem_evaluation.md} +0 -0
  55. /data/doc/agents/{recommendations.md → expired/recommendations.md} +0 -0
  56. /data/doc/agents/{rubocop-issues.md → expired/rubocop-issues.md} +0 -0
@@ -1,67 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'yaml'
4
-
5
- # Mint currency store (internal)
6
- module Mint
7
- # Internal currency registry
8
- # Manages the registry cache and currency symbol lookups.
9
- module CurrencyRegistry
10
- extend self
11
-
12
- # Returns the hash of all registered currencies.
13
- #
14
- # @return [Hash{String => Currency}] registered currencies mapped by code
15
- # @api private
16
- def currencies
17
- @currencies ||= Mint.world_currencies.dup
18
- end
19
-
20
- # Registered symbols sorted for detection: longest match wins, then parser priority.
21
- #
22
- # @return [Array<Array<String, Currency>>] sorted symbol-to-currency mappings
23
- # @api private
24
- def currency_symbols
25
- @currency_symbols ||= begin
26
- currencies.values
27
- .reject { |c| c.symbol.empty? }
28
- .map { |currency| [currency.symbol, currency] }
29
- .sort_by { |symbol, currency| [-symbol.length, -currency.priority] }
30
- end.freeze
31
- end
32
-
33
- # Registers a new currency, raising a KeyError if already registered.
34
- #
35
- # @param code [String] the unique currency code
36
- # @param subunit [Integer] the decimal subunit precision, defaults to 0
37
- # @param symbol [String] the display symbol
38
- # @param priority [Integer] parser precedence priority
39
- # @return [Currency] the newly registered Currency instance
40
- # @raise [ArgumentError] if the code contains invalid characters
41
- # @raise [KeyError] if the currency code is already registered
42
- def register(code:, subunit: 0, symbol: '', priority: 0)
43
- raise ArgumentError, 'Currency code must be String' unless code.is_a? String
44
- unless code.match?(/^[A-Z_]+$/)
45
- raise ArgumentError,
46
- "Currency code must have only letters or '_' ('USD',, 'MY_COIN')"
47
- end
48
-
49
- currencies = CurrencyRegistry.currencies
50
- raise KeyError, "Currency: #{code} already registered" if currencies[code]
51
-
52
- currency = currencies[code] = Currency.new(code:, subunit:, symbol:, priority:)
53
- invalidate_symbols_cache
54
- currency
55
- end
56
-
57
- private
58
-
59
- # Clears and refreshes the currency symbol cache.
60
- # Called when currencies are registered.
61
- #
62
- # @api private
63
- def invalidate_symbols_cache
64
- @currency_symbols = nil
65
- end
66
- end
67
- end
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Mint list of world currencies
4
- module Mint
5
- # Loads ISO world currencies from YAML file into the registry.
6
- #
7
- # @return [Hash{String => Currency}] ISO-4217 world currencies mapped by code
8
- # @api private
9
- def self.world_currencies
10
- @world_currencies ||= begin
11
- path = File.join(File.expand_path('../../data', __dir__), 'world-currencies.yaml')
12
-
13
- YAML.load_file(path).to_h { |entry| [entry['code'], Currency.new(**entry.transform_keys(&:to_sym))] }
14
- end.freeze
15
- end
16
- end
@@ -1,85 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Mint Money parsing
4
- module Mint
5
- extend self
6
-
7
- # Parses a human-readable money string into a {Money} object.
8
- #
9
- # @param input [String] Amount input, optionally including a currency symbol or code
10
- # @param currency [String, Symbol, Currency, nil] ISO code when not present in +input+
11
- # @return [Money]
12
- # @raise [ArgumentError] when +input+ is invalid or currency cannot be determined
13
- #
14
- # @example With explicit currency
15
- # Money.parse('19.99', 'USD') #=> [USD 19.99]
16
- # Money.parse('1.234,56', 'EUR') #=> [EUR 1234.56]
17
- #
18
- # @example With symbol or code in the string
19
- # Money.parse('$19.99') #=> [USD 19.99]
20
- # Money.parse('19,99 €') #=> [EUR 19.99]
21
- # Money.parse('USD 1,234.56') #=> [USD 1234.56]
22
- def parse(input, currency = nil)
23
- raise ArgumentError, 'input must be a String' unless input.is_a?(String)
24
-
25
- input = input.strip
26
- raise ArgumentError, 'input cannot be empty' if input.empty?
27
-
28
- currency = Mint.currency(currency) || parse_currency(input)
29
- raise ArgumentError, "Currency [#{currency}] not registered" unless currency
30
-
31
- amount = currency.normalize_amount(parse_amount(input))
32
- Mint::Money.new(amount, currency)
33
- end
34
-
35
- private
36
-
37
- # Extracts a numeric value from input that should only contain an amount.
38
- def parse_amount(input)
39
- # Remove any charater that is not a digit, comma or period
40
- numeric = input.scan(/[\d.,-]/).join
41
- numeric = normalize_separators(numeric)
42
- Rational(numeric)
43
- end
44
-
45
- def classify_separators(numeric)
46
- case [numeric.count(','), numeric.count('.')]
47
- in [0, 0] | [0, 1] then :decimal_period # e.g. "1500" or "34.21".
48
- in [1, 0] then :decimal_comma # Only one comma: decimal (e.g. 19,99 or 1,234).
49
- in [c, p] if c > 1 && p > 1 then :ambiguous # Both separators appear multiple times
50
- in [c, p] if c > 0 && p > 0 then :mixed # Commas and dots: the rightmost one is the decimal separator.
51
- else :thousands_only # Multiple of the same separator only (e.g. 1,234,567)
52
- end
53
- end
54
-
55
- # Converts locale-specific decimal/thousand separators into a plain decimal string.
56
- def normalize_separators(numeric)
57
- case classify_separators(numeric)
58
- when :decimal_period then numeric # Nothing to normalize (e.g. "1500" or "34.21").
59
- when :decimal_comma then numeric.tr(',', '.') # Only one comma: decimal (e.g. 19,99 or 1,234).
60
- when :thousands_only then numeric.delete(',.')
61
- when :ambiguous then raise ArgumentError, "could not distinguish decimal and thousand separators in '#{numeric}'"
62
- when :mixed # Commas and dots: the rightmost one is the decimal separator.
63
- if numeric.rindex(',') > numeric.rindex('.')
64
- numeric.delete('.').tr(',', '.')
65
- else
66
- numeric.delete(',')
67
- end
68
- end
69
- end
70
-
71
- def parse_currency(input)
72
- case input
73
- when String
74
- # Prefer an explicit ISO 4217 code (e.g. "USD 1,234.56") over symbol matching.
75
- currency = Mint.currency(input[/\b([A-Z_]+)\b/, 1])
76
- return currency if currency
77
-
78
- # Fall back to registered symbols, longest first (HK$ before $).
79
- CurrencyRegistry.currency_symbols.each do |symbol, currency|
80
- return currency if input.include?(symbol)
81
- end
82
- end
83
- raise ArgumentError, 'Currency could not be detected'
84
- end
85
- end
File without changes