sidekiq-pauzer 1.0.0.alpha → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b38aee70ca050a4c5c0f8c41d81bae5b755bb4a73fd1b5ddb7b365fb61ba3f40
4
- data.tar.gz: e233b4b60fc065a12c29f809ad196f763f05a54d7bec640c338dce6ec2c62984
3
+ metadata.gz: b31cb662c56453b04bebf1a887af9515aa0d86a74f9c9e0c6bb79513c499ed7e
4
+ data.tar.gz: c57e7667afcabea723ffdb57f5ae38f53ab756c22f2952bb1344d660df806bd4
5
5
  SHA512:
6
- metadata.gz: 8e106b6c3e162eafddf56da121c6d3af3319928a145c1991f9c09ceeb54751eb68a15f716731e5bf2feb15fd785a7062ce19db5523a79b8a3be2e1889af39c94
7
- data.tar.gz: 1db224ec249b5729e903a0ad8fc9f58235e8a39c5bc39f00a2e11a426c3a0ec20fb216faa5bf144ef20bbfa93f4dc94d8599546ab46052724142a89420e29a07
6
+ metadata.gz: 5456887979763bf5c4c741fa2dec95c4454222f77d65f212f4472a68dd248880a7b18687a6df999d9c6c25291d01027c01c4fed4630948d579cd9f2174f8635e
7
+ data.tar.gz: af94a161c39ff35abd29aa35a110c69d25144fd7b3d54b63bae65c64053694813e1eb7a8741e35182342b9e3c9b8de041290e8f46ffc3da6e34b6f489988365b
data/README.adoc CHANGED
@@ -34,6 +34,25 @@ Sidekiq.configure_server do |config|
34
34
  end
35
35
  ----
36
36
 
37
+ === API
38
+
39
+ This gem enhances Sidekiq's Queue API with:
40
+
41
+ [source, ruby]
42
+ ----
43
+ Sidekiq::Queue.new("critical").paused? # => true|false
44
+ Sidekiq::Queue.new("critical").pause!
45
+ Sidekiq::Queue.new("critical").unpause!
46
+ ----
47
+
48
+ Those translates to:
49
+
50
+ [source, ruby]
51
+ ----
52
+ Sidekiq::Pauzer.paused?("critical") # => true|false
53
+ Sidekiq::Pauzer.pause!("critical")
54
+ Sidekiq::Pauzer.unpause!("critical")
55
+ ----
37
56
 
38
57
  === Adding Pause/Resume Button to the Queues Tab
39
58
 
@@ -47,7 +66,39 @@ require "sidekiq/pauzer/web"
47
66
  ----
48
67
 
49
68
  NOTE: If you are using custom Sidekiq views path, then you will need to call
50
- (after requiring `sidekiq/pauzer/web`): `Sidekiq::Pauzer.unpatch_views!`.
69
+ (after requiring `sidekiq/pauzer/web`): `Sidekiq::Pauzer::Web.unpatch_views!`.
70
+
71
+
72
+ == Supported Ruby Versions
73
+
74
+ This library aims to support and is tested against the following Ruby versions:
75
+
76
+ * Ruby 2.7.x
77
+ * Ruby 3.0.x
78
+ * Ruby 3.1.x
79
+ * Ruby 3.2.x
80
+
81
+ If something doesn't work on one of these versions, it's a bug.
82
+
83
+ This library may inadvertently work (or seem to work) on other Ruby versions,
84
+ however support will only be provided for the versions listed above.
85
+
86
+ If you would like this library to support another Ruby version or
87
+ implementation, you may volunteer to be a maintainer. Being a maintainer
88
+ entails making sure all tests run and pass on that implementation. When
89
+ something breaks on your implementation, you will be responsible for providing
90
+ patches in a timely fashion. If critical issues for a particular implementation
91
+ exist at the time of a major release, support for that Ruby version may be
92
+ dropped.
93
+
94
+
95
+ == Supported Sidekiq Versions
96
+
97
+ This library aims to support and work with following Sidekiq versions:
98
+
99
+ * Sidekiq 6.5.x
100
+ * Sidekiq 7.0.x
101
+ * Sidekiq 7.1.x
51
102
 
