cbr_converter 1.0.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.
- checksums.yaml +7 -0
- data/.githooks/pre-commit +12 -0
- data/CHANGELOG.md +0 -0
- data/LICENSE +21 -0
- data/README.md +27 -0
- data/Rakefile +12 -0
- data/lib/cbr_converter/console.rb +327 -0
- data/lib/cbr_converter/currency_parser.rb +49 -0
- data/lib/cbr_converter/metal_parser.rb +66 -0
- data/lib/cbr_converter/version.rb +5 -0
- data/lib/cbr_converter.rb +69 -0
- data/sig/cbr_converter.rbs +3 -0
- metadata +84 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c664f4c5d1454be3586e7b24937b94fe4352efa765413c67811f6c3c5cefe50e
|
|
4
|
+
data.tar.gz: 120252e8dd915afe48f9eeacdf93057be1af5f24dd52bdf4fdc8a4e787506e52
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 20720a52af028a621085f437acba845668420584ae16eb9543d48e4cc8ee8b47b95111efd69ae1d82aecaea58d64af74cea334e9fee3c1e7c0ba19b94523b505
|
|
7
|
+
data.tar.gz: e4416761af7d3fe9cb2ea889016f3db434539cc59c82282a3b023565cbd7eff298aee0e8e14ea5d3b6c96cdaf69cb8b4b0192f6930e1acb4e2b55fa05cd2ee91
|
data/CHANGELOG.md
ADDED
|
File without changes
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Batuev Yakov Denisovich
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# CBR-converter
|
|
2
|
+
|
|
3
|
+
**cbr_converter** — Ruby gem для получения актуальных данных о курсах валют и драгоценных металлов с официального сайта Центрального Банка Российской Федерации. Гем парсит официальные XML-фиды ЦБ и предоставляет удобный интерфейс для работы с данными, включая конвертацию и математические операции.
|
|
4
|
+
|
|
5
|
+
## Особенности
|
|
6
|
+
- **Актуальные курсы валют** — USD, EUR, CNY и 40+ других валют на текущую дату
|
|
7
|
+
- **Драгоценные металлы** — золото, серебро, платина, палладий в граммах
|
|
8
|
+
- **Операции** — конвертация валют, расчет стоимости металлов, сравнение их между собой
|
|
9
|
+
|
|
10
|
+
## Установка
|
|
11
|
+
|
|
12
|
+
Вы можете установить гем напрямую:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
gem install cbr_converter
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Или добавьте его в ваш Gemfile:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
gem 'cbr_converter'
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Затем выполните:
|
|
25
|
+
```bash
|
|
26
|
+
bundle install
|
|
27
|
+
```
|
data/Rakefile
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CbrConverter
|
|
4
|
+
class CLI
|
|
5
|
+
def initialize
|
|
6
|
+
@running = true
|
|
7
|
+
@commands = {
|
|
8
|
+
"1" => -> { show_currency_rates },
|
|
9
|
+
"2" => ->(currency = nil) { show_currency_rate(currency) },
|
|
10
|
+
"3" => ->(first = nil, second = nil) { compare_currencies(first, second) },
|
|
11
|
+
"4" => -> { show_currencies },
|
|
12
|
+
"5" => ->(amount = nil, from = nil, to = nil) { convert_currency(amount, from, to) },
|
|
13
|
+
"6" => -> { refresh_currency_rates },
|
|
14
|
+
"7" => -> { show_metals_rates },
|
|
15
|
+
"8" => ->(metal = nil) { show_metal_rate(metal) },
|
|
16
|
+
"9" => ->(first = nil, second = nil) { compare_metals(first, second) },
|
|
17
|
+
"10" => -> { show_metals },
|
|
18
|
+
"11" => ->(amount = nil, from = nil, to = nil) { convert_metals(amount, from, to) },
|
|
19
|
+
"12" => -> { refresh_metals_rates },
|
|
20
|
+
"13" => -> { show_help },
|
|
21
|
+
"14" => -> { exit_console }
|
|
22
|
+
}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.start
|
|
26
|
+
new.start
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def start
|
|
30
|
+
puts "CBR Converter - Консольное приложение"
|
|
31
|
+
puts "Курсы валют ЦБ РФ"
|
|
32
|
+
show_help
|
|
33
|
+
run
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def run
|
|
39
|
+
while @running
|
|
40
|
+
print "\n> "
|
|
41
|
+
input = gets.chomp.strip
|
|
42
|
+
parse_and_execute(input)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def parse_and_execute(input)
|
|
47
|
+
return if input.empty?
|
|
48
|
+
|
|
49
|
+
parts = input.split
|
|
50
|
+
command = parts[0].downcase
|
|
51
|
+
args = parts[1..]
|
|
52
|
+
|
|
53
|
+
if @commands.key?(command)
|
|
54
|
+
@commands[command].call(*args)
|
|
55
|
+
else
|
|
56
|
+
puts "Неизвестная команда: #{command}"
|
|
57
|
+
puts "Введите 'help' для списка доступных команд"
|
|
58
|
+
end
|
|
59
|
+
rescue CbrConverter::Error => e
|
|
60
|
+
puts "Ошибка: #{e.message}"
|
|
61
|
+
rescue StandardError => e
|
|
62
|
+
puts "Произошла ошибка: #{e.message}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def show_help
|
|
66
|
+
puts "\nДоступные команды:"
|
|
67
|
+
puts "Команды для валют:"
|
|
68
|
+
puts " 1. - Показать все курсы валют"
|
|
69
|
+
puts " 2. <код валюты> - Показать курс конкретной валюты"
|
|
70
|
+
puts " 3. <валюта1> <валюта2> - Сравнить две валюты"
|
|
71
|
+
puts " 4. - Показать список доступных валют"
|
|
72
|
+
puts " 5. <сумма> <из> <в> - Конвертировать сумму из одной валюты в другую"
|
|
73
|
+
puts " 6. - Обновить курсы валют"
|
|
74
|
+
puts "Команды для металлов:"
|
|
75
|
+
puts " 7. - Показать все курсы металлов"
|
|
76
|
+
puts " 8. <код металла> - Показать конкретный металл"
|
|
77
|
+
puts " 9. <металл1> <металл2> - Сравнить два металла"
|
|
78
|
+
puts " 10. - Показать список доступных металлов"
|
|
79
|
+
puts " 11. <вес> <из> <в> - Конвертировать вес из одного металла другой"
|
|
80
|
+
puts " 12. - Обновить курсы металлов"
|
|
81
|
+
puts "Дополнительные команды:"
|
|
82
|
+
puts " 13. - Показать справку"
|
|
83
|
+
puts " 14. - Выйти из программы"
|
|
84
|
+
puts "\nПримеры:"
|
|
85
|
+
puts " 2 USD"
|
|
86
|
+
puts " 3 USD EUR"
|
|
87
|
+
puts " 5 100 USD RUB"
|
|
88
|
+
puts " 5 50.5 EUR USD"
|
|
89
|
+
puts " 11 1.1 gold silver"
|
|
90
|
+
puts " 8 gold"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def show_currency_rates
|
|
94
|
+
puts "\nТекущие курсы валют (1 единица валюты в рублях):"
|
|
95
|
+
puts "-" * 50
|
|
96
|
+
|
|
97
|
+
rates = CbrConverter.current_currency_rates
|
|
98
|
+
rates = rates.sort_by { |currency, _| currency }.to_h
|
|
99
|
+
|
|
100
|
+
rates.each_key do |currency|
|
|
101
|
+
next if currency == "RUB"
|
|
102
|
+
|
|
103
|
+
value = CbrConverter.get_currency_rate(currency)
|
|
104
|
+
puts " #{currency.ljust(5)}: #{format_rate(value)} руб."
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
puts "\n RUB : 1.00 руб."
|
|
108
|
+
puts "-" * 50
|
|
109
|
+
puts "Всего валют: #{rates.size - 1}"
|
|
110
|
+
puts "Данные от: #{Time.now.strftime("%d.%m.%Y %H:%M")}"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def show_currency_rate(currency)
|
|
114
|
+
if currency.nil?
|
|
115
|
+
puts "Использование: 2 <код валюты>"
|
|
116
|
+
puts "Пример: 2 USD"
|
|
117
|
+
return
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
currency = currency.upcase
|
|
121
|
+
rate = CbrConverter.get_currency_rate(currency)
|
|
122
|
+
|
|
123
|
+
puts "\nКурс #{currency}:"
|
|
124
|
+
puts " 1 #{currency} = #{format_rate(rate)} руб."
|
|
125
|
+
rescue CbrConverter::Error
|
|
126
|
+
puts "Валюта '#{currency}' не найдена. Используйте 4 для просмотра доступных валют."
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def compare_currencies(first, second)
|
|
130
|
+
if first.nil? || second.nil?
|
|
131
|
+
puts "Использование: 3 <валюта1> <валюта2>"
|
|
132
|
+
puts "Пример: 3 USD EUR"
|
|
133
|
+
return
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
first = first.upcase
|
|
137
|
+
second = second.upcase
|
|
138
|
+
|
|
139
|
+
ratio = CbrConverter.compare_currencies(first, second)
|
|
140
|
+
|
|
141
|
+
puts "\nСравнение #{first} и #{second}:"
|
|
142
|
+
puts " 1 #{first} = #{format_rate(ratio)} #{second}"
|
|
143
|
+
|
|
144
|
+
reverse_ratio = 1.0 / ratio
|
|
145
|
+
puts " #{format("%.4f", reverse_ratio).gsub(/\.?0+$/, "")} #{first} = 1 #{second}"
|
|
146
|
+
rescue CbrConverter::Error => e
|
|
147
|
+
puts "Ошибка: #{e.message}"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def show_currencies
|
|
151
|
+
currencies = CbrConverter.available_currencies
|
|
152
|
+
currencies.delete("RUB")
|
|
153
|
+
|
|
154
|
+
puts "\nДоступные валюты (#{currencies.size}):"
|
|
155
|
+
puts "-" * 50
|
|
156
|
+
|
|
157
|
+
currencies.each_slice(10) do |slice|
|
|
158
|
+
puts " #{slice.join(" ")}"
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def convert_currency(amount, from, to)
|
|
163
|
+
if amount.nil? || from.nil? || to.nil?
|
|
164
|
+
puts "Использование: 5 <сумма> <из валюты> <в валюту>"
|
|
165
|
+
puts "Пример: 5 100 USD RUB"
|
|
166
|
+
puts "Пример: 5 50.5 EUR USD"
|
|
167
|
+
return
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
begin
|
|
171
|
+
amount = Float(amount)
|
|
172
|
+
rescue ArgumentError
|
|
173
|
+
puts "Ошибка: сумма должна быть числом"
|
|
174
|
+
return
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
from = from.upcase
|
|
178
|
+
to = to.upcase
|
|
179
|
+
|
|
180
|
+
from_rate = CbrConverter.get_currency_rate(from)
|
|
181
|
+
to_rate = CbrConverter.get_currency_rate(to)
|
|
182
|
+
|
|
183
|
+
result_in_rub = amount * from_rate
|
|
184
|
+
result = BigDecimal(result_in_rub / to_rate).to_s
|
|
185
|
+
|
|
186
|
+
puts "\nРезультат конвертации:"
|
|
187
|
+
puts " #{format_number(amount)} #{from} = #{format_number(result)} #{to}"
|
|
188
|
+
puts " Курс: 1 #{from} = #{format_rate(from_rate)} руб."
|
|
189
|
+
puts " Курс: 1 #{to} = #{format_rate(to_rate)} руб."
|
|
190
|
+
rescue CbrConverter::Error => e
|
|
191
|
+
puts "Ошибка: #{e.message}"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def refresh_currency_rates
|
|
195
|
+
puts "Обновление курсов валют..."
|
|
196
|
+
CbrConverter.refresh_rates!
|
|
197
|
+
puts "Курсы успешно обновлены!"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def show_metals_rates
|
|
201
|
+
puts "\nТекущие курсы металлов (1 единица валюты в рублях):"
|
|
202
|
+
puts "-" * 50
|
|
203
|
+
|
|
204
|
+
rates = CbrConverter.current_metal_rates
|
|
205
|
+
rates = rates.sort_by { |metal, _| metal }.to_h
|
|
206
|
+
|
|
207
|
+
rates.each_key do |metal|
|
|
208
|
+
value = CbrConverter.get_metal_rate(metal)
|
|
209
|
+
puts " #{metal.to_s.ljust(11)}: #{format_rate(value)} руб."
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
puts "-" * 50
|
|
213
|
+
puts "Всего металлов: #{rates.size - 1}"
|
|
214
|
+
puts "Данные от: #{Time.now.strftime("%d.%m.%Y %H:%M")}"
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def show_metal_rate(metal)
|
|
218
|
+
if metal.nil?
|
|
219
|
+
puts "Использование: 8 <код металла>"
|
|
220
|
+
puts "Пример: 8 gold"
|
|
221
|
+
return
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
metal = metal.downcase
|
|
225
|
+
rate = CbrConverter.get_metal_rate(metal)
|
|
226
|
+
|
|
227
|
+
puts "\nКурс #{metal}:"
|
|
228
|
+
puts " 1г. #{metal} = #{format_rate(rate)} руб."
|
|
229
|
+
rescue CbrConverter::Error
|
|
230
|
+
puts "Металл '#{metal}' не найден. Используйте 10 для просмотра доступных металлов."
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def compare_metals(first, second)
|
|
234
|
+
if first.nil? || second.nil?
|
|
235
|
+
puts "Использование: 9 <металл1> <металл2>"
|
|
236
|
+
puts "Пример: 9 gold silver"
|
|
237
|
+
return
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
first = first.downcase
|
|
241
|
+
second = second.downcase
|
|
242
|
+
|
|
243
|
+
ratio = CbrConverter.compare_metals(first, second)
|
|
244
|
+
|
|
245
|
+
puts "\nСравнение #{first} и #{second}:"
|
|
246
|
+
puts " 1г. #{first} = #{format_rate(ratio)} #{second}"
|
|
247
|
+
|
|
248
|
+
reverse_ratio = 1.0 / ratio
|
|
249
|
+
puts " #{format("%.4f", reverse_ratio).gsub(/\.?0+$/, "")} #{first} = 1г. #{second}"
|
|
250
|
+
rescue CbrConverter::Error => e
|
|
251
|
+
puts "Ошибка: #{e.message}"
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def show_metals
|
|
255
|
+
metals = CbrConverter.available_metals
|
|
256
|
+
puts "\nДоступные металлы (#{metals.size}):"
|
|
257
|
+
puts "-" * 50
|
|
258
|
+
|
|
259
|
+
metals.each_slice(5) do |slice|
|
|
260
|
+
puts " #{slice.join(" ")}"
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def convert_metals(amount, from, to)
|
|
265
|
+
if amount.nil? || from.nil? || to.nil?
|
|
266
|
+
puts "Использование: 1 <вес> <из металла> <в металл>"
|
|
267
|
+
puts "Пример: 11 10 gold silver"
|
|
268
|
+
puts "Пример: 11 27.5 silver gold"
|
|
269
|
+
return
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
begin
|
|
273
|
+
amount = Float(amount)
|
|
274
|
+
rescue ArgumentError
|
|
275
|
+
puts "Ошибка: вес должна быть числом"
|
|
276
|
+
return
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
from = from.downcase
|
|
280
|
+
to = to.downcase
|
|
281
|
+
|
|
282
|
+
from_rate = CbrConverter.get_metal_rate(from)
|
|
283
|
+
to_rate = CbrConverter.get_metal_rate(to)
|
|
284
|
+
|
|
285
|
+
result_in_rub = amount * from_rate
|
|
286
|
+
result = BigDecimal(result_in_rub / to_rate).to_s
|
|
287
|
+
|
|
288
|
+
puts "\nРезультат конвертации:"
|
|
289
|
+
puts " #{format_number(amount)}г. #{from} = #{format_number(result)}г. #{to}"
|
|
290
|
+
puts " Курс: 1г. #{from} = #{format_rate(from_rate)} руб."
|
|
291
|
+
puts " Курс: 1г. #{to} = #{format_rate(to_rate)} руб."
|
|
292
|
+
rescue CbrConverter::Error => e
|
|
293
|
+
puts "Ошибка: #{e.message}"
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def refresh_metals_rates
|
|
297
|
+
puts "Обновление курсов металлов..."
|
|
298
|
+
CbrConverter.refresh_rates!
|
|
299
|
+
puts "Металлы успешно обновлены!"
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def exit_console
|
|
303
|
+
puts "\nДо свидания!"
|
|
304
|
+
@running = false
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def format_rate(rate)
|
|
308
|
+
rate.to_s("F")
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def format_number(number)
|
|
312
|
+
if number.is_a?(BigDecimal)
|
|
313
|
+
formatted = number.to_s("F")
|
|
314
|
+
else
|
|
315
|
+
formatted = format("%.4f", number)
|
|
316
|
+
formatted.gsub!(/\.?0+$/, "")
|
|
317
|
+
end
|
|
318
|
+
formatted
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Запуск приложения
|
|
323
|
+
if __FILE__ == $PROGRAM_NAME
|
|
324
|
+
console = CbrConverterConsole.new
|
|
325
|
+
console.start
|
|
326
|
+
end
|
|
327
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "nokogiri"
|
|
6
|
+
require "bigdecimal"
|
|
7
|
+
|
|
8
|
+
module CbrConverter
|
|
9
|
+
class CurrencyParser
|
|
10
|
+
URL = "http://www.cbr.ru/scripts/XML_daily.asp"
|
|
11
|
+
|
|
12
|
+
def fetch_rates
|
|
13
|
+
response = Net::HTTP.get_response(URI.parse(URL))
|
|
14
|
+
|
|
15
|
+
raise Error, "Сервер ЦБ не доступен: #{response.code}" unless response.is_a?(Net::HTTPSuccess)
|
|
16
|
+
|
|
17
|
+
response.body
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def parse_rates
|
|
21
|
+
doc = Nokogiri::XML(fetch_rates)
|
|
22
|
+
rates = {}
|
|
23
|
+
|
|
24
|
+
doc.xpath("//Valute").each do |valute_node|
|
|
25
|
+
char_code, rate = extract_rate(valute_node)
|
|
26
|
+
rates[char_code] = rate if char_code
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
rates
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def extract_rate(node)
|
|
35
|
+
char_code = node.at_xpath("CharCode")&.text
|
|
36
|
+
value_text = node.at_xpath("Value")&.text
|
|
37
|
+
nominal_text = node.at_xpath("Nominal")&.text
|
|
38
|
+
|
|
39
|
+
return nil if [char_code, value_text, nominal_text].any?(&:nil?)
|
|
40
|
+
|
|
41
|
+
value = BigDecimal(value_text.gsub(",", "."))
|
|
42
|
+
nominal = BigDecimal(nominal_text)
|
|
43
|
+
|
|
44
|
+
return nil if nominal.zero?
|
|
45
|
+
|
|
46
|
+
[char_code, value / nominal]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "nokogiri"
|
|
6
|
+
require "bigdecimal"
|
|
7
|
+
require "date"
|
|
8
|
+
|
|
9
|
+
module CbrConverter
|
|
10
|
+
class MetalsParser
|
|
11
|
+
BASE_URL = "http://www.cbr.ru/scripts/xml_metall.asp"
|
|
12
|
+
METALS = {
|
|
13
|
+
"1" => :gold,
|
|
14
|
+
"2" => :silver,
|
|
15
|
+
"3" => :platinum,
|
|
16
|
+
"4" => :palladium
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
def fetch_rates
|
|
20
|
+
date = Date.today
|
|
21
|
+
attempts = 0
|
|
22
|
+
|
|
23
|
+
while attempts < 7
|
|
24
|
+
formatted_date = (date - attempts).strftime("%d/%m/%Y")
|
|
25
|
+
url = "#{BASE_URL}?date_req1=#{formatted_date}&date_req2=#{formatted_date}"
|
|
26
|
+
|
|
27
|
+
response = Net::HTTP.get_response(URI.parse(url))
|
|
28
|
+
|
|
29
|
+
return response.body if response.is_a?(Net::HTTPSuccess) && contains_data?(response.body)
|
|
30
|
+
|
|
31
|
+
attempts += 1
|
|
32
|
+
end
|
|
33
|
+
raise Error, "Не удалось получить данные по металлам за последнюю неделю"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def parse_rates
|
|
37
|
+
doc = Nokogiri::XML(fetch_rates)
|
|
38
|
+
rates = {}
|
|
39
|
+
|
|
40
|
+
doc.xpath("//Record").each do |record_node|
|
|
41
|
+
metal_name, price = extract_metal_data(record_node)
|
|
42
|
+
rates[metal_name] = price if metal_name
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
rates
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def contains_data?(xml_body)
|
|
51
|
+
doc = Nokogiri::XML(xml_body)
|
|
52
|
+
doc.xpath("//Record").any?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def extract_metal_data(node)
|
|
56
|
+
code = node["Code"]
|
|
57
|
+
buy_text = node.at_xpath("Buy")&.text
|
|
58
|
+
return nil if code.nil? || buy_text.nil?
|
|
59
|
+
|
|
60
|
+
metal_name = METALS[code]
|
|
61
|
+
price = BigDecimal(buy_text.gsub(",", "."))
|
|
62
|
+
|
|
63
|
+
[metal_name, price]
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "cbr_converter/version"
|
|
4
|
+
require_relative "cbr_converter/currency_parser"
|
|
5
|
+
require_relative "cbr_converter/metal_parser"
|
|
6
|
+
require_relative "cbr_converter/console"
|
|
7
|
+
|
|
8
|
+
module CbrConverter
|
|
9
|
+
class Error < StandardError; end
|
|
10
|
+
|
|
11
|
+
def self.current_currency_rates
|
|
12
|
+
@current_currency_rates ||= begin
|
|
13
|
+
parser = CurrencyParser.new.parse_rates
|
|
14
|
+
parser["RUB"] = BigDecimal("1.0")
|
|
15
|
+
parser
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.current_metal_rates
|
|
20
|
+
@current_metal_rates ||= MetalsParser.new.parse_rates
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.get_currency_rate(currency)
|
|
24
|
+
rates = current_currency_rates
|
|
25
|
+
|
|
26
|
+
raise Error, "Валюта (#{currency}) не найдена в данных ЦБ" unless rates[currency]
|
|
27
|
+
|
|
28
|
+
rates[currency].truncate(4)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.get_metal_rate(metal)
|
|
32
|
+
rates = current_metal_rates
|
|
33
|
+
metal_sym = metal.to_sym
|
|
34
|
+
|
|
35
|
+
raise Error, "Металл (#{metal}) не найден в данных ЦБ" unless rates[metal_sym]
|
|
36
|
+
|
|
37
|
+
rates[metal_sym].truncate(4)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.compare_currencies(first_currency, second_currency)
|
|
41
|
+
first_rate = get_currency_rate(first_currency)
|
|
42
|
+
second_rate = get_currency_rate(second_currency)
|
|
43
|
+
|
|
44
|
+
(first_rate / second_rate).truncate(4)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.compare_metals(first_metal, second_metal)
|
|
48
|
+
first_rate = get_metal_rate(first_metal)
|
|
49
|
+
second_rate = get_metal_rate(second_metal)
|
|
50
|
+
|
|
51
|
+
(first_rate / second_rate).truncate(4)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.available_currencies
|
|
55
|
+
current_currency_rates.keys.sort
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.available_metals
|
|
59
|
+
current_metal_rates.keys.sort
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.refresh_rates!
|
|
63
|
+
@current_currency_rates = nil
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.refresh_metals!
|
|
67
|
+
@current_metal_rates = nil
|
|
68
|
+
end
|
|
69
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: cbr_converter
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Batuev Yakov Denisovich
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: httparty
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.21'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.21'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: nokogiri
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.15'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.15'
|
|
40
|
+
description: Позволяет получать актуальные данные XML с сайта Центрального Банка РФ
|
|
41
|
+
и выполнять конвертацию.
|
|
42
|
+
email:
|
|
43
|
+
- batuev@sfedu.ru
|
|
44
|
+
executables: []
|
|
45
|
+
extensions: []
|
|
46
|
+
extra_rdoc_files: []
|
|
47
|
+
files:
|
|
48
|
+
- ".githooks/pre-commit"
|
|
49
|
+
- CHANGELOG.md
|
|
50
|
+
- LICENSE
|
|
51
|
+
- README.md
|
|
52
|
+
- Rakefile
|
|
53
|
+
- lib/cbr_converter.rb
|
|
54
|
+
- lib/cbr_converter/console.rb
|
|
55
|
+
- lib/cbr_converter/currency_parser.rb
|
|
56
|
+
- lib/cbr_converter/metal_parser.rb
|
|
57
|
+
- lib/cbr_converter/version.rb
|
|
58
|
+
- sig/cbr_converter.rbs
|
|
59
|
+
homepage: https://github.com/NeMoYaxa/CBR-converter
|
|
60
|
+
licenses:
|
|
61
|
+
- MIT
|
|
62
|
+
metadata:
|
|
63
|
+
homepage_uri: https://github.com/NeMoYaxa/CBR-converter
|
|
64
|
+
source_code_uri: https://github.com/NeMoYaxa/CBR-converter
|
|
65
|
+
changelog_uri: https://github.com/NeMoYaxa/CBR-converter/blob/main/CHANGELOG.md
|
|
66
|
+
rubygems_mfa_required: 'true'
|
|
67
|
+
rdoc_options: []
|
|
68
|
+
require_paths:
|
|
69
|
+
- lib
|
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: 3.2.0
|
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
|
+
requirements:
|
|
77
|
+
- - ">="
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: '0'
|
|
80
|
+
requirements: []
|
|
81
|
+
rubygems_version: 4.0.6
|
|
82
|
+
specification_version: 4
|
|
83
|
+
summary: Библиотека для конвертации валют и драгоценных металлов по курсам ЦБ РФ.
|
|
84
|
+
test_files: []
|