num2words 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 82267cd5645c3767f5b34c5c7c54d6f997ee26f19425b86937ee021f747f19f4
4
- data.tar.gz: 33da5152ce64b117d4d9e59f2f33a63331b037cea6562f3c6fafc77d6b9f9f71
3
+ metadata.gz: 288098773139982ac980a108f2bde3e3220c7b090cadd82cf8bada24053cce84
4
+ data.tar.gz: 0103cff58a94f52fd255aec76b83d4d487cd3767a7861ea25b2aff44c334797b
5
5
  SHA512:
6
- metadata.gz: 9808bb66c09d3acecb944d1c1b37a3af17e46f758f6f500d5f618eecca798b3bf1e5b8bff415e23f9eb0d47804b672c1b509de2d89e77861dbc2fec2e88a8775
7
- data.tar.gz: fac35c5117774db5619fc82d4153f8ce0495dcb13e27fdba3b4726e211f6a155788f4b1a035987f4a083ec7035ea0b6429bb8d3549d03c8c64906d1325731f81
6
+ metadata.gz: e9570c86cb884cd7abc6c8f16df6946d85ab8292c38a05a8567b48179a1aa04fdc3d4ecfb8252f358c35bcac4d093c5f236c04a63b16009ee04487b47d5fad3d
7
+ data.tar.gz: 613f2887655f9b5dcd1d9dcf927bf6d368337fa95abe2f2e7b909a87df7caf8d5ea88fda30e167ab22f9b624ef20bc68371ed916a52d239ea3156c9f6ef09bdb
@@ -0,0 +1,40 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ - master
8
+ pull_request:
9
+
10
+ jobs:
11
+ test:
12
+ name: Ruby ${{ matrix.ruby }}
13
+ runs-on: ubuntu-22.04
14
+
15
+ strategy:
16
+ fail-fast: false
17
+ matrix:
18
+ ruby:
19
+ - "2.7"
20
+ - "3.0"
21
+ - "3.1"
22
+ - "3.2"
23
+ - "3.3"
24
+ - "3.4"
25
+
26
+ steps:
27
+ - name: Checkout
28
+ uses: actions/checkout@v4
29
+
30
+ - name: Set up Ruby
31
+ uses: ruby/setup-ruby@v1
32
+ with:
33
+ ruby-version: ${{ matrix.ruby }}
34
+ bundler-cache: true
35
+
36
+ - name: Run specs
37
+ run: bundle exec rspec
38
+
39
+ - name: Build gem
40
+ run: gem build num2words.gemspec
data/CHANGELOG.md CHANGED
@@ -11,6 +11,45 @@
11
11
 
12
12
  ---
13
13
 
14
+ ## [0.4.0] - 2026-07-05
15
+
16
+ ### Added
17
+ - Опциональные Rails helpers и Railtie:
18
+ - `number_to_words`
19
+ - `currency_to_words`
20
+ - `date_to_words`
21
+ - `time_to_words`
22
+ - `datetime_to_words`
23
+ - Интеграционные specs для проверки Railtie в минимальном Rails-приложении.
24
+ - Подключение Rails helpers в controller helper context.
25
+ - Валидатор ActiveModel `num2words_currency` для проверки валюты по локали.
26
+ - Валидатор ActiveModel `num2words_locale` для проверки поддерживаемой локали.
27
+ - Публичный metadata API `Num2words.available_locales`.
28
+ - Публичный metadata API `Num2words.currency_available?`.
29
+ - Публичный metadata API `Num2words.currency_info`.
30
+ - Публичный metadata API `Num2words.default_currency_info`.
31
+ - Документация по Rails-интеграции: `docs/rails.md` и `docs/rails.ru.md`.
32
+
33
+ ---
34
+
35
+ ## [0.3.1] - 2026-07-04
36
+
37
+ ### Added
38
+ - Набор benchmark-проверок: `benchmark/converter_benchmark.rb` и `bundle exec rake benchmark`.
39
+ - Справочник API: `docs/api.md` и `docs/api.ru.md`.
40
+ - Документация по числовым лимитам: `docs/limits.md` и `docs/limits.ru.md`.
41
+ - Rake-задачи для разработки и релизной проверки:
42
+ - `bundle exec rake spec`
43
+ - `bundle exec rake build`
44
+ - `bundle exec rake release:check`
45
+ - `bundle exec rake locales`
46
+ - `bundle exec rake currencies`
47
+
48
+ ### Fixed
49
+ - Добавлена runtime-зависимость `bigdecimal` для совместимости с Ruby 3.4+.
50
+
51
+ ---
52
+
14
53
  ## [0.3.0] - 2026-06-27
15
54
 
16
55
  ### Added
data/Gemfile CHANGED
@@ -9,4 +9,5 @@ gem "i18n", "~> 1.10"
9
9
 
10
10
  group :development, :test do
11
11
  gem "rspec", "~> 3.12"
12
+ gem "rails", "~> 7.1"
12
13
  end