52
103
 
53
104
  == Development
@@ -13,15 +13,15 @@ module Sidekiq
13
13
  false
14
14
  end
15
15
 
16
- def pause!(redis, key, queue)
16
+ def add(redis, key, queue)
17
17
  redis.sadd(key, queue)
18
18
  end
19
19
 
20
- def unpause!(redis, key, queue)
20
+ def remove(redis, key, queue)
21
21
  redis.srem(key, queue)
22
22
  end
23
23
 
24
- def paused_queues(redis, key)
24
+ def list(redis, key)
25
25
  # Cursor is not atomic, so there may be duplicates because of
26
26
  # concurrent update operations
27
27
  # See: https://redis.io/commands/scan/#scan-guarantees
@@ -1,31 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../runtime"
4
+
3
5
  module Sidekiq
4
6
  module Pauzer
5
7
  module Adapters
6
8
  # redis-client adapter
7
9
  module RedisClient
8
- SIDEKIQ_SEVEN = Gem::Version.new("7.0.0").freeze
9
- SIDEKIQ_VERSION = Gem::Version.new(Sidekiq::VERSION).freeze
10
-
11
10
  class << self
12
11
  def adapts?(redis)
13
- return true if SIDEKIQ_SEVEN <= SIDEKIQ_VERSION
12
+ return true if Runtime::SIDEKIQ_SEVEN
14
13
  return true if defined?(::RedisClient) && redis.is_a?(::RedisClient)
15
14
  return true if defined?(::RedisClient::Decorator::Client) && redis.is_a?(::RedisClient::Decorator::Client)
16
15
 
17
16
  false
18
17
  end
19
18
 
20
- def pause!(redis, key, queue)
19
+ def add(redis, key, queue)
21
20
  redis.call("SADD", key, queue)
22
21
  end
23
22
 
24
- def unpause!(redis, key, queue)
23
+ def remove(redis, key, queue)
25
24
  redis.call("SREM", key, queue)
26
25
  end
27
26
 
28
- def paused_queues(redis, key)
27
+ def list(redis, key)
29
28
  # Cursor is not atomic, so there may be duplicates because of
30
29
  # concurrent update operations
31
30
  # See: https://redis.io/commands/scan/#scan-guarantees
@@ -3,37 +3,47 @@
3
3
  require "sidekiq"
4
4
  require "sidekiq/fetch"
5
5
 
6
+ require_relative "./runtime"
7
+
6
8
  module Sidekiq
7
9
  module Pauzer
8
10
  # Default Sidekiq's BasicFetch infused with Pauzer
9
11
  class BasicFetch < Sidekiq::BasicFetch
10
12
  private
11
13
 
12
- if Gem::Version.new("7.0.0") <= Gem::Version.new(Sidekiq::VERSION)
14
+ if Runtime::SIDEKIQ_SEVEN
13
15
  def queues_cmd
14
- if @strictly_ordered_queues
15
- @queues - Pauzer.paused_queues
16
- else
17
- permute = (@queues - Pauzer.paused_queues)
18
- permute.shuffle!
19
- permute.uniq!
20
- permute
21
- end
16
+ queues =
17
+ if @strictly_ordered_queues
18
+ @queues
19
+ else
20
+ permute = @queues.dup
21
+ permute.shuffle!
22
+ permute.uniq!
23
+ permute
24
+ end
25
+
26
+ excluding_paused(queues)
22
27
  end
23
28
  else
24
29
  def queues_cmd
25
- if @strictly_ordered_queues
26
- *queues, timeout = @queues
30
+ queues =
31
+ if @strictly_ordered_queues
32
+ @queues[0...-1]
33
+ else
34
+ permute = @queues.dup
35
+ permute.shuffle!
36
+ permute.uniq!
37
+ permute
38
+ end
27
39
 
