meplato-money 0.1.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.
- data/CHANGELOG.md +18 -0
- data/LICENSE +21 -0
- data/README.md +16 -0
- data/lib/money.rb +15 -0
- data/lib/money/core_ext.rb +2 -0
- data/lib/money/core_ext/string_ext.rb +5 -0
- data/lib/money/core_ext/try.rb +9 -0
- data/lib/money/currencies.rb +110 -0
- data/lib/money/fixed_bank.rb +28 -0
- data/lib/money/formats.rb +2490 -0
- data/lib/money/meplato_bank.rb +52 -0
- data/lib/money/money.rb +138 -0
- data/lib/money/money_formatter.rb +67 -0
- metadata +95 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
require 'thread'
|
5
|
+
require 'open-uri'
|
6
|
+
require 'rubygems' if RUBY_VERSION <= '1.9'
|
7
|
+
if defined?(Yajl)
|
8
|
+
require 'yajl/json_gem'
|
9
|
+
else
|
10
|
+
require 'json'
|
11
|
+
end
|
12
|
+
|
13
|
+
module Money
|
14
|
+
class MeplatoBank
|
15
|
+
include Singleton
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@rates = {}
|
19
|
+
@mutex = Mutex.new
|
20
|
+
load_rates
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_rate(source, target, rate)
|
24
|
+
@mutex.synchronize do
|
25
|
+
add_rate_synchronized(source, target, rate)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_rate(source, target)
|
30
|
+
@mutex.synchronize do
|
31
|
+
@rates["#{source}_#{target}".upcase]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def add_rate_synchronized(source, target, rate)
|
38
|
+
@rates["#{source}_#{target}".upcase] = rate
|
39
|
+
end
|
40
|
+
|
41
|
+
def load_rates
|
42
|
+
latest = JSON.parse(open("http://services.meplato.com/rates/latest?format=json").read)
|
43
|
+
@mutex.synchronize do
|
44
|
+
latest.each do |r|
|
45
|
+
add_rate_synchronized(r['source'], r['target'], r['rate'])
|
46
|
+
add_rate_synchronized(r['target'], r['source'], 1.0 / r['rate'])
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
data/lib/money/money.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'money/currencies'
|
4
|
+
require 'money/formats'
|
5
|
+
|
6
|
+
module Money
|
7
|
+
|
8
|
+
class UnknownCurrency < StandardError; end
|
9
|
+
class UnknownRate < StandardError; end
|
10
|
+
|
11
|
+
# Money::Money is the central place for working with
|
12
|
+
# monetary values. You can create a new +Money+ instance
|
13
|
+
# by one of these methods:
|
14
|
+
#
|
15
|
+
# Initializing with amount and currency:
|
16
|
+
#
|
17
|
+
# m = Money::Money.new(1.99, "EUR")
|
18
|
+
#
|
19
|
+
# Initializing with amount only.
|
20
|
+
# The +default_currency+ will then be used:
|
21
|
+
#
|
22
|
+
# m = Money::Money.new(1.99)
|
23
|
+
#
|
24
|
+
class Money
|
25
|
+
include Comparable
|
26
|
+
|
27
|
+
attr_reader :amount, :currency, :bank
|
28
|
+
|
29
|
+
class << self
|
30
|
+
attr_accessor :default_bank
|
31
|
+
attr_accessor :default_currency
|
32
|
+
attr_accessor :default_locale
|
33
|
+
|
34
|
+
def same_currency?(source, target)
|
35
|
+
source.upcase == target.upcase
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
self.default_bank = MeplatoBank.instance
|
40
|
+
self.default_currency = "USD"
|
41
|
+
self.default_locale = "en"
|
42
|
+
|
43
|
+
def initialize(amount, currency = Money.default_currency, bank = Money.default_bank)
|
44
|
+
@amount, @currency, @bank = amount, currency || Money.default_currency, bank || Money.default_bank
|
45
|
+
raise UnknownCurrency unless CURRENCIES[@currency]
|
46
|
+
end
|
47
|
+
|
48
|
+
def positive?
|
49
|
+
@amount && @amount > 0
|
50
|
+
end
|
51
|
+
|
52
|
+
def negative?
|
53
|
+
@amount && @amount < 0
|
54
|
+
end
|
55
|
+
|
56
|
+
def zero?
|
57
|
+
@amount && @amount == 0
|
58
|
+
end
|
59
|
+
|
60
|
+
def nan?
|
61
|
+
@amount.nil?
|
62
|
+
end
|
63
|
+
|
64
|
+
def ==(other)
|
65
|
+
self.amount == other.amount && Money.same_currency?(self.currency, other.currency)
|
66
|
+
end
|
67
|
+
|
68
|
+
def <=>(other)
|
69
|
+
if Money.same_currency?(@currency, other.currency)
|
70
|
+
@amount <=> other.amount
|
71
|
+
else
|
72
|
+
@amount <=> other.exchange_to(@currency).amount
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def +(other)
|
77
|
+
if Money.same_currency?(@currency, other.currency)
|
78
|
+
Money.new(@amount + other.amount, @currency)
|
79
|
+
else
|
80
|
+
Money.new(@amount + other.exchange_to(@currency).amount, @currency)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def -(other)
|
85
|
+
if Money.same_currency?(@currency, other.currency)
|
86
|
+
Money.new(@amount - other.amount, @currency)
|
87
|
+
else
|
88
|
+
Money.new(@amount - other.exchange_to(@currency).amount, @currency)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def exchange_to(target)
|
93
|
+
unless Money.same_currency?(@currency, target)
|
94
|
+
rate = self.bank.get_rate(@currency, target)
|
95
|
+
if !rate
|
96
|
+
raise UnknownRate, "No conversion from #{@currency} to #{target}"
|
97
|
+
end
|
98
|
+
Money.new(@amount * rate, target)
|
99
|
+
else
|
100
|
+
self
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def as_euro
|
105
|
+
self.exchange_to("EUR")
|
106
|
+
end
|
107
|
+
|
108
|
+
def format(options = {})
|
109
|
+
defaults = {
|
110
|
+
:locale => self.class.default_locale,
|
111
|
+
:bank => @bank,
|
112
|
+
:currency => @currency
|
113
|
+
}
|
114
|
+
MoneyFormatter.new(self, defaults.merge(options)).to_s
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_s(options = {})
|
118
|
+
self.format(options)
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.parse(value, currency, options = {})
|
122
|
+
return Money.new(0, currency, options) if value.nil?
|
123
|
+
opts = {
|
124
|
+
:locale => default_locale,
|
125
|
+
:bank => default_bank,
|
126
|
+
:currency => currency || default_currency
|
127
|
+
}.merge(options)
|
128
|
+
format = FORMATS[opts[:locale]]
|
129
|
+
# TODO there's a special case e.g. "($12,345,678.90)" that has to be converted to -12345678.90
|
130
|
+
s = value.gsub(/[^0-9\-#{format[:decimal_sep]}]/, '')
|
131
|
+
s.gsub!(format[:decimal_sep], '.')
|
132
|
+
Money.new(s.to_f, opts[:currency], opts)
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'money/currencies'
|
4
|
+
require 'money/formats'
|
5
|
+
|
6
|
+
module Money
|
7
|
+
# Money::MoneyFormatter persues locale-specific
|
8
|
+
# formatting of a +Money+ instance.
|
9
|
+
#
|
10
|
+
# m = Money::Money.new(100, "EUR")
|
11
|
+
# puts MoneyFormatter.new(m, :locale => "de-DE")
|
12
|
+
#
|
13
|
+
# or as a shortcut:
|
14
|
+
#
|
15
|
+
# m = Money::Money.new(100, "EUR")
|
16
|
+
# puts m.format(:locale => "de-DE")
|
17
|
+
#
|
18
|
+
class MoneyFormatter
|
19
|
+
|
20
|
+
attr_reader :money, :locale, :bank
|
21
|
+
|
22
|
+
def initialize(money, options = {})
|
23
|
+
@money = money
|
24
|
+
@locale = options[:locale] || Money.default_locale
|
25
|
+
@locale.gsub!(/_/, '-')
|
26
|
+
@bank = options[:bank] || Money.default_bank
|
27
|
+
@format = FORMATS[@locale]
|
28
|
+
@currency_symbol = CURRENCIES[@money.currency][:symbol]
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
unless @money.nan?
|
33
|
+
pattern = @money.positive? || @money.zero? ? @format[:pos_pattern] : @format[:neg_pattern]
|
34
|
+
precision = CURRENCIES[@money.currency][:decimal_digits] # @format[:decimal_digits]
|
35
|
+
# divisor = 10 ** precision
|
36
|
+
#if @money.amount.round(2) == 1.77
|
37
|
+
# puts ""
|
38
|
+
# puts "Money = #{@money.amount} #{@money.currency}"
|
39
|
+
# puts " Prec: #{precision}"
|
40
|
+
# puts " Divi: #{divisor}"
|
41
|
+
#end
|
42
|
+
number = number_with_precision(
|
43
|
+
@money.amount.to_f.abs, # / divisor,
|
44
|
+
precision,
|
45
|
+
@format[:decimal_sep],
|
46
|
+
@format[:group_sep])
|
47
|
+
pattern.gsub("n", number).gsub("$", @currency_symbol)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Shamelessly stolen from actionpack
|
54
|
+
def number_with_precision(number, precision, separator, delimiter)
|
55
|
+
begin
|
56
|
+
rounded_number = (Float(number) * (10 ** precision)).round.to_f / 10 ** precision
|
57
|
+
number = "%01.#{precision}f" % rounded_number
|
58
|
+
parts = number.to_s.split('.')
|
59
|
+
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
|
60
|
+
parts.join(separator)
|
61
|
+
rescue
|
62
|
+
number
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: meplato-money
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.3
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Oliver Eilhard
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-10-12 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: &70300784862420 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70300784862420
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rdoc
|
27
|
+
requirement: &70300784861960 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2.5'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70300784861960
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
requirement: &70300784861500 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0.8'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70300784861500
|
47
|
+
description: Working with monetary values as well as performing currency exchange.
|
48
|
+
email:
|
49
|
+
- oliver.eilhard@gmail.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files:
|
53
|
+
- CHANGELOG.md
|
54
|
+
- LICENSE
|
55
|
+
- README.md
|
56
|
+
files:
|
57
|
+
- lib/money.rb
|
58
|
+
- lib/money/core_ext.rb
|
59
|
+
- lib/money/core_ext/string_ext.rb
|
60
|
+
- lib/money/core_ext/try.rb
|
61
|
+
- lib/money/currencies.rb
|
62
|
+
- lib/money/fixed_bank.rb
|
63
|
+
- lib/money/formats.rb
|
64
|
+
- lib/money/meplato_bank.rb
|
65
|
+
- lib/money/money.rb
|
66
|
+
- lib/money/money_formatter.rb
|
67
|
+
- CHANGELOG.md
|
68
|
+
- LICENSE
|
69
|
+
- README.md
|
70
|
+
homepage: http://github.com/meplato/money
|
71
|
+
licenses: []
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options:
|
74
|
+
- --charset=UTF-8
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: 1.3.6
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 1.8.10
|
92
|
+
signing_key:
|
93
|
+
specification_version: 3
|
94
|
+
summary: Working with monetary values as well as performing currency exchange.
|
95
|
+
test_files: []
|