data/README.md CHANGED
@@ -14,6 +14,9 @@ Ruby-гем для преобразования чисел, дробей, ден
14
14
  - Даты, время и дата-время.
15
15
  - Регистры вывода: `word_case: :upper | :downcase | :capitalize | :title`.
16
16
  - Удобные методы для `Integer`, `Float`, `String`, `Date`, `Time`, `DateTime`.
17
+ - Опциональные Rails helpers и Railtie.
18
+ - ActiveModel validators для локалей и валют.
19
+ - Metadata API для локалей и валют.
17
20
  - Интерактивная консоль `num2words-console`.
18
21
 
19
22
  ---
@@ -143,6 +146,70 @@ Num2words.to_currency(BigDecimal("21.05"), :ru)
143
146
  - `minor: :always | :nonzero | :never` — вывод младшей денежной единицы.
144
147
  - `code: :USD` — выбор валюты для `to_currency`.
145
148
 
149
+ Полная справка по API:
150
+
151
+ - [English](docs/api.md)
152
+ - [Русский](docs/api.ru.md)
153
+
154
+ Ограничения числовых разрядов:
155
+
156
+ - [English](docs/limits.md)
157
+ - [Русский](docs/limits.ru.md)
158
+
159
+ Rails-интеграция:
160
+
161
+ - [English](docs/rails.md)
162
+ - [Русский](docs/rails.ru.md)
163
+
164
+ ---
165
+
166
+ ## Rails
167
+
168
+ Если Rails доступен, helpers подключаются автоматически через Railtie.
169
+
170
+ ```erb
171
+ <%= number_to_words(123, locale: :ru) %>
172
+ <%= currency_to_words(@payment.amount, locale: :ru, code: @payment.currency) %>
173
+ <%= date_to_words(Date.current, locale: I18n.locale) %>
174
+ ```
175
+
176
+ Также доступны ActiveModel validators:
177
+
178
+ ```ruby
179
+ class Payment < ApplicationRecord
180
+ validates :currency, num2words_currency: { locale: ->(payment) { payment.locale } }
181
+ validates :locale, num2words_locale: true
182
+ end
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Metadata API
188
+
189
+ ```ruby
190
+ Num2words.available_locales
191
+ # => [:ar, :be, :bg, :bn, :cs, ...]
192
+
193
+ Num2words.currency_available?(:ru, :RUB)
194
+ # => true
195
+
196
+ Num2words.currency_info(:ru, :RUB)
197
+ # => {
198
+ # code: :RUB,
199
+ # major_unit: ["рубль", "рубля", "рублей"],
200
+ # minor_unit: ["копейка", "копейки", "копеек"],
201
+ # symbol: "₽"
202
+ # }
203
+
204
+ Num2words.default_currency_info(:ru)
205
+ # => {
206
+ # code: :RUB,
207
+ # major_unit: ["рубль", "рубля", "рублей"],
208
+ # minor_unit: ["копейка", "копейки", "копеек"],
209
+ # symbol: "₽"
210
+ # }
211
+ ```
212
+
146
213
  ---
147
214
 
148
215
  ## Консоль
@@ -203,6 +270,11 @@ KZT MYR NOK PKR PLN RON RSD RUB SAR SEK THB TRY UAH USD VND
203
270
 
204
271
  После добавления локали стоит покрыть ее отдельным spec и добавить в `spec/currency_catalog_spec.rb`.
205
272
 
273
+ Подробная инструкция:
274
+
275
+ - [English](docs/locale_development.md)
276
+ - [Русский](docs/locale_development.ru.md)
277
+
206
278
  ---
207
279
 
208
280
  ## Тестирование
@@ -211,6 +283,17 @@ KZT MYR NOK PKR PLN RON RSD RUB SAR SEK THB TRY UAH USD VND
211
283
  bundle exec rspec
212
284
  ```
213
285
 
286
+ ## Rake tasks
287
+
288
+ ```bash
289
+ bundle exec rake spec
290
+ bundle exec rake build
291
+ bundle exec rake release:check
292
+ bundle exec rake locales
293
+ bundle exec rake currencies
294
+ bundle exec rake benchmark
295
+ ```
296
+
214
297
  ---
215
298
 
216
299
  ## Roadmap
data/Rakefile CHANGED
@@ -2,9 +2,45 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require "rspec/core/rake_task"
5
+ require "yaml"
5
6
 
6
- # таска для запуска тестов
7
7
  RSpec::Core::RakeTask.new(:spec)
8
8
 