28
- (queues - Pauzer.paused_queues) << timeout
29
- else
30
- permute = (@queues - Pauzer.paused_queues)
31
- permute.shuffle!
32
- permute.uniq!
33
- permute << { timeout: Sidekiq::BasicFetch::TIMEOUT }
34
- end
40
+ excluding_paused(queues) << { timeout: Sidekiq::BasicFetch::TIMEOUT }
35
41
  end
36
42
  end
43
+
44
+ def excluding_paused(queues)
45
+ queues - Pauzer.paused_queue_names.map { |name| "queue:#{name}" }
46
+ end
37
47
  end
38
48
  end
39
49
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq"
4
+ require "sidekiq/api"
5
+
6
+ module Sidekiq
7
+ module Pauzer
8
+ module Patches
9
+ # @private
10
+ module Queue
11
+ def self.apply!
12
+ Sidekiq::Queue.prepend(self)
13
+ end
14
+
15
+ def paused?
16
+ Pauzer.paused?(name)
17
+ end
18
+
19
+ def pause!
20
+ Pauzer.pause!(name)
21
+ nil
22
+ end
23
+
24
+ def unpause!
25
+ Pauzer.unpause!(name)
26
+ nil
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+
5
+ require "sidekiq"
6
+ require "sidekiq/web"
7
+
8
+ module Sidekiq
9
+ module Pauzer
10
+ module Patches
11
+ # @private
12
+ module WebAction
13
+ PAUZER_QUEUES_TEMPLATE =
14
+ ERB.new(File.read(File.expand_path("../../../../web/views/queues.erb", __dir__))).src
15
+
16
+ class << self
17
+ def apply!
18
+ revert!
19
+
20
+ Sidekiq::WebAction.class_eval <<-RUBY, __FILE__, __LINE__ + 1 # rubocop:disable Style/DocumentDynamicEvalDefinition
21
+ def _erb_queues
22
+ #{PAUZER_QUEUES_TEMPLATE}
23
+ end
24
+ RUBY
25
+ end
26
+
27
+ def revert!
28
+ Sidekiq::WebAction.remove_method(:_erb_queues) if Sidekiq::WebAction.method_defined?(:_erb_queues)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq"
4
+ require "sidekiq/web"
5
+
6
+ module Sidekiq
7
+ module Pauzer
8
+ module Patches
9
+ # @private
10
+ module WebApplication
11
+ class << self
12
+ def apply!
13
+ remove_theirs_queue_update_route
14
+ register_ours_queue_update_route
15
+ end
16
+
17
+ private
18
+
19
+ def remove_theirs_queue_update_route
20
+ Sidekiq::WebApplication
21
+ .instance_variable_get(:@routes)
22
+ .fetch(Sidekiq::WebRouter::POST)
23
+ .delete_if { |route| route.pattern == "/queues/:name" }
24
+ end
25
+
26
+ def register_ours_queue_update_route # rubocop:disable Metrics/MethodLength
27
+ Sidekiq::WebApplication.class_exec do
28
+ post "/queues/:name" do
29
+ queue = Sidekiq::Queue.new(route_params[:name])
30
+
31
+ if params["pause"]
32
+ queue.pause!
33
+ elsif params["unpause"]
34
+ queue.unpause!
35
+ else
36
+ queue.clear
37
+ end
38
+
39
+ redirect "#{root_path}queues"
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -12,12 +12,10 @@ module Sidekiq
12
12
 
13
13
  class Refresher < Concurrent::TimerTask; end
14
14
 
15
- QUEUE_PREFIX = "queue:"
16
-
17
15
  # @param config [Config]
18
16
  def initialize(config)
19
17
  @mutex = Mutex.new
20
- @queues = []
18
+ @names = []
21
19
  @redis_key = config.redis_key
22
20
  @refresher = initialize_refresher(config.refresh_rate)
23
21
  end
@@ -25,39 +23,41 @@ module Sidekiq
25
23
  def each(&block)
26
24
  return to_enum __method__ unless block
27
25
 
