Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions lib/monetize.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,17 @@ def from_numeric(value, currency = Money.default_currency)
fail ArgumentError, "'value' should be a type of Numeric" unless value.is_a?(Numeric)
Money.from_amount(value, currency)
end

def register_currency_symbol(symbol, iso_code)
Monetize::Parser.register_currency_symbol(symbol, iso_code)
end

def unregister_currency_symbol(symbol)
Monetize::Parser.unregister_currency_symbol(symbol)
end

def reset_currency_symbols!
Monetize::Parser.reset_currency_symbols!
end
end
end
73 changes: 53 additions & 20 deletions lib/monetize/parser.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
module Monetize
class Parser
CURRENCY_SYMBOLS = {
MULTIPLIER_SUFFIXES = { 'K' => 3, 'M' => 6, 'B' => 9, 'T' => 12 }
MULTIPLIER_SUFFIXES.default = 0
MULTIPLIER_REGEXP = /^(.*?\d)(#{MULTIPLIER_SUFFIXES.keys.join('|')})\b([^\d]*)$/i

DEFAULT_DECIMAL_MARK = '.'.freeze

@@original_currency_symbols = {
'$' => 'USD',
'€' => 'EUR',
'£' => 'GBP',
Expand All @@ -26,15 +32,38 @@ class Parser
'S$' => 'SGD',
'HK$'=> 'HKD',
'NT$'=> 'TWD',
'₱' => 'PHP',
}
'₱' => 'PHP'
}.freeze

CURRENCY_SYMBOL_REGEX = /(?<![A-Z])(#{CURRENCY_SYMBOLS.keys.map { |key| Regexp.escape(key) }.join('|')})(?![A-Z])/i
MULTIPLIER_SUFFIXES = { 'K' => 3, 'M' => 6, 'B' => 9, 'T' => 12 }
MULTIPLIER_SUFFIXES.default = 0
MULTIPLIER_REGEXP = Regexp.new(format('^(.*?\d)(%s)\b([^\d]*)$', MULTIPLIER_SUFFIXES.keys.join('|')), 'i')
class << self
def currency_symbols
@@currency_symbols ||= @@original_currency_symbols.dup
end

DEFAULT_DECIMAL_MARK = '.'.freeze
def register_currency_symbol(symbol, iso_code)
currency_symbols[symbol] = iso_code

reset_currency_symbol_regex
end

def unregister_currency_symbol(symbol)
currency_symbols.delete(symbol)
reset_currency_symbol_regex
end

def reset_currency_symbols!
@@currency_symbols = @@original_currency_symbols.dup
reset_currency_symbol_regex
end

def currency_symbol_regex
@@currency_symbol_regex ||= /(?<![A-Z])(#{currency_symbols.keys.map { |key| Regexp.escape(key) }.join('|')})(?![A-Z])/i
end

def reset_currency_symbol_regex
@@currency_symbol_regex = nil
end
end

def initialize(input, fallback_currency = Money.default_currency, options = {})
@input = input.to_s.strip
Expand Down Expand Up @@ -73,14 +102,23 @@ def to_big_decimal(value)
attr_reader :input, :fallback_currency, :options

def parse_currency
computed_currency = nil
computed_currency = input[/[A-Z]{2,3}/]
computed_currency = nil unless Money::Currency.find(computed_currency)
computed_currency ||= compute_currency if assume_from_symbol?
computed_currency = compute_currency_from_iso_code
computed_currency ||= compute_currency_from_symbol if assume_from_symbol?
computed_currency ||= fallback_currency || Money.default_currency

raise Money::Currency::UnknownCurrency unless computed_currency

found = computed_currency || fallback_currency || Money.default_currency
raise Money::Currency::UnknownCurrency unless found
found
computed_currency
end

def compute_currency_from_iso_code
Money::Currency.find(input[/[A-Z]{2,3}/])
end

def compute_currency_from_symbol
match = input.match(self.class.currency_symbol_regex)

self.class.currency_symbols[match.to_s] if match
end

def assume_from_symbol?
Expand All @@ -99,11 +137,6 @@ def apply_sign(negative, amount)
negative ? amount * -1 : amount
end

def compute_currency
match = input.match(CURRENCY_SYMBOL_REGEX)
CURRENCY_SYMBOLS[match.to_s] if match
end

def extract_major_minor(num, currency)
used_delimiters = num.scan(/[^\d]/).uniq

Expand Down
65 changes: 63 additions & 2 deletions spec/monetize_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
Monetize.assume_from_symbol = false
end

Monetize::Parser::CURRENCY_SYMBOLS.each_pair do |symbol, iso_code|
Monetize::Parser.currency_symbols.each_pair do |symbol, iso_code|
context iso_code do
let(:currency) { Money::Currency.find(iso_code) }
let(:amount) { 5_95 }
Expand Down Expand Up @@ -132,6 +132,67 @@
expect(Monetize.parse('kr9.99')).to eq Money.new(999, 'USD')
end
end

context "registered custom symbols and unregistering" do
after { Monetize.reset_currency_symbols! }

it "parses registered custom currency symbol" do
Monetize.register_currency_symbol("S/", "PEN")

expect(Monetize.parse("S/19.90")).to eq Money.new(1990, "PEN")
end

it "overrides existing currency symbol when registering" do
Monetize.register_currency_symbol("€", "USD")

expect(Monetize.parse("€19.90")).to eq Money.new(1990, "USD")
end

it "registers currency symbol before currency_symbols is accessed" do
Monetize::Parser.class_variable_set(:@@currency_symbols, nil)

expect {
Monetize.register_currency_symbol("S/", "PEN")
}.not_to raise_error

expect(Monetize.parse("S/19.90")).to eq Money.new(1990, "PEN")
end

it "unregisters custom currency symbol" do
Monetize.register_currency_symbol("S/", "PEN")

expect(Monetize.parse("S/19.90")).to eq Money.new(1990, "PEN")

Monetize.unregister_currency_symbol("S/")

expect(Monetize.parse("S/19.90")).to eq Money.new(1990, "USD")
end

it "unregisters existing currency symbol" do
Monetize.unregister_currency_symbol("€")

expect(Monetize.parse("€19.90")).to eq Money.new(1990, "USD")
end

it "unregisters non-registered currency symbol without errors" do
expect {
Monetize.unregister_currency_symbol("XYZ")
}.not_to raise_error
end

it "resets currency symbols to original set" do
Monetize.register_currency_symbol("S/", "PEN")
Monetize.unregister_currency_symbol("€")

expect(Monetize.parse("S/19.90")).to eq Money.new(1990, "PEN")
expect(Monetize.parse("€19.90")).to eq Money.new(1990, "USD")

Monetize.reset_currency_symbols!

expect(Monetize.parse("€19.90")).to eq Money.new(1990, "EUR")
expect(Monetize.parse("S/19.90")).to eq Money.new(1990, "USD")
end
end
end

context 'opted out' do
Expand Down Expand Up @@ -172,7 +233,7 @@
end

it 'parses currency not in CURRENCY_SYMBOLS given as ISO code' do
expect(Monetize::Parser::CURRENCY_SYMBOLS).to_not have_value('DKK')
expect(Monetize::Parser.currency_symbols).to_not have_value('DKK')
expect('20.00 DKK'.to_money).to eq Money.new(20_00, 'DKK')
end

Expand Down