9
- # по умолчанию запускаем тесты
9
+ task build: "build:clean"
10
+
11
+ namespace :build do
12
+ task :clean do
13
+ Dir.glob("num2words-*.gem").each { |file| File.delete(file) }
14
+ end
15
+ end
16
+
17
+ namespace :release do
18
+ desc "Run tests and build the gem"
19
+ task check: [:spec, :build]
20
+ end
21
+
22
+ desc "Print supported locale codes"
23
+ task :locales do
24
+ locales = Dir[File.expand_path("config/locales/*.yml", __dir__)]
25
+ .map { |file| File.basename(file, ".yml") }
26
+ .sort
27
+
28
+ puts locales.join(" ")
29
+ end
30
+
31
+ desc "Print supported currency codes"
32
+ task :currencies do
33
+ currencies = YAML.load_file(File.expand_path("config/locales/ru.yml", __dir__))
34
+ .dig("ru", "num2words", "currencies")
35
+ .keys
36
+ .sort
37
+
38
+ puts currencies.join(" ")
39
+ end
40
+
41
+ desc "Run converter benchmark"
42
+ task :benchmark do
43
+ sh "ruby benchmark/converter_benchmark.rb"
44
+ end
45
+
10
46
  task default: :spec
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "benchmark"
4
+ require_relative "../lib/num2words"
5
+
6
+ DEFAULT_ITERATIONS = 100
7
+
8
+ def all_locales
9
+ Dir[File.expand_path("../config/locales/*.yml", __dir__)]
10
+ .map { |file| File.basename(file, ".yml").to_sym }
11
+ .sort
12
+ end
13
+
14
+ def selected_locales
15
+ requested = ENV.fetch("LOCALES", "").split(",").map(&:strip).reject(&:empty?).map(&:to_sym)
16
+ return all_locales if requested.empty?
17
+
18
+ unknown = requested - all_locales
19
+ abort "Unknown locales: #{unknown.join(', ')}" unless unknown.empty?
20
+
21
+ requested
22
+ end
23
+
24
+ def iterations
25
+ Integer(ENV.fetch("ITERATIONS", DEFAULT_ITERATIONS.to_s))
26
+ rescue ArgumentError
27
+ abort "ITERATIONS must be an integer"
28
+ end
29
+
30
+ SCENARIOS = {
31
+ integer: ->(locale) { Num2words.to_words(123_456_789, locale) },
32
+ decimal: ->(locale) { Num2words.to_words("12345.67", locale) },
33
+ date: ->(locale) { Num2words.to_words("2024-08-21", locale) },
34
+ time: ->(locale) { Num2words.to_words("14:35:42", locale) },
35
+ datetime: ->(locale) { Num2words.to_words("2024-08-21 14:35:42", locale) },
36
+ currency: ->(locale) { Num2words.to_currency("12345.67", locale) }
37
+ }.freeze
38
+
39
+ def measure_scenarios(locales, iterations_count)
40
+ SCENARIOS.map do |name, callable|
41
+ calls = locales.size * iterations_count
42
+ elapsed = Benchmark.realtime do
43
+ iterations_count.times do
44
+ locales.each { |locale| callable.call(locale) }
45
+ end
46
+ end
47
+
48
+ [name, calls, elapsed]
49
+ end
50
+ end
51
+
52
+ def measure_slowest_locales(locales, iterations_count)
53
+ locales.map do |locale|
54
+ calls = SCENARIOS.size * iterations_count
55
+ elapsed = Benchmark.realtime do
56
+ iterations_count.times do
57
+ SCENARIOS.each_value { |callable| callable.call(locale) }
58
+ end
59
+ end
60
+
61
+ [locale, calls, elapsed]
62
+ end.sort_by { |(_, _, elapsed)| -elapsed }.first(10)
63
+ end
64
+
65
+ def print_table(headers, rows)
66
+ widths = headers.each_with_index.map do |header, index|
67
+ ([header.length] + rows.map { |row| row[index].to_s.length }).max
68
+ end
69
+
70
+ puts headers.each_with_index.map { |header, index| header.ljust(widths[index]) }.join(" ")
71
+ puts widths.map { |width| "-" * width }.join(" ")
72
+ rows.each do |row|
73
+ puts row.each_with_index.map { |value, index| value.to_s.ljust(widths[index]) }.join(" ")
74
+ end
75
+ end
76
+
77
+ locales = selected_locales
78
+ iterations_count = iterations
79
+
80
+ puts "num2words benchmark"
81
+ puts "Locales: #{locales.join(', ')}"
82
+ puts "Iterations per locale: #{iterations_count}"
83
+ puts
84
+
85
+ scenario_rows = measure_scenarios(locales, iterations_count).map do |name, calls, elapsed|
86
+ [name, calls, format("%.4f", elapsed), format("%.4f", (elapsed / calls) * 1000)]
87
+ end
88
+
89
+ print_table(["Scenario", "Calls", "Total, s", "ms/call"], scenario_rows)
90
+ puts
91
+
92
+ slowest_rows = measure_slowest_locales(locales, iterations_count).map do |locale, calls, elapsed|
93
+ [locale, calls, format("%.4f", elapsed), format("%.4f", (elapsed / calls) * 1000)]
94
+ end
95
+
96
+ print_table(["Slowest locale", "Calls", "Total, s", "ms/call"], slowest_rows)