28
- @mutex.synchronize { @queues.dup }.each(&block)
26
+ @mutex.synchronize { @names.dup }.each(&block)
29
27
 
30
28
  self
31
29
  end
32
30
 
33
- def pause!(queue)
34
- queue = normalize_queue_name(queue)
35
-
36
- Sidekiq.redis { |conn| Adapters[conn].pause!(conn, @redis_key, queue) }
31
+ # @param name [#to_s]
32
+ def pause!(name)
33
+ Sidekiq.redis { |conn| Adapters[conn].add(conn, @redis_key, name.to_s) }
37
34
 
38
35
  refresh
39
36
  end
40
37
 
41
- def unpause!(queue)
42
- queue = normalize_queue_name(queue)
43
-
44
- Sidekiq.redis { |conn| Adapters[conn].unpause!(conn, @redis_key, queue) }
38
+ # @param name [#to_s]
39
+ def unpause!(name)
40
+ Sidekiq.redis { |conn| Adapters[conn].remove(conn, @redis_key, name.to_s) }
45
41
 
46
42
  refresh
47
43
  end
48
44
 
49
- def paused?(queue)
50
- include?(normalize_queue_name(queue))
45
+ # @param name [#to_s]
46
+ # @return [Boolean]
47
+ def paused?(name)
48
+ include?(name.to_s)
51
49
  end
52
50
 
53
51
  def start_refresher
54
52
  @refresher.execute
55
- nil
53
+
54
+ self
56
55
  end
57
56
 
58
57
  def stop_refresher
59
58
  @refresher.shutdown
60
- nil
59
+
60
+ self
61
61
  end
62
62
 
63
63
  def refresher_running?
@@ -74,19 +74,13 @@ module Sidekiq
74
74
 
75
75
  def refresh
76
76
  @mutex.synchronize do
77
- paused_queues = Sidekiq.redis do |conn|
78
- Adapters[conn].paused_queues(conn, @redis_key)
79
- end
77
+ names = Sidekiq.redis { |conn| Adapters[conn].list(conn, @redis_key) }
80
78
 
81
- @queues.replace(paused_queues)
79
+ @names.replace(names)
82
80
  end
83
81
 
84
82
  self
85
83
  end
86
-
87
- def normalize_queue_name(queue)
88
- queue.dup.delete_prefix(QUEUE_PREFIX)
89
- end
90
84
  end
91
85
  end
92
86
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq"
4
+
5
+ module Sidekiq
6
+ module Pauzer
7
+ module Runtime
8
+ SIDEKIQ_SEVEN = Gem::Version.new("7.0.0") <= Gem::Version.new(Sidekiq::VERSION)
9
+ end
10
+ end
11
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sidekiq
4
4
  module Pauzer
5
- VERSION = "1.0.0.alpha"
5
+ VERSION = "1.1.0"
6
6
  end
7
7
  end
@@ -1,46 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "erb"
4
- require "sidekiq"
5
- require "sidekiq/web"
6
-
7
- require_relative "../pauzer"
3
+ require_relative "./patches/web_action"
4
+ require_relative "./patches/web_application"
8
5
 
9
6
  module Sidekiq
10
7
  module Pauzer
11
- def self.unpatch_views!
12
- WebAction.remove_method(:_erb_queues)
13
- end
14
- end
15
-
16
- class WebApplication
17
- @routes[Sidekiq::WebRouter::POST].delete_if do |web_route|
18
- web_route.pattern == "/queues/:name"
19
- end
20
-
21
- post "/queues/:name" do
22
- queue = Sidekiq::Queue.new(route_params[:name])
23
-
24
- if params["pause"]
25
- queue.pause!
26
- elsif params["unpause"]
27
- queue.unpause!
28
- else
29
- queue.clear
8
+ module Web
9
+ def self.unpatch_views!
10
+ Patches::WebAction.revert!
30
11
  end
31
-
32
- redirect "#{root_path}queues"
33
12
  end
34
13
  end
