minting 1.3.0 → 1.5.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.
@@ -1,27 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mint
4
+ # Money constructors
2
5
  class Money
3
6
  # The default display format pattern for formatting monetary values.
4
7
  # Uses `%<symbol>s` for the currency symbol and `%<amount>f` for the rounded amount.
5
- DEFAULT_FORMAT = '%<symbol>s%<amount>f'.freeze
8
+ DEFAULT_FORMAT = '%<symbol>s%<amount>f'
6
9
 
7
10
  attr_reader :amount, :currency
8
11
 
9
- # Creates a new Money immutable object with the specified amount and currency
10
- # @param amount [Numeric] The monetary amount
11
- # @param currency [Currency] The currency object
12
- # @raise [ArgumentError] If amount is not numeric or currency is invalid
13
- def self.create(amount, currency)
14
- raise ArgumentError, 'amount must be Numeric' unless amount.is_a?(Numeric)
15
-
16
- checked_currency = Mint.currency(currency)
17
- unless checked_currency
18
- raise ArgumentError,
19
- "Currency not found (#{currency}). Check Mint.currencies"
20
- end
21
-
22
- new(checked_currency.normalize_amount(amount), checked_currency)
23
- end
24
-
25
12
  # Returns the ISO 3-letter currency code string.
26
13
  #
27
14
  # @return [String] the ISO currency code
@@ -34,14 +21,6 @@ module Mint
34
21
  # @return [Integer] the calculated hash value
35
22
  def hash = [amount, currency_code].hash
36
23
 
37
- # Returns a new Money object with the specified amount, or self if unchanged
38
- # @param new_amount [Numeric] The new amount
39
- # @return [Money] A new Money object or self
40
- def mint(new_amount)
41
- new_amount = new_amount.to_r.round(currency.subunit)
42
- new_amount == amount ? self : Money.new(new_amount, currency)
43
- end
44
-
45
24
  # Returns a standard developer-oriented string inspection of the Money object.
46
25
  #
47
26
  # @return [String] the formatted inspect representation
@@ -51,19 +30,66 @@ module Mint
51
30
 
52
31
  # Helper method to verify if another object has the identical currency.
53
32
  #
54
- # @param other [Object] the target object to compare
33
+ # @param other [Currency] the target currency to compare
55
34
  # @return [Boolean] true if currencies match, false otherwise
56
- def same_currency?(other) = other.respond_to?(:currency) && other.currency == currency
35
+ def same_currency?(other) = other.currency == currency
36
+
37
+ # Constrains +self+ to the inclusive range [+min+, +max+].
38
+ #
39
+ # Bounds may be:
40
+ # - nil meaning no boundary
41
+ # - same-currency {Money} or Range
42
+ # - Numeric amount, or Range
43
+ #
44
+ # Numeric is interpreted as an amount in +self+'s currency, so the common
45
+ # pricing idiom +price.clamp(0, 100)+ reads as "0 to 100 in the same
46
+ # currency as +price+".
47
+ #
48
+ # When +self+ is already in range the receiver is returned (no new object
49
+ # allocated). When out of range, the nearest bound is returned as a new
50
+ # frozen {Money} in +self+'s currency.
51
+ #
52
+ # @param min_or_range [Money, Numeric, Range, nil] lower bound (inclusive), or range
53
+ # @param max [Money, Numeric, nil] upper bound (inclusive)
54
+ # @return [Money] +self+ if in range, otherwise the nearer bound
55
+ # @raise [ArgumentError] if +min+ or +max+ is not a Money, Numeric or nil; if
56
+ # a Money operand has a different currency; if +min+ > +max+;
57
+ # if min is a Range, and max is not nil
58
+ #
59
+ # @example In range
60
+ # Mint.money(5, 'USD').clamp(0, 10) #=> [USD 5.00] (returns self)
61
+ #
62
+ # @example Out of range, with Numeric bounds
63
+ # Mint.money(50, 'USD').clamp(0, 10) #=> [USD 10.00]
64
+ #
65
+ # @example Out of range, with Money bounds
66
+ # loss = Mint.money(-5, 'USD')
67
+ # floor = Mint.money(0, 'USD')
68
+ # ceil = Mint.money(10, 'USD')
69
+ # loss.clamp(floor, ceil) #=> [USD 0.00]
70
+ #
71
+ # @example Subunit-0 currency (JPY)
72
+ # Mint.money(500, 'JPY').clamp(0, 100) #=> [JPY 100]
73
+ def clamp(min_or_range, max = nil)
74
+ if min_or_range.is_a?(Range)
75
+ raise(ArgumentError, "Either amount range alone or two amounts accepted: #{max}") if max
76
+
77
+ min, max = min_or_range.minmax
78
+ else
79
+ min = min_or_range
80
+ end
81
+ mint(amount.clamp(normalize_boundary(min), normalize_boundary(max)))
82
+ end
57
83
 
58
84
  private
59
85
 
60
- # Initializes a new Money object with the given amount and currency.
61
- # @param amount [Numeric] The monetary amount
62
- # @param currency [Currency] The currency object
63
- def initialize(amount, currency)
64
- @amount = amount
65
- @currency = currency
66
- freeze
86
+ def normalize_boundary(boundary)
87
+ case boundary
88
+ in NilClass | Numeric then boundary
89
+ in Money if same_currency?(boundary) then boundary.amount
90
+ in Money then raise ArgumentError, "oundary currency must be: #{currency_code}"
91
+ else raise ArgumentError, "Boundary must be Numeric or Money #{boundary}"
92
+ end
67
93
  end
68
94
  end
