gorails 0.1.2 → 0.1.3

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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -1
  3. data/Gemfile.lock +1 -1
  4. data/bin/update-deps +95 -0
  5. data/exe/gorails +3 -2
  6. data/lib/gorails/commands/railsbytes.rb +10 -10
  7. data/lib/gorails/commands.rb +1 -4
  8. data/lib/gorails/version.rb +1 -1
  9. data/lib/gorails.rb +11 -20
  10. data/vendor/deps/cli-kit/REVISION +1 -0
  11. data/vendor/deps/cli-kit/lib/cli/kit/args/definition.rb +301 -0
  12. data/vendor/deps/cli-kit/lib/cli/kit/args/evaluation.rb +237 -0
  13. data/vendor/deps/cli-kit/lib/cli/kit/args/parser/node.rb +131 -0
  14. data/vendor/deps/cli-kit/lib/cli/kit/args/parser.rb +128 -0
  15. data/vendor/deps/cli-kit/lib/cli/kit/args/tokenizer.rb +132 -0
  16. data/vendor/deps/cli-kit/lib/cli/kit/args.rb +15 -0
  17. data/vendor/deps/cli-kit/lib/cli/kit/base_command.rb +29 -0
  18. data/vendor/deps/cli-kit/lib/cli/kit/command_help.rb +256 -0
  19. data/vendor/deps/cli-kit/lib/cli/kit/command_registry.rb +141 -0
  20. data/vendor/deps/cli-kit/lib/cli/kit/config.rb +137 -0
  21. data/vendor/deps/cli-kit/lib/cli/kit/core_ext.rb +30 -0
  22. data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +165 -0
  23. data/vendor/deps/cli-kit/lib/cli/kit/executor.rb +99 -0
  24. data/vendor/deps/cli-kit/lib/cli/kit/ini.rb +94 -0
  25. data/vendor/deps/cli-kit/lib/cli/kit/levenshtein.rb +89 -0
  26. data/vendor/deps/cli-kit/lib/cli/kit/logger.rb +95 -0
  27. data/vendor/deps/cli-kit/lib/cli/kit/opts.rb +284 -0
  28. data/vendor/deps/cli-kit/lib/cli/kit/resolver.rb +67 -0
  29. data/vendor/deps/cli-kit/lib/cli/kit/sorbet_runtime_stub.rb +142 -0
  30. data/vendor/deps/cli-kit/lib/cli/kit/support/test_helper.rb +253 -0
  31. data/vendor/deps/cli-kit/lib/cli/kit/support.rb +10 -0
  32. data/vendor/deps/cli-kit/lib/cli/kit/system.rb +350 -0
  33. data/vendor/deps/cli-kit/lib/cli/kit/util.rb +133 -0
  34. data/vendor/deps/cli-kit/lib/cli/kit/version.rb +7 -0
  35. data/vendor/deps/cli-kit/lib/cli/kit.rb +151 -0
  36. data/vendor/deps/cli-ui/REVISION +1 -0
  37. data/vendor/deps/cli-ui/lib/cli/ui/ansi.rb +180 -0
  38. data/vendor/deps/cli-ui/lib/cli/ui/color.rb +98 -0
  39. data/vendor/deps/cli-ui/lib/cli/ui/formatter.rb +216 -0
  40. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_stack.rb +116 -0
  41. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/box.rb +176 -0
  42. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/bracket.rb +149 -0
  43. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style.rb +112 -0
  44. data/vendor/deps/cli-ui/lib/cli/ui/frame.rb +300 -0
  45. data/vendor/deps/cli-ui/lib/cli/ui/glyph.rb +92 -0
  46. data/vendor/deps/cli-ui/lib/cli/ui/os.rb +58 -0
  47. data/vendor/deps/cli-ui/lib/cli/ui/printer.rb +72 -0
  48. data/vendor/deps/cli-ui/lib/cli/ui/progress.rb +102 -0
  49. data/vendor/deps/cli-ui/lib/cli/ui/prompt/interactive_options.rb +534 -0
  50. data/vendor/deps/cli-ui/lib/cli/ui/prompt/options_handler.rb +36 -0
  51. data/vendor/deps/cli-ui/lib/cli/ui/prompt.rb +354 -0
  52. data/vendor/deps/cli-ui/lib/cli/ui/sorbet_runtime_stub.rb +143 -0
  53. data/vendor/deps/cli-ui/lib/cli/ui/spinner/async.rb +46 -0
  54. data/vendor/deps/cli-ui/lib/cli/ui/spinner/spin_group.rb +292 -0
  55. data/vendor/deps/cli-ui/lib/cli/ui/spinner.rb +82 -0
  56. data/vendor/deps/cli-ui/lib/cli/ui/stdout_router.rb +264 -0
  57. data/vendor/deps/cli-ui/lib/cli/ui/terminal.rb +53 -0
  58. data/vendor/deps/cli-ui/lib/cli/ui/truncater.rb +107 -0
  59. data/vendor/deps/cli-ui/lib/cli/ui/version.rb +6 -0
  60. data/vendor/deps/cli-ui/lib/cli/ui/widgets/base.rb +37 -0
  61. data/vendor/deps/cli-ui/lib/cli/ui/widgets/status.rb +75 -0
  62. data/vendor/deps/cli-ui/lib/cli/ui/widgets.rb +91 -0
  63. data/vendor/deps/cli-ui/lib/cli/ui/wrap.rb +63 -0
  64. data/vendor/deps/cli-ui/lib/cli/ui.rb +356 -0
  65. metadata +57 -1
