tiny-sharp-rb 0.0.1

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.
@@ -0,0 +1,146 @@
1
+ {
2
+ "bch": {
3
+ "priority": 100,
4
+ "iso_code": "BCH",
5
+ "name": "Bitcoin Cash",
6
+ "symbol": "₿",
7
+ "disambiguate_symbol": "₿CH",
8
+ "alternate_symbols": ["BCH"],
9
+ "subunit": "Satoshi",
10
+ "subunit_to_unit": 100000000,
11
+ "symbol_first": false,
12
+ "format": "%n %u",
13
+ "html_entity": "₿",
14
+ "decimal_mark": ".",
15
+ "thousands_separator": ",",
16
+ "iso_numeric": "",
17
+ "smallest_denomination": 1
18
+ },
19
+ "btc": {
20
+ "priority": 100,
21
+ "iso_code": "BTC",
22
+ "name": "Bitcoin",
23
+ "symbol": "₿",
24
+ "alternate_symbols": [],
25
+ "subunit": "Satoshi",
26
+ "subunit_to_unit": 100000000,
27
+ "symbol_first": true,
28
+ "html_entity": "₿",
29
+ "decimal_mark": ".",
30
+ "thousands_separator": ",",
31
+ "iso_numeric": "",
32
+ "smallest_denomination": 1
33
+ },
34
+ "jep": {
35
+ "priority": 100,
36
+ "iso_code": "JEP",
37
+ "name": "Jersey Pound",
38
+ "symbol": "£",
39
+ "disambiguate_symbol": "JEP",
40
+ "alternate_symbols": [],
41
+ "subunit": "Penny",
42
+ "subunit_to_unit": 100,
43
+ "symbol_first": true,
44
+ "html_entity": "£",
45
+ "decimal_mark": ".",
46
+ "thousands_separator": ",",
47
+ "iso_numeric": "",
48
+ "smallest_denomination": 1
49
+ },
50
+ "ggp": {
51
+ "priority": 100,
52
+ "iso_code": "GGP",
53
+ "name": "Guernsey Pound",
54
+ "symbol": "£",
55
+ "disambiguate_symbol": "GGP",
56
+ "alternate_symbols": [],
57
+ "subunit": "Penny",
58
+ "subunit_to_unit": 100,
59
+ "symbol_first": true,
60
+ "html_entity": "£",
61
+ "decimal_mark": ".",
62
+ "thousands_separator": ",",
63
+ "iso_numeric": "",
64
+ "smallest_denomination": 1
65
+ },
66
+ "imp": {
67
+ "priority": 100,
68
+ "iso_code": "IMP",
69
+ "name": "Isle of Man Pound",
70
+ "symbol": "£",
71
+ "disambiguate_symbol": "IMP",
72
+ "alternate_symbols": ["M£"],
73
+ "subunit": "Penny",
74
+ "subunit_to_unit": 100,
75
+ "symbol_first": true,
76
+ "html_entity": "£",
77
+ "decimal_mark": ".",
78
+ "thousands_separator": ",",
79
+ "iso_numeric": "",
80
+ "smallest_denomination": 1
81
+ },
82
+ "xfu": {
83
+ "priority": 100,
84
+ "iso_code": "XFU",
85
+ "name": "UIC Franc",
86
+ "symbol": "",
87
+ "disambiguate_symbol": "XFU",
88
+ "alternate_symbols": [],
89
+ "subunit": "",
90
+ "subunit_to_unit": 100,
91
+ "symbol_first": true,
92
+ "html_entity": "",
93
+ "decimal_mark": ".",
94
+ "thousands_separator": ",",
95
+ "iso_numeric": "",
96
+ "smallest_denomination": ""
97
+ },
98
+ "gbx": {
99
+ "priority": 100,
100
+ "iso_code": "GBX",
101
+ "name": "British Penny",
102
+ "symbol": "",
103
+ "disambiguate_symbol": "GBX",
104
+ "alternate_symbols": [],
105
+ "subunit": "",
106
+ "subunit_to_unit": 1,
107
+ "symbol_first": true,
108
+ "html_entity": "",
109
+ "decimal_mark": ".",
110
+ "thousands_separator": ",",
111
+ "iso_numeric": "",
112
+ "smallest_denomination": 1
113
+ },
114
+ "cnh": {
115
+ "priority": 100,
116
+ "iso_code": "CNH",
117
+ "name": "Chinese Renminbi Yuan Offshore",
118
+ "symbol": "¥",
119
+ "disambiguate_symbol": "CNH",
120
+ "alternate_symbols": ["CN¥", "元", "CN元"],
121
+ "subunit": "Fen",
122
+ "subunit_to_unit": 100,
123
+ "symbol_first": true,
124
+ "html_entity": "¥",
125
+ "decimal_mark": ".",
126
+ "thousands_separator": ",",
127
+ "iso_numeric": "",
128
+ "smallest_denomination": 1
129
+ },
130
+ "usdc": {
131
+ "priority": 100,
132
+ "iso_code": "USDC",
133
+ "name": "USD Coin",
134
+ "symbol": "USDC",
135
+ "disambiguate_symbol": "USDC",
136
+ "alternate_symbols": [],
137
+ "subunit": "Cent",
138
+ "subunit_to_unit": 1000000,
139
+ "symbol_first": false,
140
+ "html_entity": "$",
141
+ "decimal_mark": ".",
142
+ "thousands_separator": ",",
143
+ "iso_numeric": "",
144
+ "smallest_denomination": 1
145
+ }
146
+ }
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Money
4
+ # Provides classes that aid in the ability of exchange one currency with
5
+ # another.
6
+ module Bank
7
+
8
+ # The lowest Money::Bank error class.
9
+ # All Money::Bank errors should inherit from it.
10
+ class Error < StandardError
11
+ end
12
+
13
+ # Raised when the bank doesn't know about the conversion rate
14
+ # for specified currencies.
15
+ class UnknownRate < Error
16
+ end
17
+
18
+
19
+ # Money::Bank::Base is the basic interface for creating a money exchange
20
+ # object, also called Bank.
21
+ #
22
+ # A Bank is responsible for storing exchange rates, take a Money object as
23
+ # input and returns the corresponding Money object converted into an other
24
+ # currency.
25
+ #
26
+ # This class exists for aiding in the creating of other classes to exchange
27
+ # money between different currencies. When creating a subclass you will
28
+ # need to implement the following methods to exchange money between
29
+ # currencies:
30
+ #
31
+ # - #exchange_with(Money) #=> Money
32
+ #
33
+ # See Money::Bank::VariableExchange for a real example.
34
+ #
35
+ # Also, you can extend +Money::Bank::VariableExchange+ instead of
36
+ # +Money::Bank::Base+ if your bank implementation needs to store rates
37
+ # internally.
38
+ #
39
+ # @abstract Subclass and override +#exchange_with+ to implement a custom
40
+ # +Money::Bank+ class. You can also override +#setup+ instead of
41
+ # +#initialize+ to setup initial variables, etc.
42
+ class Base
43
+
44
+ # Returns the singleton instance of the Base bank.
45
+ #
46
+ # @return [Money::Bank::Base]
47
+ def self.instance
48
+ @singleton ||= self.new
49
+ end
50
+
51
+ # The rounding method to use when exchanging rates.
52
+ #
53
+ # @return [Proc]
54
+ attr_reader :rounding_method
55
+
56
+ # Initializes a new +Money::Bank::Base+ object. An optional block can be
57
+ # passed to dictate the rounding method that +#exchange_with+ can use.
58
+ #
59
+ # @yield [n] Optional block to use when rounding after exchanging one
60
+ # currency for another.
61
+ # @yieldparam [Float] n The resulting float after exchanging one currency
62
+ # for another.
63
+ # @yieldreturn [Integer]
64
+ #
65
+ # @return [Money::Bank::Base]
66
+ #
67
+ # @example
68
+ # Money::Bank::Base.new #=> #<Money::Bank::Base @rounding_method=nil>
69
+ # Money::Bank::Base.new {|n|
70
+ # n.floor
71
+ # } #=> #<Money::Bank::Base @round_method=#<Proc>>
72
+ def initialize(&block)
73
+ @rounding_method = block
74
+ setup
75
+ end
76
+
77
+ # Called after initialize. Subclasses can use this method to setup
78
+ # variables, etc that they normally would in +#initialize+.
79
+ #
80
+ # @abstract Subclass and override +#setup+ to implement a custom
81
+ # +Money::Bank+ class.
82
+ #
83
+ # @return [self]
84
+ def setup
85
+ end
86
+
87
+ # Exchanges the given +Money+ object to a new +Money+ object in
88
+ # +to_currency+.
89
+ #
90
+ # @abstract Subclass and override +#exchange_with+ to implement a custom
91
+ # +Money::Bank+ class.
92
+ #
93
+ # @raise NotImplementedError
94
+ #
95
+ # @param [Money] from The +Money+ object to exchange from.
96
+ # @param [Money::Currency, String, Symbol] to_currency The currency
97
+ # string or object to exchange to.
98
+ # @yield [n] Optional block to use to round the result after making
99
+ # the exchange.
100
+ # @yieldparam [Float] n The result after exchanging from one currency to
101
+ # the other.
102
+ # @yieldreturn [Integer]
103
+ #
104
+ # @return [Money]
105
+ def exchange_with(from, to_currency, &block)
106
+ raise NotImplementedError, "#exchange_with must be implemented"
107
+ end
108
+
109
+ # Given two currency strings or object, checks whether they're both the
110
+ # same currency. Return +true+ if the currencies are the same, +false+
111
+ # otherwise.
112
+ #
113
+ # @param [Money::Currency, String, Symbol] currency1 The first currency
114
+ # to compare.
115
+ # @param [Money::Currency, String, Symbol] currency2 The second currency
116
+ # to compare.
117
+ #
118
+ # @return [Boolean]
119
+ #
120
+ # @example
121
+ # same_currency?("usd", "USD") #=> true
122
+ # same_currency?("usd", "EUR") #=> false
123
+ # same_currency?("usd", Currency.new("USD")) #=> true
124
+ # same_currency?("usd", "USD") #=> true
125
+ def same_currency?(currency1, currency2)
126
+ Currency.wrap(currency1) == Currency.wrap(currency2)
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'money/bank/base'
4
+
5
+ class Money
6
+ module Bank
7
+ # Raised when trying to exchange currencies
8
+ class DifferentCurrencyError < Error; end
9
+
10
+ # Class to ensure client code is operating in a single currency
11
+ # by raising if an exchange attempts to happen.
12
+ #
13
+ # This is useful when an application uses multiple currencies but
14
+ # it usually deals with only one currency at a time so any arithmetic
15
+ # where exchanges happen are erroneous. Using this as the default bank
16
+ # means that that these mistakes don't silently do the wrong thing.
17
+ class SingleCurrency < Base
18
+
19
+ # Raises a DifferentCurrencyError to remove possibility of accidentally
20
+ # exchanging currencies
21
+ def exchange_with(from, to_currency, &block)
22
+ raise DifferentCurrencyError, "No exchanging of currencies allowed: #{from} #{from.currency} to #{to_currency}"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,285 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'money/bank/base'
4
+ require 'money/rates_store/memory'
5
+ require 'json'
6
+ require 'yaml'
7
+
8
+ class Money
9
+ module Bank
10
+ # Thrown when an unknown rate format is requested.
11
+ class UnknownRateFormat < StandardError; end
12
+
13
+ # Class for aiding in exchanging money between different currencies. By
14
+ # default, the +Money+ class uses an object of this class (accessible
15
+ # through +Money#bank+) for performing currency exchanges.
16
+ #
17
+ # By default, +Money::Bank::VariableExchange+ has no knowledge about
18
+ # conversion rates. One must manually specify them with +add_rate+, after
19
+ # which one can perform exchanges with +#exchange_with+.
20
+ #
21
+ # Exchange rates are stored in memory using +Money::RatesStore::Memory+ by default.
22
+ # Pass custom rates stores for other types of storage (file, database, etc)
23
+ #
24
+ # @example
25
+ # bank = Money::Bank::VariableExchange.new
26
+ # bank.add_rate("USD", "CAD", 1.24515)
27
+ # bank.add_rate("CAD", "USD", 0.803115)
28
+ #
29
+ # c1 = Money.new(100_00, "USD")
30
+ # c2 = Money.new(100_00, "CAD")
31
+ #
32
+ # # Exchange 100 USD to CAD:
33
+ # bank.exchange_with(c1, "CAD") #=> #<Money fractional:12451 currency:CAD>
34
+ #
35
+ # # Exchange 100 CAD to USD:
36
+ # bank.exchange_with(c2, "USD") #=> #<Money fractional:8031 currency:USD>
37
+ #
38
+ # # With custom exchange rates storage
39
+ # redis_store = MyCustomRedisStore.new(host: 'localhost:6379')
40
+ # bank = Money::Bank::VariableExchange.new(redis_store)
41
+ # # Store rates in redis
42
+ # bank.add_rate 'USD', 'CAD', 0.98
43
+ # # Get rate from redis
44
+ # bank.get_rate 'USD', 'CAD'
45
+ class VariableExchange < Base
46
+
47
+ attr_reader :mutex
48
+
49
+ # Available formats for importing/exporting rates.
50
+ RATE_FORMATS = [:json, :ruby, :yaml].freeze
51
+ SERIALIZER_SEPARATOR = '_TO_'.freeze
52
+ FORMAT_SERIALIZERS = {json: JSON, ruby: Marshal, yaml: YAML}.freeze
53
+
54
+ # Initializes a new +Money::Bank::VariableExchange+ object.
55
+ # It defaults to using an in-memory, thread safe store instance for
56
+ # storing exchange rates.
57
+ #
58
+ # @param [RateStore] st An exchange rate store, used to persist exchange rate pairs.
59
+ # @yield [n] Optional block to use when rounding after exchanging one
60
+ # currency for another. See +Money::bank::base+
61
+ def initialize(st = Money::RatesStore::Memory.new, &block)
62
+ @store = st
63
+ super(&block)
64
+ end
65
+
66
+ def store
67
+ @store.is_a?(String) ? Object.const_get(@store) : @store
68
+ end
69
+
70
+ def marshal_dump
71
+ [store.marshal_dump, @rounding_method]
72
+ end
73
+
74
+ def marshal_load(arr)
75
+ store_info = arr[0]
76
+ @store = store_info.shift.new(*store_info)
77
+ @rounding_method = arr[1]
78
+ end
79
+
80
+ # Exchanges the given +Money+ object to a new +Money+ object in
81
+ # +to_currency+.
82
+ #
83
+ # @param [Money] from
84
+ # The +Money+ object to exchange.
85
+ # @param [Currency, String, Symbol] to_currency
86
+ # The currency to exchange to.
87
+ #
88
+ # @yield [n] Optional block to use when rounding after exchanging one
89
+ # currency for another.
90
+ # @yieldparam [Float] n The resulting float after exchanging one currency
91
+ # for another.
92
+ # @yieldreturn [Integer]
93
+ #
94
+ # @return [Money]
95
+ #
96
+ # @raise +Money::Bank::UnknownRate+ if the conversion rate is unknown.
97
+ #
98
+ # @example
99
+ # bank = Money::Bank::VariableExchange.new
100
+ # bank.add_rate("USD", "CAD", 1.24515)
101
+ # bank.add_rate("CAD", "USD", 0.803115)
102
+ #
103
+ # c1 = Money.new(100_00, "USD")
104
+ # c2 = Money.new(100_00, "CAD")
105
+ #
106
+ # # Exchange 100 USD to CAD:
107
+ # bank.exchange_with(c1, "CAD") #=> #<Money fractional:12451 currency:CAD>
108
+ #
109
+ # # Exchange 100 CAD to USD:
110
+ # bank.exchange_with(c2, "USD") #=> #<Money fractional:8031 currency:USD>
111
+ def exchange_with(from, to_currency, &block)
112
+ to_currency = Currency.wrap(to_currency)
113
+ if from.currency == to_currency
114
+ from
115
+ else
116
+ if rate = get_rate(from.currency, to_currency)
117
+ fractional = calculate_fractional(from, to_currency)
118
+ from.dup_with(
119
+ fractional: exchange(fractional, rate, &block),
120
+ currency: to_currency,
121
+ bank: self
122
+ )
123
+ else
124
+ raise UnknownRate, "No conversion rate known for '#{from.currency.iso_code}' -> '#{to_currency}'"
125
+ end
126
+ end
127
+ end
128
+
129
+ def calculate_fractional(from, to_currency)
130
+ BigDecimal(from.fractional.to_s) / (
131
+ BigDecimal(from.currency.subunit_to_unit.to_s) /
132
+ BigDecimal(to_currency.subunit_to_unit.to_s)
133
+ )
134
+ end
135
+
136
+ def exchange(fractional, rate, &block)
137
+ ex = fractional * BigDecimal(rate.to_s)
138
+ if block_given?
139
+ yield ex
140
+ elsif @rounding_method
141
+ @rounding_method.call(ex)
142
+ else
143
+ ex
144
+ end
145
+ end
146
+
147
+ # Registers a conversion rate and returns it (uses +#set_rate+).
148
+ # Delegates to +Money::RatesStore::Memory+
149
+ #
150
+ # @param [Currency, String, Symbol] from Currency to exchange from.
151
+ # @param [Currency, String, Symbol] to Currency to exchange to.
152
+ # @param [Numeric] rate Rate to use when exchanging currencies.
153
+ #
154
+ # @return [Numeric]
155
+ #
156
+ # @example
157
+ # bank = Money::Bank::VariableExchange.new
158
+ # bank.add_rate("USD", "CAD", 1.24515)
159
+ # bank.add_rate("CAD", "USD", 0.803115)
160
+ def add_rate(from, to, rate)
161
+ set_rate(from, to, rate)
162
+ end
163
+
164
+ # Set the rate for the given currencies.
165
+ # access.
166
+ # Delegates to +Money::RatesStore::Memory+
167
+ #
168
+ # @param [Currency, String, Symbol] from Currency to exchange from.
169
+ # @param [Currency, String, Symbol] to Currency to exchange to.
170
+ # @param [Numeric] rate Rate to use when exchanging currencies.
171
+ # @param [Hash] opts Options hash to set special parameters. Backwards compatibility only.
172
+ #
173
+ # @return [Numeric]
174
+ #
175
+ # @example
176
+ # bank = Money::Bank::VariableExchange.new
177
+ # bank.set_rate("USD", "CAD", 1.24515)
178
+ # bank.set_rate("CAD", "USD", 0.803115)
179
+ def set_rate(from, to, rate, opts = {})
180
+ store.add_rate(Currency.wrap(from).iso_code, Currency.wrap(to).iso_code, rate)
181
+ end
182
+
183
+ # Retrieve the rate for the given currencies.
184
+ # data access.
185
+ # Delegates to +Money::RatesStore::Memory+
186
+ #
187
+ # @param [Currency, String, Symbol] from Currency to exchange from.
188
+ # @param [Currency, String, Symbol] to Currency to exchange to.
189
+ # @param [Hash] opts Options hash to set special parameters. Backwards compatibility only.
190
+ #
191
+ # @return [Numeric]
192
+ #
193
+ # @example
194
+ # bank = Money::Bank::VariableExchange.new
195
+ # bank.set_rate("USD", "CAD", 1.24515)
196
+ # bank.set_rate("CAD", "USD", 0.803115)
197
+ #
198
+ # bank.get_rate("USD", "CAD") #=> 1.24515
199
+ # bank.get_rate("CAD", "USD") #=> 0.803115
200
+ def get_rate(from, to, opts = {})
201
+ store.get_rate(Currency.wrap(from).iso_code, Currency.wrap(to).iso_code)
202
+ end
203
+
204
+ # Return the known rates as a string in the format specified. If +file+
205
+ # is given will also write the string out to the file specified.
206
+ # Available formats are +:json+, +:ruby+ and +:yaml+.
207
+ #
208
+ # @param [Symbol] format Request format for the resulting string.
209
+ # @param [String] file Optional file location to write the rates to.
210
+ # @param [Hash] opts Options hash to set special parameters. Backwards compatibility only.
211
+ #
212
+ # @return [String]
213
+ #
214
+ # @raise +Money::Bank::UnknownRateFormat+ if format is unknown.
215
+ #
216
+ # @example
217
+ # bank = Money::Bank::VariableExchange.new
218
+ # bank.set_rate("USD", "CAD", 1.24515)
219
+ # bank.set_rate("CAD", "USD", 0.803115)
220
+ #
221
+ # s = bank.export_rates(:json)
222
+ # s #=> "{\"USD_TO_CAD\":1.24515,\"CAD_TO_USD\":0.803115}"
223
+ def export_rates(format, file = nil, opts = {})
224
+ raise Money::Bank::UnknownRateFormat unless RATE_FORMATS.include?(format)
225
+
226
+ store.transaction do
227
+ s = FORMAT_SERIALIZERS[format].dump(rates)
228
+
229
+ unless file.nil?
230
+ File.open(file, "w") {|f| f.write(s) }
231
+ end
232
+
233
+ s
234
+ end
235
+ end
236
+
237
+ # This should be deprecated.
238
+ def rates
239
+ store.each_rate.each_with_object({}) do |(from,to,rate),hash|
240
+ hash[[from, to].join(SERIALIZER_SEPARATOR)] = rate
241
+ end
242
+ end
243
+
244
+ # Loads rates provided in +s+ given the specified format. Available
245
+ # formats are +:json+, +:ruby+ and +:yaml+.
246
+ # Delegates to +Money::RatesStore::Memory+
247
+ #
248
+ # @param [Symbol] format The format of +s+.
249
+ # @param [String] s The rates string.
250
+ # @param [Hash] opts Options hash to set special parameters. Backwards compatibility only.
251
+ #
252
+ # @return [self]
253
+ #
254
+ # @raise +Money::Bank::UnknownRateFormat+ if format is unknown.
255
+ #
256
+ # @example
257
+ # s = "{\"USD_TO_CAD\":1.24515,\"CAD_TO_USD\":0.803115}"
258
+ # bank = Money::Bank::VariableExchange.new
259
+ # bank.import_rates(:json, s)
260
+ #
261
+ # bank.get_rate("USD", "CAD") #=> 1.24515
262
+ # bank.get_rate("CAD", "USD") #=> 0.803115
263
+ def import_rates(format, s, opts = {})
264
+ raise Money::Bank::UnknownRateFormat unless RATE_FORMATS.include?(format)
265
+
266
+ if format == :ruby
267
+ warn '[WARNING] Using :ruby format when importing rates is potentially unsafe and ' \
268
+ 'might lead to remote code execution via Marshal.load deserializer. Consider using ' \
269
+ 'safe alternatives such as :json and :yaml.'
270
+ end
271
+
272
+ store.transaction do
273
+ data = FORMAT_SERIALIZERS[format].load(s)
274
+
275
+ data.each do |key, rate|
276
+ from, to = key.split(SERIALIZER_SEPARATOR)
277
+ store.add_rate from, to, rate
278
+ end
279
+ end
280
+
281
+ self
282
+ end
283
+ end
284
+ end
285
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Money
4
+ class Currency
5
+ module Heuristics
6
+ def analyze(str)
7
+ raise StandardError, 'Heuristics deprecated, add `gem "money-heuristics"` to Gemfile'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Money
4
+ class Currency
5
+ module Loader
6
+ DATA_PATH = File.expand_path("../../../../config", __FILE__)
7
+
8
+ class << self
9
+ # Loads and returns the currencies stored in JSON files in the config directory.
10
+ #
11
+ # @return [Hash]
12
+ def load_currencies
13
+ currencies = parse_currency_file("currency_iso.json")
14
+ currencies.merge! parse_currency_file("currency_non_iso.json")
15
+ currencies.merge! parse_currency_file("currency_backwards_compatible.json")
16
+ end
17
+
18
+ private
19
+
20
+ def parse_currency_file(filename)
21
+ json = File.read("#{DATA_PATH}/#{filename}")
22
+ json.force_encoding(::Encoding::UTF_8) if defined?(::Encoding)
23
+ JSON.parse(json, symbolize_names: true)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end