69
95
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mint
2
- # nodoc
4
+ # Money parser
3
5
  class Money
4
6
  # Parses a human-readable money string into a {Money} object.
5
7
  #
@@ -22,7 +24,7 @@ module Mint
22
24
  input = input.strip
23
25
  raise ArgumentError, 'input cannot be empty' if input.empty?
24
26
 
25
- currency = currency ? Mint.currency(currency) : parse_currency(input)
27
+ currency = parse_currency(currency) || parse_currency(input)
26
28
  raise ArgumentError, "Currency [#{currency}] not registered" unless currency
27
29
 
28
30
  amount = currency.normalize_amount(parse_amount(input))
@@ -40,10 +42,8 @@ module Mint
40
42
  # Converts locale-specific decimal/thousand separators into a plain decimal string.
41
43
  def self.normalize_separators(numeric)
42
44
  case [numeric.count(','), numeric.count('.')]
43
- in [0, 0] | [0, 1] # Nothing to normalize (e.g. "1500" or "34.21").
44
- numeric
45
- in [1, 0] # Only one comma: decimal (e.g. 19,99 or 1,234).
46
- numeric.tr(',', '.')
45
+ in [0, 0] | [0, 1] then numeric # Nothing to normalize (e.g. "1500" or "34.21").
46
+ in [1, 0] then numeric.tr(',', '.') # Only one comma: decimal (e.g. 19,99 or 1,234).
47
47
  in [c, p] if c > 1 && p > 1 # Both separators appear multiple times
48
48
  raise ArgumentError, "could not distinguish decimal and thousand separators in '#{numeric}'"
49
49
  in [c, p] if c > 0 && p > 0 # Commas and dots: the rightmost one is the decimal separator.
@@ -58,22 +58,19 @@ module Mint
58
58
  end
59
59
 
60
60
  def self.parse_currency(input)
61
- # Prefer an explicit ISO 4217 code (e.g. "USD 1,234.56") over symbol matching.
62
- code = input[/\b([A-Z]{3})\b/, 1]
63
- if code
64
- currency = Mint.currency(code)
61
+ case input
62
+ when NilClass, Mint::Currency then return input
63
+ when String
64
+ # Prefer an explicit ISO 4217 code (e.g. "USD 1,234.56") over symbol matching.
65
+ currency = Mint.currency(input[/\b([A-Z]+)\b/, 1])
65
66
  return currency if currency
66
- end
67
-
68
- # Fall back to registered symbols, longest first (HK$ before $).
69
- Mint.currency_symbols.each do |symbol, currency|
70
- next if symbol.empty?
71
67
 
72
- return currency if input.include?(symbol)
68
+ # Fall back to registered symbols, longest first (HK$ before $).
69
+ Mint.currency_symbols.each do |symbol, currency|
70
+ return currency if input.include?(symbol)
71
+ end
73
72
  end
74
-
75
- raise ArgumentError,
76
- 'currency could not be detected; pass a currency code as the second argument'
73
+ raise ArgumentError, 'currency could not be detected; pass a currency code as the second argument'
77
74
  end
78
75
 
79
76
  private_class_method :parse_amount, :normalize_separators,
data/lib/minting/money.rb CHANGED
@@ -1,8 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'minting/money/parse'
2
4
  require 'minting/money/allocation'
3
5
  require 'minting/money/arithmetics'
4
6
  require 'minting/money/coercion'
5
7
  require 'minting/money/comparable'
8
+ require 'minting/money/constructors'
6
9
  require 'minting/money/conversion'
7
10
  require 'minting/money/formatting'
8
11
  require 'minting/money/money'
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Root namespace for the Minting library.
2
4
  module Minting
3
5
  # Current version of the Minting gem.
4
- VERSION = '1.3.0'.freeze
6
+ VERSION = '1.5.0'
5
7
  end
data/lib/minting.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'minting/mint'
2
4
  require 'minting/money'
3
5
  require 'minting/version'
data/minting.gemspec CHANGED
@@ -27,6 +27,7 @@ Gem::Specification.new do |s|
27
27
  }
28
28
 
29
29
  s.required_ruby_version = '>= 3.2.0'
30
+ s.add_dependency 'bigdecimal', '>= 4.0'
30
31
 
31
32
  s.files = Dir.glob('{bin,doc,lib}/**/*')
32
33
  s.files += %w[minting.gemspec Rakefile README.md LICENSE]
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.3.0
4
+ version: 1.5.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,10 +34,16 @@ files:
20
34
  - Rakefile
21
35
  - bin/console
22
36
  - bin/setup
37
+ - doc/agents/AGENTS.md
38
+ - doc/agents/copilot-instructions.md
39
+ - doc/agents/gemini_gem_evaluation.md
40
+ - doc/agents/recommendations.md
41
+ - doc/agents/rubocop-issues.md
23
42
  - lib/minting.rb
24
43
  - lib/minting/data/currencies.yaml
25
44
  - lib/minting/mint.rb
26
45
  - lib/minting/mint/currency.rb
46
+ - lib/minting/mint/currency_store.rb
27
47
  - lib/minting/mint/refinements.rb
28
48
  - lib/minting/mint/registry.rb
29
49
  - lib/minting/money.rb
@@ -31,6 +51,7 @@ files:
31
51
  - lib/minting/money/arithmetics.rb
32
52
  - lib/minting/money/coercion.rb
33
53
  - lib/minting/money/comparable.rb
54
+ - lib/minting/money/constructors.rb
34
55
  - lib/minting/money/conversion.rb
35
56
  - lib/minting/money/formatting.rb
36
57
  - lib/minting/money/money.rb