@@ -0,0 +1,354 @@
1
+ # coding: utf-8
2
+
3
+ # typed: true
4
+
5
+ require 'cli/ui'
6
+ require 'readline'
7
+
8
+ module Readline
9
+ unless const_defined?(:FILENAME_COMPLETION_PROC)
10
+ FILENAME_COMPLETION_PROC = proc do |input|
11
+ directory = input[-1] == '/' ? input : File.dirname(input)
12
+ filename = input[-1] == '/' ? '' : File.basename(input)
13
+
14
+ (Dir.entries(directory).select do |fp|
15
+ fp.start_with?(filename)
16
+ end - (input[-1] == '.' ? [] : ['.', '..'])).map do |fp|
17
+ File.join(directory, fp).gsub(/\A\.\//, '')
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ module CLI
24
+ module UI
25
+ module Prompt
26
+ autoload :InteractiveOptions, 'cli/ui/prompt/interactive_options'
27
+ autoload :OptionsHandler, 'cli/ui/prompt/options_handler'
28
+
29
+ class << self
30
+ extend T::Sig
31
+
32
+ # Ask a user a question with either free form answer or a set of answers (multiple choice)
33
+ # Can use arrows, y/n, numbers (1/2), and vim bindings to control multiple choice selection
34
+ # Do not use this method for yes/no questions. Use +confirm+
35
+ #
36
+ # * Handles free form answers (options are nil)
37
+ # * Handles default answers for free form text
38
+ # * Handles file auto completion for file input
39
+ # * Handles interactively choosing answers using +InteractiveOptions+
40
+ #
41
+ # https://user-images.githubusercontent.com/3074765/33799822-47f23302-dd01-11e7-82f3-9072a5a5f611.png
42
+ #
43
+ # ==== Attributes
44
+ #
45
+ # * +question+ - (required) The question to ask the user
46
+ #
47
+ # ==== Options
48
+ #
49
+ # * +:options+ - Options that the user may select from. Will use +InteractiveOptions+ to do so.
50
+ # * +:default+ - The default answer to the question (e.g. they just press enter and don't input anything)
51
+ # * +:is_file+ - Tells the input to use file auto-completion (tab completion)
52
+ # * +:allow_empty+ - Allows the answer to be empty
53
+ # * +:multiple+ - Allow multiple options to be selected
54
+ # * +:filter_ui+ - Enable option filtering (default: true)
55
+ # * +:select_ui+ - Enable long-form option selection (default: true)
56
+ #
57
+ # Note:
58
+ # * +:options+ or providing a +Block+ conflicts with +:default+ and +:is_file+,
59
+ # you cannot set options with either of these keywords
60
+ # * +:default+ conflicts with +:allow_empty:, you cannot set these together
61
+ # * +:options+ conflicts with providing a +Block+ , you may only set one
62
+ # * +:multiple+ can only be used with +:options+ or a +Block+; it is ignored, otherwise.
63
+ #
64
+ # ==== Block (optional)
65
+ #
66
+ # * A Proc that provides a +OptionsHandler+ and uses the public +:option+ method to add options and their
67
+ # respective handlers
68
+ #
69
+ # ==== Return Value
70
+ #
71
+ # * If a +Block+ was not provided, the selected option or response to the free form question will be returned
72
+ # * If a +Block+ was provided, the evaluated value of the +Block+ will be returned
73
+ #
74
+ # ==== Example Usage:
75
+ #
76
+ # Free form question
77
+ # CLI::UI::Prompt.ask('What color is the sky?')
78
+ #
79
+ # Free form question with a file answer
80
+ # CLI::UI::Prompt.ask('Where is your Gemfile located?', is_file: true)
81
+ #
82
+ # Free form question with a default answer
83
+ # CLI::UI::Prompt.ask('What color is the sky?', default: 'blue')
84
+ #
85
+ # Free form question when the answer can be empty
86
+ # CLI::UI::Prompt.ask('What is your opinion on this question?', allow_empty: true)
87
+ #
88
+ # Interactive (multiple choice) question
89
+ # CLI::UI::Prompt.ask('What kind of project is this?', options: %w(rails go ruby python))
90
+ #
91
+ # Interactive (multiple choice) question with defined handlers
92
+ # CLI::UI::Prompt.ask('What kind of project is this?') do |handler|
93
+ # handler.option('rails') { |selection| selection }
94
+ # handler.option('go') { |selection| selection }
95
+ # handler.option('ruby') { |selection| selection }
96
+ # handler.option('python') { |selection| selection }
97
+ # end
98
+ #
99
+ sig do
100
+ params(
101
+ question: String,
102
+ options: T.nilable(T::Array[String]),
103
+ default: T.nilable(T.any(String, T::Array[String])),
104
+ is_file: T::Boolean,
105
+ allow_empty: T::Boolean,
106
+ multiple: T::Boolean,
107
+ filter_ui: T::Boolean,
108
+ select_ui: T::Boolean,
109
+ options_proc: T.nilable(T.proc.params(handler: OptionsHandler).void)
110
+ ).returns(T.any(String, T::Array[String]))
111
+ end
112
+ def ask(
113
+ question,
114
+ options: nil,
115
+ default: nil,
116
+ is_file: false,
117
+ allow_empty: true,
118
+ multiple: false,
119
+ filter_ui: true,
120
+ select_ui: true,
121
+ &options_proc
122
+ )
123
+ has_options = !!(options || block_given?)
124
+ if has_options && default && !multiple
125
+ raise(ArgumentError, 'conflicting arguments: default may not be provided with options when not multiple')
126
+ end
127
+
128
+ if has_options && is_file
129
+ raise(ArgumentError, 'conflicting arguments: is_file is only useful when options are not provided')
130
+ end
131
+
132
+ if options && multiple && default && !(Array(default) - options).empty?
133
+ raise(ArgumentError, 'conflicting arguments: default should only include elements present in options')
134
+ end
135
+
136
+ if multiple && !has_options
137
+ raise(ArgumentError, 'conflicting arguments: options must be provided when multiple is true')
138
+ end
139
+
140
+ if !multiple && default.is_a?(Array)
141
+ raise(ArgumentError, 'conflicting arguments: multiple defaults may only be provided when multiple is true')
142
+ end
143
+
144
+ if has_options
145
+ ask_interactive(
146
+ question,
147
+ options,
148
+ multiple: multiple,
149
+ default: default,
150
+ filter_ui: filter_ui,
151
+ select_ui: select_ui,
152
+ &options_proc
153
+ )
154
+ else
155
+ ask_free_form(question, T.cast(default, T.nilable(String)), is_file, allow_empty)
156
+ end
157
+ end
158
+
159
+ # Asks the user for a single-line answer, without displaying the characters while typing.
160
+ # Typically used for password prompts
161
+ #
162
+ # ==== Return Value
163
+ #
164
+ # The password, without a trailing newline.
165
+ # If the user simply presses "Enter" without typing any password, this will return an empty string.
166
+ sig { params(question: String).returns(String) }
167
+ def ask_password(question)
168
+ require 'io/console'
169
+
170
+ CLI::UI.with_frame_color(:blue) do
171
+ STDOUT.print(CLI::UI.fmt('{{?}} ' + question)) # Do not use puts_question to avoid the new line.
172
+
173
+ # noecho interacts poorly with Readline under system Ruby, so do a manual `gets` here.
174
+ # No fancy Readline integration (like echoing back) is required for a password prompt anyway.
175
+ password = STDIN.noecho do
176
+ # Chomp will remove the one new line character added by `gets`, without touching potential extra spaces:
177
+ # " 123 \n".chomp => " 123 "
178
+ T.must(STDIN.gets).chomp
179
+ end
180
+
181
+ STDOUT.puts # Complete the line
182
+
183
+ password
184
+ end
185
+ end
186
+
187
+ # Asks the user a yes/no question.
188
+ # Can use arrows, y/n, numbers (1/2), and vim bindings to control
189
+ #
190
+ # ==== Example Usage:
191
+ #
192
+ # Confirmation question
193
+ # CLI::UI::Prompt.confirm('Is the sky blue?')
194
+ #
195
+ # CLI::UI::Prompt.confirm('Do a dangerous thing?', default: false)
196
+ #
197
+ sig { params(question: String, default: T::Boolean).returns(T::Boolean) }
198
+ def confirm(question, default: true)
199
+ ask_interactive(question, default ? ['yes', 'no'] : ['no', 'yes'], filter_ui: false) == 'yes'
200
+ end
201
+
202
+ private
203
+
204
+ sig do
205
+ params(question: String, default: T.nilable(String), is_file: T::Boolean, allow_empty: T::Boolean)
206
+ .returns(String)
207
+ end
208
+ def ask_free_form(question, default, is_file, allow_empty)
209
+ if default && !allow_empty
210
+ raise(ArgumentError, 'conflicting arguments: default enabled but allow_empty is false')
211
+ end
212
+
213
+ if default
214
+ puts_question("#{question} (empty = #{default})")
215
+ else
216
+ puts_question(question)
217
+ end
218
+
219
+ # Ask a free form question
220
+ loop do
221
+ line = readline(is_file: is_file)
222
+
223
+ if line.empty? && default
224
+ write_default_over_empty_input(default)
225
+ return default
226
+ end
227
+
228
+ if !line.empty? || allow_empty
229
+ return line
230
+ end
231
+ end
232
+ end
233
+
234
+ sig do
235
+ params(
236
+ question: String,
237
+ options: T.nilable(T::Array[String]),
238
+ multiple: T::Boolean,
239
+ default: T.nilable(T.any(String, T::Array[String])),
240
+ filter_ui: T::Boolean,
241
+ select_ui: T::Boolean
242
+ ).returns(T.any(String, T::Array[String]))
243
+ end
244
+ def ask_interactive(question, options = nil, multiple: false, default: nil, filter_ui: true, select_ui: true)
245
+ raise(ArgumentError, 'conflicting arguments: options and block given') if options && block_given?
246
+
247
+ options ||= if block_given?
248
+ handler = OptionsHandler.new
249
+ yield handler
250
+ handler.options
251
+ end
252
+
253
+ raise(ArgumentError, 'insufficient options') if options.nil? || options.empty?
254
+
255
+ navigate_text = if CLI::UI::OS.current.suggest_arrow_keys?
256
+ 'Choose with ↑ ↓ ⏎'
257
+ else
258
+ "Navigate up with 'k' and down with 'j', press Enter to select"
259
+ end
260
+
261
+ instructions = (multiple ? 'Toggle options. ' : '') + navigate_text
262
+ instructions += ", filter with 'f'" if filter_ui
263
+ instructions += ", enter option with 'e'" if select_ui && (options.size > 9)
264
+ puts_question("#{question} {{yellow:(#{instructions})}}")
265
+ resp = interactive_prompt(options, multiple: multiple, default: default)
266
+
267
+ # Clear the line
268
+ print(ANSI.previous_line + ANSI.clear_to_end_of_line)
269
+ # Force StdoutRouter to prefix
270
+ print(ANSI.previous_line + "\n")
271
+
272
+ # reset the question to include the answer
273
+ resp_text = case resp
274
+ when Array
275
+ case resp.size
276
+ when 0
277
+ '<nothing>'
278
+ when 1..2
279
+ resp.join(' and ')
280
+ else
281
+ "#{resp.size} items"
282
+ end
283
+ else
284
+ resp
285
+ end
286
+ puts_question("#{question} (You chose: {{italic:#{resp_text}}})")
287
+
288
+ return T.must(handler).call(resp) if block_given?
289
+
290
+ resp
291
+ end
292
+
293
+ # Useful for stubbing in tests
294
+ sig do
295
+ params(options: T::Array[String], multiple: T::Boolean, default: T.nilable(T.any(T::Array[String], String)))
296
+ .returns(T.any(T::Array[String], String))
297
+ end
298
+ def interactive_prompt(options, multiple: false, default: nil)
299
+ InteractiveOptions.call(options, multiple: multiple, default: default)
300
+ end
301
+
302
+ sig { params(default: String).void }
303
+ def write_default_over_empty_input(default)
304
+ CLI::UI.raw do
305
+ STDERR.puts(
306
+ CLI::UI::ANSI.cursor_up(1) +
307
+ "\r" +
308
+ CLI::UI::ANSI.cursor_forward(4) + # TODO: width
309
+ default +
310
+ CLI::UI::Color::RESET.code
311
+ )
312
+ end
313
+ end
314
+
315
+ sig { params(str: String).void }
316
+ def puts_question(str)
317
+ CLI::UI.with_frame_color(:blue) do
318
+ STDOUT.puts(CLI::UI.fmt('{{?}} ' + str))
319
+ end
320
+ end
321
+
322
+ sig { params(is_file: T::Boolean).returns(String) }
323
+ def readline(is_file: false)
324
+ if is_file
325
+ Readline.completion_proc = Readline::FILENAME_COMPLETION_PROC
326
+ Readline.completion_append_character = ''
327
+ else
328
+ Readline.completion_proc = proc { |*| nil }
329
+ Readline.completion_append_character = ' '
330
+ end
331
+
332
+ # because Readline is a C library, CLI::UI's hooks into $stdout don't
333
+ # work. We could work around this by having CLI::UI use a pipe and a
334
+ # thread to manage output, but the current strategy feels like a
335
+ # better tradeoff.
336
+ prefix = CLI::UI.with_frame_color(:blue) { CLI::UI::Frame.prefix }
337
+ # If a prompt is interrupted on Windows it locks the colour of the terminal from that point on, so we should
338
+ # not change the colour here.
339
+ prompt = prefix + CLI::UI.fmt('{{blue:> }}')
340
+ prompt += CLI::UI::Color::YELLOW.code if CLI::UI::OS.current.use_color_prompt?
341
+
342
+ begin
343
+ line = Readline.readline(prompt, true)
344
+ print(CLI::UI::Color::RESET.code)
345
+ line.to_s.chomp
346
+ rescue Interrupt
347
+ CLI::UI.raw { STDERR.puts('^C' + CLI::UI::Color::RESET.code) }
348
+ raise
349
+ end
350
+ end
351
+ end
352
+ end
353
+ end
354
+ end
@@ -0,0 +1,143 @@
1
+ # typed: ignore
2
+ # frozen_string_literal: true
3
+
4
+ module T
5
+ class << self
6
+ def absurd(value); end
7
+ def all(type_a, type_b, *types); end
8
+ def any(type_a, type_b, *types); end
9
+ def attached_class; end
10
+ def class_of(klass); end
11
+ def enum(values); end
12
+ def nilable(type); end
13
+ def noreturn; end
14
+ def self_type; end
15
+ def type_alias(type = nil, &_blk); end
16
+ def type_parameter(name); end
17
+ def untyped; end
18
+
19
+ def assert_type!(value, _type, _checked: true)
20
+ value
21
+ end
22
+
23
+ def cast(value, _type, _checked: true)
24
+ value
25
+ end
26
+
27
+ def let(value, _type, _checked: true)
28
+ value
29
+ end
30
+
31
+ def must(arg, _msg = nil)
32
+ arg
33
+ end
34
+
35
+ def proc
36
+ T::Proc.new
37
+ end
38
+
39
+ def reveal_type(value)
40
+ value
41
+ end
42
+
43
+ def unsafe(value)
44
+ value
45
+ end
46
+ end
47
+
48
+ module Sig
49
+ def sig(arg0 = nil, &blk); end
50
+ end
51
+
52
+ module Helpers
53
+ def abstract!; end
54
+ def interface!; end
55
+ def final!; end
56
+ def sealed!; end
57
+ def mixes_in_class_methods(mod); end
58
+ end
59
+
60
+ module Generic
61
+ include(T::Helpers)
62
+
63
+ def type_parameters(*params); end
64
+ def type_member(variance = :invariant, fixed: nil, lower: nil, upper: BasicObject); end
65
+ def type_template(variance = :invariant, fixed: nil, lower: nil, upper: BasicObject); end
66
+
67
+ def [](*types)
68
+ self
69
+ end
70
+ end
71
+
72
+ module Array
73
+ def self.[](type); end
74
+ end
75
+
76
+ Boolean = Object.new.freeze
77
+
78
+ module Configuration
79
+ def self.call_validation_error_handler(signature, opts); end
80
+ def self.call_validation_error_handler=(value); end
81
+ def self.default_checked_level=(default_checked_level); end
82
+ def self.enable_checking_for_sigs_marked_checked_tests; end
83
+ def self.enable_final_checks_on_hooks; end
84
+ def self.enable_legacy_t_enum_migration_mode; end
85
+ def self.reset_final_checks_on_hooks; end
86
+ def self.hard_assert_handler(str, extra); end
87
+ def self.hard_assert_handler=(value); end
88
+ def self.inline_type_error_handler(error); end
89
+ def self.inline_type_error_handler=(value); end
90
+ def self.log_info_handler(str, extra); end
91
+ def self.log_info_handler=(value); end
92
+ def self.scalar_types; end
93
+ def self.scalar_types=(values); end
94
+ # rubocop:disable Naming/InclusiveLanguage
95
+ def self.sealed_violation_whitelist; end
96
+ def self.sealed_violation_whitelist=(sealed_violation_whitelist); end
97
+ # rubocop:enable Naming/InclusiveLanguage
98
+ def self.sig_builder_error_handler(error, location); end
99
+ def self.sig_builder_error_handler=(value); end
100
+ def self.sig_validation_error_handler(error, opts); end
101
+ def self.sig_validation_error_handler=(value); end
102
+ def self.soft_assert_handler(str, extra); end
103
+ def self.soft_assert_handler=(value); end
104
+ end
105
+
106
+ module Enumerable
107
+ def self.[](type); end
108
+ end
109
+
110
+ module Enumerator
111
+ def self.[](type); end
112
+ end
113
+
114
+ module Hash
115
+ def self.[](keys, values); end
116
+ end
117
+
118
+ class Proc
119
+ def bind(*_)
120
+ self
121
+ end
122
+
123
+ def params(*_param)
124
+ self
125
+ end
126
+
127
+ def void
128
+ self
129
+ end
130
+
131
+ def returns(_type)
132
+ self
133
+ end
134
+ end
135
+
136
+ module Range
137
+ def self.[](type); end
138
+ end
139
+
140
+ module Set
141
+ def self.[](type); end
142
+ end
143
+ end
@@ -0,0 +1,46 @@
1
+ # typed: true
2
+ module CLI
3
+ module UI
4
+ module Spinner
5
+ class Async
6
+ extend T::Sig
7
+
8
+ # Convenience method for +initialize+
9
+ #
10
+ sig { params(title: String).returns(Async) }
11
+ def self.start(title)
12
+ new(title)
13
+ end
14
+
15
+ # Initializes a new asynchronous spinner with no specific end.
16
+ # Must call +.stop+ to end the spinner
17
+ #
18
+ # ==== Attributes
19
+ #
20
+ # * +title+ - Title of the spinner to use
21
+ #
22
+ # ==== Example Usage:
23
+ #
24
+ # CLI::UI::Spinner::Async.new('Title')
25
+ #
26
+ sig { params(title: String).void }
27
+ def initialize(title)
28
+ require 'thread'
29
+ sg = CLI::UI::Spinner::SpinGroup.new
30
+ @m = Mutex.new
31
+ @cv = ConditionVariable.new
32
+ sg.add(title) { @m.synchronize { @cv.wait(@m) } }
33
+ @t = Thread.new { sg.wait }
34
+ end
35
+
36
+ # Stops an asynchronous spinner
37
+ #
38
+ sig { returns(T::Boolean) }
39
+ def stop
40
+ @m.synchronize { @cv.signal }
41
+ @t.value
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end