35
-
36
- class WebAction
37
- PAUZER_QUEUES_TEMPLATE =
38
- ERB.new(File.read(File.expand_path("../../../web/views/queues.erb", __dir__))).src
39
-
40
- class_eval <<-RUBY, __FILE__, __LINE__ + 1 # rubocop:disable Style/DocumentDynamicEvalDefinition
41
- def _erb_queues
42
- #{PAUZER_QUEUES_TEMPLATE}
43
- end
44
- RUBY
45
- end
46
14
  end
15
+
16
+ Sidekiq::Pauzer::Patches::WebAction.apply!
17
+ Sidekiq::Pauzer::Patches::WebApplication.apply!
@@ -6,6 +6,7 @@ require "sidekiq/api"
6
6
 
7
7
  require_relative "./pauzer/basic_fetch"
8
8
  require_relative "./pauzer/config"
9
+ require_relative "./pauzer/patches/queue"
9
10
  require_relative "./pauzer/queues"
10
11
  require_relative "./pauzer/version"
11
12
 
@@ -28,57 +29,123 @@ module Sidekiq
28
29
  module Pauzer
29
30
  MUTEX = Mutex.new
30
31
 
31
- @config = Config.new
32
+ @config = Config.new.freeze
32
33
  @queues = Queues.new(@config)
33
34
 
34
35
  class << self
35
36
  extend Forwardable
36
37
 
37
- def_delegators :@queues, :pause!, :unpause!, :paused?
38
+ # @!attribute [r] redis_key
39
+ # @see Config#redis_key
40
+ # @return [String]
41
+ def_delegators :@config, :redis_key
42
+
43
+ # @example
44
+ # Sidekiq::Pauzer.pause!("minor")
45
+ # Sidekiq::Pauzer.paused?("minor") # => true
46
+ #
47
+ # @param (see Queues#pause!)
48
+ # @return [void]
49
+ def pause!(name)
50
+ @queues.pause!(name)
51
+
52
+ nil
53
+ end
54
+
55
+ # @example
56
+ # Sidekiq::Pauzer.pause!("minor")
57
+ # Sidekiq::Pauzer.paused?("minor") # => true
58
+ # Sidekiq::Pauzer.unpause!("minor")
59
+ # Sidekiq::Pauzer.paused?("minor") # => false
60
+ #
61
+ # @param (see Queues#unpause!)
62
+ # @return [void]
63
+ def unpause!(name)
64
+ @queues.unpause!(name)
65
+
66
+ nil
67
+ end
68
+
69
+ # @example
70
+ # Sidekiq::Pauzer.pause!("minor")
71
+ # Sidekiq::Pauzer.paused?("minor") # => true
72
+ # Sidekiq::Pauzer.paused?("threat") # => false
73
+ #
74
+ # @see Queues#paused?
75
+ def paused?(name)
76
+ @queues.paused?(name)
77
+ end
38
78
 
79
+ # @example
80
+ # Sidekiq::Pauzer.pause!("minor")
81
+ # Sidekiq::Pauzer.paused_queue_names # => ["minor"]
82
+ #
83
+ # @return [Array<String>]
84
+ def paused_queue_names
85
+ @queues.to_a
86
+ end
87
+
88
+ # @deprecated Use {.paused_queue_names} instead.
89
+ # Will be removed in ‹2.0.0›.
90
+ #
91
+ # @example
92
+ # Sidekiq::Pauzer.pause!("minor")
93
+ # Sidekiq::Pauzer.paused_queues # => ["queue:minor"]
94
+ #
95
+ # @return [Array<String>]
39
96
  def paused_queues
40
- @queues.map { |queue| "#{Queues::QUEUE_PREFIX}#{queue}" }
97
+ @queues.map { |name| "queue:#{name}" }
41
98
  end
42
99
 
100
+ # Yields `config` for a block.
101
+ #
102
+ # @example
103
+ # Sidekiq::Pauzer.configure do |config|
104
+ # config.refresh_rate = 42
105
+ # end
106
+ #
107
+ # @yieldparam config [Config]
43
108
  def configure
44
109
  MUTEX.synchronize do
45
- yield @config
110
+ config = @config.dup
111
+
112
+ yield config
113
+
114
+ @config = config.freeze
115
+
116
+ self
46
117
  ensure
47
- start_refresher = @queues.refresher_running?
48
- @queues.stop_refresher
49
- @queues = Queues.new(@config)
50
- @queues.start_refresher if start_refresher
118
+ reinit_queues
51
119
  end
52
120
  end
53
121
 
54
122
  def startup
55
123
  MUTEX.synchronize { @queues.start_refresher }
124
+
125
+ self
56
126
  end
57
127
 
58
128
  def shutdown
59
129
  MUTEX.synchronize { @queues.stop_refresher }
60
- end
61
- end
62
- end
63
-
64
- class Queue
65
- remove_method :paused?
66
130
 
67
- def paused?
68
- Pauzer.paused?(name)
69
- end
131
+ self
132
+ end
70
133
 
71
- def pause!
72
- Pauzer.pause!(name)
73
- end
134
+ private
74
135
 
75
- def unpause!
76
- Pauzer.unpause!(name)
136
+ def reinit_queues
137
+ start_refresher = @queues.refresher_running?
138
+ @queues.stop_refresher
139
+ @queues = Queues.new(@config)
140
+ @queues.start_refresher if start_refresher
141
+ end
77
142
  end
78
143
  end
79
144
 
80
145
  configure_server do |config|
81
- config.on(:startup) { Pauzer.startup }
146
+ config.on(:startup) { Pauzer.startup }
82
147
  config.on(:shutdown) { Pauzer.shutdown }
83
148
  end
84
149
  end
150
+
151
+ Sidekiq::Pauzer::Patches::Queue.apply!
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-pauzer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.alpha
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexey Zapparov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-05-03 00:00:00.000000000 Z
11
+ date: 2023-05-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -53,7 +53,11 @@ files:
53
53
  - lib/sidekiq/pauzer/adapters/redis_client.rb
54
54
  - lib/sidekiq/pauzer/basic_fetch.rb
55
55
  - lib/sidekiq/pauzer/config.rb
56
+ - lib/sidekiq/pauzer/patches/queue.rb
57
+ - lib/sidekiq/pauzer/patches/web_action.rb
58
+ - lib/sidekiq/pauzer/patches/web_application.rb
56
59
  - lib/sidekiq/pauzer/queues.rb
60
+ - lib/sidekiq/pauzer/runtime.rb
57
61
  - lib/sidekiq/pauzer/version.rb
58
62
  - lib/sidekiq/pauzer/web.rb
59
63
  - web/views/queues.erb
@@ -62,9 +66,9 @@ licenses:
62
66
  - MIT
63
67
  metadata:
64
68
  homepage_uri: https://gitlab.com/ixti/sidekiq-pauzer
65
- source_code_uri: https://gitlab.com/ixti/sidekiq-pauzer/tree/v1.0.0.alpha
69
+ source_code_uri: https://gitlab.com/ixti/sidekiq-pauzer/tree/v1.1.0
66
70
  bug_tracker_uri: https://gitlab.com/ixti/sidekiq-pauzer/issues
67
- changelog_uri: https://gitlab.com/ixti/sidekiq-pauzer/blob/v1.0.0.alpha/CHANGES.md
71
+ changelog_uri: https://gitlab.com/ixti/sidekiq-pauzer/blob/v1.1.0/CHANGES.md
68
72
  rubygems_mfa_required: 'true'
69
73
  post_install_message:
70
74
  rdoc_options: []
@@ -77,9 +81,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
77
81
  version: '2.7'
78
82
  required_rubygems_version: !ruby/object:Gem::Requirement
79
83
  requirements:
80
- - - ">"
84
+ - - ">="
81
85
  - !ruby/object:Gem::Version
82
- version: 1.3.1
86
+ version: '0'
83
87
  requirements: []
84
88
  rubygems_version: 3.4.10
85
89
  signing_key: