fbe 0.24.3 → 0.24.4

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: ce758baa2b569c87b5c6318bd2a58df0a5041c578164bb07f337c197a83ef4a7
4
- data.tar.gz: fa85c249bc4ea36ecf435e0412bccd9a1c8885ed222f0538be0c02ebd4f59ccc
3
+ metadata.gz: 92baa5e54a0df9b2e57f2d6619cfad4d4ee1b4a4150b1e30b82780062dd9bd34
4
+ data.tar.gz: ac08a108536716ff130271018211616e02b58a55dcef303bed0c0d01808387eb
5
5
  SHA512:
6
- metadata.gz: 43e184c1ee6955ca80cd5a4ba01aa47cf5e59ce8ad1767c03e535c77c253a1bb9223d6603825ef99c6d094828d03e3ae7b6a68e3cc4b7766cfb108c3058fa714
7
- data.tar.gz: f1399b8d6b1022a2460f6c1049bd2b58aa924933773c0bdbec3cc21ba65e28aeca6712d382fdd070e0ec5f44f8c5ac3608aacdb9d87857c11f00b45bc6efd728
6
+ metadata.gz: 4b87c1fefa9477f19588b01eeb7a7d593ca38f1eed068fe7d27a84a66b4cc35214f83b8f6128a169a007df962063f4ae5373442a17c61d12f1c7e876c1099ef6
7
+ data.tar.gz: ae01c566bca05de64432b34b6f27bbf82497d5d72817c5bbb0ef9f80e0f62f742681f1ae820d30bc081d891b31f926369ee1f0efe42e4b778a0223b4be26959e
data/.rubocop.yml CHANGED
@@ -56,3 +56,5 @@ Metrics/ParameterLists:
56
56
  Enabled: false
57
57
  Layout/MultilineAssignmentLayout:
58
58
  Enabled: true
59
+ Style/RequireOrder:
60
+ Enabled: true
data/Rakefile CHANGED
@@ -4,9 +4,9 @@
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
6
  require 'qbash'
7
- require 'rubygems'
8
7
  require 'rake'
9
8
  require 'rake/clean'
9
+ require 'rubygems'
10
10
 
11
11
  def name
12
12
  @name ||= File.basename(Dir['*.gemspec'].first, '.*')
data/lib/fbe/conclude.rb CHANGED
@@ -6,8 +6,8 @@
6
6
  require 'tago'
7
7
  require_relative '../fbe'
8
8
  require_relative 'fb'
9
- require_relative 'octo'
10
9
  require_relative 'if_absent'
10
+ require_relative 'octo'
11
11
 
12
12
  # Creates an instance of {Fbe::Conclude} and evals it with the block provided.
13
13
  #
data/lib/fbe/fb.rb CHANGED
@@ -4,11 +4,11 @@
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
6
  require 'factbase'
7
+ require 'factbase/cached/cached_factbase'
8
+ require 'factbase/indexed/indexed_factbase'
7
9
  require 'factbase/logged'
8
10
  require 'factbase/pre'
9
11
  require 'factbase/rules'
10
- require 'factbase/cached/cached_factbase'
11
- require 'factbase/indexed/indexed_factbase'
12
12
  require 'factbase/sync/sync_factbase'
13
13
  require 'judges'
14
14
  require 'loog'
data/lib/fbe/just_one.rb CHANGED
@@ -3,8 +3,8 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Zerocracy
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
- require 'time'
7
6
  require 'others'
7
+ require 'time'
8
8
  require_relative '../fbe'
9
9
  require_relative 'fb'
10
10
 
@@ -3,6 +3,7 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Zerocracy
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
+ require 'ellipsized'
6
7
  require 'faraday'
7
8
  require 'faraday/logging/formatter'
8
9
  require_relative '../../fbe'
@@ -66,6 +67,21 @@ class Fbe::Middleware::Formatter < Faraday::Logging::Formatter
66
67
  )
67
68
  return
68
69
  end
70
+ if http.status >= 500 && http.response_headers['content-type']&.start_with?('text')
71
+ error(
72
+ [
73
+ "#{@req.method.upcase} #{apply_filters(@req.url.to_s)} HTTP/1.1",
74
+ shifted(apply_filters(dump_headers(@req.request_headers))),
75
+ '',
76
+ shifted(apply_filters(@req.request_body)),
77
+ "HTTP/1.1 #{http.status}",
78
+ shifted(apply_filters(dump_headers(http.response_headers))),
79
+ '',
80
+ shifted(apply_filters(http.response_body&.ellipsized(100, :right)))
81
+ ].join("\n")
82
+ )
83
+ return
84
+ end
69
85
  error(
70
86
  [
71
87
  "#{@req.method.upcase} #{apply_filters(@req.url.to_s)} HTTP/1.1",
@@ -3,12 +3,12 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Zerocracy
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
- require 'zlib'
7
6
  require 'filesize'
8
7
  require 'json'
9
8
  require 'loog'
10
9
  require 'sqlite3'
11
10
  require 'time'
11
+ require 'zlib'
12
12
  require_relative '../../fbe'
13
13
  require_relative '../../fbe/middleware'
14
14
 
@@ -53,8 +53,10 @@ class Fbe::Middleware::SqliteStore
53
53
  # @param loog [Loog] Logger instance (optional, defaults to Loog::NULL)
54
54
  # @param maxsize [Integer] Maximum database size in bytes (optional, defaults to 10MB)
55
55
  # @param maxvsize [Integer] Maximum size in bytes of a single value (optional, defaults to 10Kb)
56
- # @raise [ArgumentError] If path is nil/empty, directory doesn't exist, or version is nil/empty
57
- def initialize(path, version, loog: Loog::NULL, maxsize: '10Mb', maxvsize: '10Kb')
56
+ # @param ttl [Integer, nil] lifetime of keys in hours
57
+ # @raise [ArgumentError] If path is nil/empty, directory doesn't exist, version is nil/empty,
58
+ # or ttl is not nil or not Integer or not positive
59
+ def initialize(path, version, loog: Loog::NULL, maxsize: '10Mb', maxvsize: '10Kb', ttl: nil)
58
60
  raise ArgumentError, 'Database path cannot be nil or empty' if path.nil? || path.empty?
59
61
  dir = File.dirname(path)
60
62
  raise ArgumentError, "Directory #{dir} does not exist" unless File.directory?(dir)
@@ -64,6 +66,8 @@ class Fbe::Middleware::SqliteStore
64
66
  @loog = loog
65
67
  @maxsize = Filesize.from(maxsize.to_s).to_i
66
68
  @maxvsize = Filesize.from(maxvsize.to_s).to_i
69
+ raise ArgumentError, 'TTL can be nil or Integer > 0' if !ttl.nil? && !(ttl.is_a?(Integer) && ttl.positive?)
70
+ @ttl = ttl
67
71
  end
68
72
 
69
73
  # Read a value from the cache.
@@ -106,8 +110,8 @@ class Fbe::Middleware::SqliteStore
106
110
  return if value.bytesize > @maxvsize
107
111
  perform do |t|
108
112
  t.execute(<<~SQL, [key, value, Time.now.utc.iso8601])
109
- INSERT INTO cache(key, value, touched_at) VALUES(?1, ?2, ?3)
110
- ON CONFLICT(key) DO UPDATE SET value = ?2, touched_at = ?3
113
+ INSERT INTO cache(key, value, touched_at, created_at) VALUES(?1, ?2, ?3, ?3)
114
+ ON CONFLICT(key) DO UPDATE SET value = ?2, touched_at = ?3, created_at = ?3
111
115
  SQL
112
116
  end
113
117
  nil
@@ -153,10 +157,29 @@ class Fbe::Middleware::SqliteStore
153
157
  SQL
154
158
  t.execute 'INSERT INTO cache SELECT * FROM cache_old;'
155
159
  t.execute 'DROP TABLE cache_old;'
160
+ t.execute 'CREATE INDEX IF NOT EXISTS cache_key_idx ON cache(key);'
156
161
  t.execute 'CREATE INDEX IF NOT EXISTS cache_touched_at_idx ON cache(touched_at);'
157
162
  end
158
163
  d.execute 'VACUUM;'
159
164
  end
165
+ if d.execute("SELECT 1 FROM pragma_table_info('cache') WHERE name = 'created_at';").dig(0, 0) != 1
166
+ d.transaction do |t|
167
+ t.execute 'ALTER TABLE cache ADD COLUMN created_at TEXT;'
168
+ t.execute 'UPDATE cache set created_at = ?;', [Time.now.utc.iso8601]
169
+ t.execute 'ALTER TABLE cache RENAME TO cache_old;'
170
+ t.execute <<~SQL
171
+ CREATE TABLE IF NOT EXISTS cache(
172
+ key TEXT UNIQUE NOT NULL, value TEXT, touched_at TEXT NOT NULL, created_at TEXT NOT NULL
173
+ );
174
+ SQL
175
+ t.execute 'INSERT INTO cache SELECT * FROM cache_old;'
176
+ t.execute 'DROP TABLE cache_old;'
177
+ t.execute 'CREATE INDEX IF NOT EXISTS cache_key_idx ON cache(key);'
178
+ t.execute 'CREATE INDEX IF NOT EXISTS cache_touched_at_idx ON cache(touched_at);'
179
+ t.execute 'CREATE INDEX IF NOT EXISTS cache_created_at_idx ON cache(created_at);'
180
+ end
181
+ d.execute 'VACUUM;'
182
+ end
160
183
  found = d.execute("SELECT value FROM meta WHERE key = 'version' LIMIT 1;").dig(0, 0)
161
184
  if found != @version
162
185
  @loog.info("Version mismatch in SQLite cache: stored '#{found}' != current '#{@version}', cleaning up")
@@ -166,6 +189,15 @@ class Fbe::Middleware::SqliteStore
166
189
  end
167
190
  d.execute 'VACUUM;'
168
191
  end
192
+ unless @ttl.nil?
193
+ d.transaction do |t|
194
+ t.execute <<~SQL, [(Time.now.utc - (@ttl * 60 * 60)).iso8601]
195
+ DELETE FROM cache
196
+ WHERE key IN (SELECT key FROM cache WHERE (created_at < ?));
197
+ SQL
198
+ end
199
+ d.execute 'VACUUM;'
200
+ end
169
201
  if File.size(@path) > @maxsize
170
202
  @loog.info(
171
203
  "SQLite cache file size (#{Filesize.from(File.size(@path).to_s).pretty} bytes) exceeds " \
data/lib/fbe/octo.rb CHANGED
@@ -93,7 +93,9 @@ def Fbe.octo(options: $options, global: $global, loog: $loog)
93
93
  if options.sqlite_cache
94
94
  maxsize = Filesize.from(options.sqlite_cache_maxsize || '100M').to_i
95
95
  maxvsize = Filesize.from(options.sqlite_cache_maxvsize || '100K').to_i
96
- store = Fbe::Middleware::SqliteStore.new(options.sqlite_cache, Fbe::VERSION, loog:, maxsize:, maxvsize:)
96
+ store = Fbe::Middleware::SqliteStore.new(
97
+ options.sqlite_cache, Fbe::VERSION, loog:, maxsize:, maxvsize:, ttl: 24
98
+ )
97
99
  loog.info(
98
100
  "Using HTTP cache in SQLite file: #{store.path} (" \
99
101
  "#{File.exist?(store.path) ? Filesize.from(File.size(store.path).to_s).pretty : 'file is absent'}, " \
data/lib/fbe.rb CHANGED
@@ -10,5 +10,5 @@
10
10
  # License:: MIT
11
11
  module Fbe
12
12
  # Current version of the gem (changed by +.rultor.yml+ on every release)
13
- VERSION = '0.24.3' unless const_defined?(:VERSION)
13
+ VERSION = '0.24.4' unless const_defined?(:VERSION)
14
14
  end
@@ -5,10 +5,11 @@
5
5
 
6
6
  require 'faraday'
7
7
  require 'loog'
8
- require_relative '../../test__helper'
8
+ require 'securerandom'
9
9
  require_relative '../../../lib/fbe'
10
10
  require_relative '../../../lib/fbe/middleware'
11
11
  require_relative '../../../lib/fbe/middleware/formatter'
12
+ require_relative '../../test__helper'
12
13
 
13
14
  # Test.
14
15
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -49,9 +50,38 @@ class LoggingFormatterTest < Fbe::Test
49
50
  end
50
51
  end
51
52
 
53
+ def test_truncate_body_for_error_text_response
54
+ body = SecureRandom.alphanumeric(120)
55
+ log_it(
56
+ status: 502,
57
+ response_body: body,
58
+ response_headers: {
59
+ 'content-type' => 'text/html; charset=utf-8',
60
+ 'x-github-api-version-selected' => '2022-11-28'
61
+ }
62
+ ) do |loog|
63
+ str = loog.to_s
64
+ refute_empty(str)
65
+ [
66
+ %r{http://example.com},
67
+ /Authorization:/,
68
+ /some request body/,
69
+ %r{HTTP/1.1 502},
70
+ /x-github-api-version-selected: "2022-11-28"/,
71
+ %r{content-type: "text/html; charset=utf-8"},
72
+ "#{body.slice(0, 97)}..."
73
+ ].each { |ptn| assert_match(ptn, str) }
74
+ end
75
+ end
76
+
52
77
  private
53
78
 
54
- def log_it(status:, method: :get)
79
+ def log_it(
80
+ status:,
81
+ method: :get,
82
+ response_body: '{"message": "hello, world!"}',
83
+ response_headers: { 'content-type' => 'application/json', 'x-github-api-version-selected' => '2022-11-28' }
84
+ )
55
85
  loog = Loog::Buffer.new
56
86
  formatter = Fbe::Middleware::Formatter.new(logger: loog, options: {})
57
87
  formatter.request(
@@ -67,16 +97,7 @@ class LoggingFormatterTest < Fbe::Test
67
97
  )
68
98
  )
69
99
  formatter.response(
70
- Faraday::Env.from(
71
- {
72
- status:,
73
- response_body: '{"message": "hello, world!"}',
74
- response_headers: {
75
- 'content-type' => 'application/json',
76
- 'x-github-api-version-selected' => '2022-11-28'
77
- }
78
- }
79
- )
100
+ Faraday::Env.from({ status:, response_body:, response_headers: })
80
101
  )
81
102
  yield loog
82
103
  end
@@ -5,10 +5,10 @@
5
5
 
6
6
  require 'faraday'
7
7
  require 'webmock'
8
- require_relative '../../test__helper'
9
8
  require_relative '../../../lib/fbe'
10
9
  require_relative '../../../lib/fbe/middleware'
11
10
  require_relative '../../../lib/fbe/middleware/rate_limit'
11
+ require_relative '../../test__helper'
12
12
 
13
13
  # Test.
14
14
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -3,11 +3,11 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Zerocracy
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
- require 'securerandom'
7
6
  require 'qbash'
8
- require_relative '../../test__helper'
7
+ require 'securerandom'
9
8
  require_relative '../../../lib/fbe/middleware'
10
9
  require_relative '../../../lib/fbe/middleware/sqlite_store'
10
+ require_relative '../../test__helper'
11
11
 
12
12
  # Test.
13
13
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -237,6 +237,99 @@ class SqliteStoreTest < Fbe::Test
237
237
  end
238
238
  end
239
239
 
240
+ def test_upgrade_sqlite_schema_for_add_created_at_column
241
+ with_tmpfile('a.db') do |f|
242
+ SQLite3::Database.new(f).tap do |d|
243
+ d.execute 'CREATE TABLE IF NOT EXISTS cache(key TEXT UNIQUE NOT NULL, value TEXT, touched_at TEXT NOT NULL);'
244
+ [
245
+ ['key1', Zlib::Deflate.deflate(JSON.dump('value1')), Time.now.utc.iso8601],
246
+ ['key2', Zlib::Deflate.deflate(JSON.dump('value2')), Time.now.utc.iso8601]
247
+ ].each { d.execute 'INSERT INTO cache(key, value, touched_at) VALUES(?1, ?2, ?3);', _1 }
248
+ d.execute 'CREATE TABLE IF NOT EXISTS meta(key TEXT UNIQUE NOT NULL, value TEXT);'
249
+ d.execute "INSERT INTO meta(key, value) VALUES('version', ?);", ['0.0.1']
250
+ end
251
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog).then do |store|
252
+ assert_equal('value1', store.read('key1'))
253
+ assert_equal('value2', store.read('key2'))
254
+ rescue SQLite3::SQLException => e
255
+ assert_nil(e)
256
+ end
257
+ end
258
+ end
259
+
260
+ def test_set_correct_ttl
261
+ with_tmpfile('c.db') do |f|
262
+ s = Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, ttl: nil)
263
+ refute_nil(s)
264
+ s = Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, ttl: 24)
265
+ refute_nil(s)
266
+ end
267
+ end
268
+
269
+ def test_set_incorrect_ttl
270
+ with_tmpfile('c.db') do |f|
271
+ ex =
272
+ assert_raises(ArgumentError) do
273
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, ttl: 0)
274
+ end
275
+ assert_equal('TTL can be nil or Integer > 0', ex.message)
276
+ ex =
277
+ assert_raises(ArgumentError) do
278
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, ttl: -10)
279
+ end
280
+ assert_equal('TTL can be nil or Integer > 0', ex.message)
281
+ ex =
282
+ assert_raises(ArgumentError) do
283
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, ttl: 10.0)
284
+ end
285
+ assert_equal('TTL can be nil or Integer > 0', ex.message)
286
+ ex =
287
+ assert_raises(ArgumentError) do
288
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, ttl: '10')
289
+ end
290
+ assert_equal('TTL can be nil or Integer > 0', ex.message)
291
+ ex =
292
+ assert_raises(ArgumentError) do
293
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, ttl: Object.new)
294
+ end
295
+ assert_equal('TTL can be nil or Integer > 0', ex.message)
296
+ end
297
+ end
298
+
299
+ def test_delete_keys_if_ttl_expired
300
+ with_tmpfile('c.db') do |f|
301
+ now = Time.now
302
+ Time.stub(:now, now) do
303
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, ttl: 24).then do |s|
304
+ s.write('test1', 'value1')
305
+ s.write('test2', 'value2')
306
+ end
307
+ end
308
+ Time.stub(:now, now + (12 * 60 * 60)) do
309
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, ttl: 24).then do |s|
310
+ s.write('test3', 'value3')
311
+ s.write('test4', 'value4')
312
+ end
313
+ end
314
+ Time.stub(:now, now + (24 * 60 * 60)) do
315
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, ttl: 24).then do |s|
316
+ assert_equal('value1', s.read('test1'))
317
+ assert_equal('value2', s.read('test2'))
318
+ assert_equal('value3', s.read('test3'))
319
+ assert_equal('value4', s.read('test4'))
320
+ end
321
+ end
322
+ Time.stub(:now, now + (24 * 60 * 60) + 1) do
323
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, ttl: 24).then do |s|
324
+ assert_nil(s.read('test1'))
325
+ assert_nil(s.read('test2'))
326
+ assert_equal('value3', s.read('test3'))
327
+ assert_equal('value4', s.read('test4'))
328
+ end
329
+ end
330
+ end
331
+ end
332
+
240
333
  private
241
334
 
242
335
  def with_tmpfile(name = 'test.db', &)
@@ -6,10 +6,10 @@
6
6
  require 'faraday'
7
7
  require 'faraday/http_cache'
8
8
  require 'webmock'
9
- require_relative '../../test__helper'
10
9
  require_relative '../../../lib/fbe'
11
10
  require_relative '../../../lib/fbe/middleware'
12
11
  require_relative '../../../lib/fbe/middleware/trace'
12
+ require_relative '../../test__helper'
13
13
 
14
14
  # Test.
15
15
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -4,10 +4,10 @@
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
6
  require 'judges/options'
7
- require 'webmock/minitest'
8
7
  require 'loog'
9
- require_relative '../test__helper'
8
+ require 'webmock/minitest'
10
9
  require_relative '../../lib/fbe/github_graph'
10
+ require_relative '../test__helper'
11
11
 
12
12
  # Test.
13
13
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -3,11 +3,11 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Zerocracy
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
- require 'tmpdir'
7
6
  require 'factbase'
8
7
  require 'loog'
9
- require_relative '../test__helper'
8
+ require 'tmpdir'
10
9
  require_relative '../../lib/fbe/if_absent'
10
+ require_relative '../test__helper'
11
11
 
12
12
  # Test.
13
13
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -4,8 +4,8 @@
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
6
  require 'factbase'
7
- require_relative '../test__helper'
8
7
  require_relative '../../lib/fbe/just_one'
8
+ require_relative '../test__helper'
9
9
 
10
10
  # Test.
11
11
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -5,8 +5,8 @@
5
5
 
6
6
  require 'judges/options'
7
7
  require 'loog'
8
- require_relative '../test__helper'
9
8
  require_relative '../../lib/fbe/unmask_repos'
9
+ require_relative '../test__helper'
10
10
 
11
11
  # Test.
12
12
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
data/test/test__helper.rb CHANGED
@@ -32,8 +32,8 @@ require 'minitest/reporters'
32
32
  Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new]
33
33
 
34
34
  require 'loog'
35
- require 'minitest/stub_const'
36
35
  require 'minitest/autorun'
36
+ require 'minitest/stub_const'
37
37
  require 'webmock/minitest'
38
38
  require_relative '../lib/fbe'
39
39
 
data/test/test_fbe.rb CHANGED
@@ -3,8 +3,8 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Zerocracy
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
- require_relative 'test__helper'
7
6
  require_relative '../lib/fbe'
7
+ require_relative 'test__helper'
8
8
 
9
9
  # Main module test.
10
10
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fbe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.24.3
4
+ version: 0.24.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko