cli-ui 2.3.1 → 2.6.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 +4 -4
- data/lib/cli/ui/ansi.rb +23 -27
- data/lib/cli/ui/color.rb +8 -13
- data/lib/cli/ui/formatter.rb +24 -23
- data/lib/cli/ui/frame/frame_stack.rb +9 -21
- data/lib/cli/ui/frame/frame_style/box.rb +11 -10
- data/lib/cli/ui/frame/frame_style/bracket.rb +11 -10
- data/lib/cli/ui/frame/frame_style.rb +31 -22
- data/lib/cli/ui/frame.rb +12 -43
- data/lib/cli/ui/glyph.rb +8 -14
- data/lib/cli/ui/os.rb +9 -10
- data/lib/cli/ui/printer.rb +1 -15
- data/lib/cli/ui/progress.rb +20 -23
- data/lib/cli/ui/progress_reporter.rb +209 -0
- data/lib/cli/ui/prompt/interactive_options.rb +85 -53
- data/lib/cli/ui/prompt/options_handler.rb +4 -6
- data/lib/cli/ui/prompt.rb +22 -45
- data/lib/cli/ui/spinner/async.rb +3 -7
- data/lib/cli/ui/spinner/spin_group.rb +205 -135
- data/lib/cli/ui/spinner.rb +3 -16
- data/lib/cli/ui/stdout_router.rb +38 -55
- data/lib/cli/ui/table.rb +3 -7
- data/lib/cli/ui/terminal.rb +4 -8
- data/lib/cli/ui/truncater.rb +5 -6
- data/lib/cli/ui/version.rb +1 -1
- data/lib/cli/ui/widgets/base.rb +13 -14
- data/lib/cli/ui/widgets/status.rb +10 -10
- data/lib/cli/ui/widgets.rb +7 -15
- data/lib/cli/ui/work_queue.rb +23 -27
- data/lib/cli/ui/wrap.rb +3 -5
- data/lib/cli/ui.rb +42 -97
- metadata +4 -7
- data/lib/cli/ui/sorbet_runtime_stub.rb +0 -169
@@ -10,23 +10,17 @@ module CLI
|
|
10
10
|
DEFAULT_FINAL_GLYPH = ->(success) { success ? CLI::UI::Glyph::CHECK : CLI::UI::Glyph::X }
|
11
11
|
|
12
12
|
class << self
|
13
|
-
|
14
|
-
|
15
|
-
sig { returns(Mutex) }
|
13
|
+
#: Mutex
|
16
14
|
attr_reader :pause_mutex
|
17
15
|
|
18
|
-
|
16
|
+
#: -> bool
|
19
17
|
def paused?
|
20
18
|
@paused
|
21
19
|
end
|
22
20
|
|
23
|
-
|
24
|
-
type_parameters(:T)
|
25
|
-
.params(block: T.proc.returns(T.type_parameter(:T)))
|
26
|
-
.returns(T.type_parameter(:T))
|
27
|
-
end
|
21
|
+
#: [T] { -> T } -> T
|
28
22
|
def pause_spinners(&block)
|
29
|
-
previous_paused =
|
23
|
+
previous_paused = nil #: bool?
|
30
24
|
@pause_mutex.synchronize do
|
31
25
|
previous_paused = @paused
|
32
26
|
@paused = true
|
@@ -42,8 +36,6 @@ module CLI
|
|
42
36
|
@pause_mutex = Mutex.new
|
43
37
|
@paused = false
|
44
38
|
|
45
|
-
extend T::Sig
|
46
|
-
|
47
39
|
# Initializes a new spin group
|
48
40
|
# This lets you add +Task+ objects to the group to multi-thread work
|
49
41
|
#
|
@@ -67,15 +59,7 @@ module CLI
|
|
67
59
|
#
|
68
60
|
# https://user-images.githubusercontent.com/3074765/33798558-c452fa26-dce8-11e7-9e90-b4b34df21a46.gif
|
69
61
|
#
|
70
|
-
|
71
|
-
params(
|
72
|
-
auto_debrief: T::Boolean,
|
73
|
-
interrupt_debrief: T::Boolean,
|
74
|
-
max_concurrent: Integer,
|
75
|
-
work_queue: T.nilable(WorkQueue),
|
76
|
-
to: IOLike,
|
77
|
-
).void
|
78
|
-
end
|
62
|
+
#: (?auto_debrief: bool, ?interrupt_debrief: bool, ?max_concurrent: Integer, ?work_queue: WorkQueue?, ?to: io_like) -> void
|
79
63
|
def initialize(auto_debrief: true, interrupt_debrief: false, max_concurrent: 0, work_queue: nil, to: $stdout)
|
80
64
|
@m = Mutex.new
|
81
65
|
@tasks = []
|
@@ -85,10 +69,7 @@ module CLI
|
|
85
69
|
@start = Time.new
|
86
70
|
@stopped = false
|
87
71
|
@internal_work_queue = work_queue.nil?
|
88
|
-
@work_queue =
|
89
|
-
work_queue || WorkQueue.new(max_concurrent.zero? ? 1024 : max_concurrent),
|
90
|
-
WorkQueue,
|
91
|
-
)
|
72
|
+
@work_queue = work_queue || WorkQueue.new(max_concurrent.zero? ? 1024 : max_concurrent) #: WorkQueue
|
92
73
|
if block_given?
|
93
74
|
yield self
|
94
75
|
wait(to: to)
|
@@ -96,20 +77,21 @@ module CLI
|
|
96
77
|
end
|
97
78
|
|
98
79
|
class Task
|
99
|
-
|
100
|
-
|
101
|
-
sig { returns(String) }
|
80
|
+
#: String
|
102
81
|
attr_reader :title, :stdout, :stderr
|
103
82
|
|
104
|
-
|
83
|
+
#: bool
|
105
84
|
attr_reader :success
|
106
85
|
|
107
|
-
|
86
|
+
#: bool
|
108
87
|
attr_reader :done
|
109
88
|
|
110
|
-
|
89
|
+
#: Exception?
|
111
90
|
attr_reader :exception
|
112
91
|
|
92
|
+
#: Integer?
|
93
|
+
attr_reader :progress_percentage
|
94
|
+
|
113
95
|
# Initializes a new Task
|
114
96
|
# This is managed entirely internally by +SpinGroup+
|
115
97
|
#
|
@@ -118,16 +100,7 @@ module CLI
|
|
118
100
|
# * +title+ - Title of the task
|
119
101
|
# * +block+ - Block for the task, will be provided with an instance of the spinner
|
120
102
|
#
|
121
|
-
|
122
|
-
params(
|
123
|
-
title: String,
|
124
|
-
final_glyph: T.proc.params(success: T::Boolean).returns(T.any(Glyph, String)),
|
125
|
-
merged_output: T::Boolean,
|
126
|
-
duplicate_output_to: IO,
|
127
|
-
work_queue: WorkQueue,
|
128
|
-
block: T.proc.params(task: Task).returns(T.untyped),
|
129
|
-
).void
|
130
|
-
end
|
103
|
+
#: (String title, final_glyph: ^(bool success) -> (Glyph | String), merged_output: bool, duplicate_output_to: IO, work_queue: WorkQueue) { (Task task) -> untyped } -> void
|
131
104
|
def initialize(title, final_glyph:, merged_output:, duplicate_output_to:, work_queue:, &block)
|
132
105
|
@title = title
|
133
106
|
@final_glyph = final_glyph
|
@@ -149,16 +122,18 @@ module CLI
|
|
149
122
|
@done = false
|
150
123
|
@exception = nil
|
151
124
|
@success = false
|
125
|
+
@progress_percentage = nil
|
126
|
+
@wants_progress_mode = false
|
152
127
|
end
|
153
128
|
|
154
|
-
|
129
|
+
#: { (Task task) -> void } -> void
|
155
130
|
def on_done(&block)
|
156
131
|
@on_done = block
|
157
132
|
end
|
158
133
|
|
159
134
|
# Checks if a task is finished
|
160
135
|
#
|
161
|
-
|
136
|
+
#: -> bool
|
162
137
|
def check
|
163
138
|
return true if @done
|
164
139
|
return false unless @future.completed?
|
@@ -196,7 +171,7 @@ module CLI
|
|
196
171
|
# * +force+ - force rerender of the task
|
197
172
|
# * +width+ - current terminal width to format for
|
198
173
|
#
|
199
|
-
|
174
|
+
#: (Integer index, ?bool force, ?width: Integer) -> String
|
200
175
|
def render(index, force = true, width: CLI::UI::Terminal.width)
|
201
176
|
@m.synchronize do
|
202
177
|
if !CLI::UI.enable_cursor? || force || @always_full_render || @force_full_render
|
@@ -215,7 +190,7 @@ module CLI
|
|
215
190
|
#
|
216
191
|
# * +title+ - title to change the spinner to
|
217
192
|
#
|
218
|
-
|
193
|
+
#: (String new_title) -> void
|
219
194
|
def update_title(new_title)
|
220
195
|
@m.synchronize do
|
221
196
|
@always_full_render = new_title =~ Formatter::SCAN_WIDGET
|
@@ -224,9 +199,39 @@ module CLI
|
|
224
199
|
end
|
225
200
|
end
|
226
201
|
|
202
|
+
# Set progress percentage (0-100) and switch to progress mode
|
203
|
+
#: (Integer percentage) -> void
|
204
|
+
def set_progress(percentage) # rubocop:disable Naming/AccessorMethodName
|
205
|
+
@m.synchronize do
|
206
|
+
@progress_percentage = percentage.clamp(0, 100)
|
207
|
+
@wants_progress_mode = true
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# Switch back to indeterminate mode
|
212
|
+
#: -> void
|
213
|
+
def clear_progress
|
214
|
+
@m.synchronize do
|
215
|
+
@progress_percentage = nil
|
216
|
+
@wants_progress_mode = false
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# Check if this task wants progress mode
|
221
|
+
#: -> bool
|
222
|
+
def wants_progress_mode?
|
223
|
+
@m.synchronize { @wants_progress_mode }
|
224
|
+
end
|
225
|
+
|
226
|
+
# Get current progress percentage
|
227
|
+
#: -> Integer?
|
228
|
+
def current_progress
|
229
|
+
@m.synchronize { @progress_percentage }
|
230
|
+
end
|
231
|
+
|
227
232
|
private
|
228
233
|
|
229
|
-
|
234
|
+
#: (Integer index, Integer terminal_width) -> String
|
230
235
|
def full_render(index, terminal_width)
|
231
236
|
o = +''
|
232
237
|
|
@@ -242,7 +247,7 @@ module CLI
|
|
242
247
|
o
|
243
248
|
end
|
244
249
|
|
245
|
-
|
250
|
+
#: (Integer index) -> String
|
246
251
|
def partial_render(index)
|
247
252
|
o = +''
|
248
253
|
|
@@ -252,7 +257,7 @@ module CLI
|
|
252
257
|
o
|
253
258
|
end
|
254
259
|
|
255
|
-
|
260
|
+
#: (Integer index) -> String
|
256
261
|
def glyph(index)
|
257
262
|
if @done
|
258
263
|
final_glyph = @final_glyph.call(@success)
|
@@ -272,12 +277,12 @@ module CLI
|
|
272
277
|
end
|
273
278
|
end
|
274
279
|
|
275
|
-
|
280
|
+
#: -> String
|
276
281
|
def inset
|
277
282
|
@inset ||= CLI::UI::Frame.prefix
|
278
283
|
end
|
279
284
|
|
280
|
-
|
285
|
+
#: -> Integer
|
281
286
|
def inset_width
|
282
287
|
@inset_width ||= CLI::UI::ANSI.printing_width(inset)
|
283
288
|
end
|
@@ -295,15 +300,7 @@ module CLI
|
|
295
300
|
# spin_group.add('Title') { |spinner| sleep 1.0 }
|
296
301
|
# spin_group.wait
|
297
302
|
#
|
298
|
-
|
299
|
-
params(
|
300
|
-
title: String,
|
301
|
-
final_glyph: T.proc.params(success: T::Boolean).returns(T.any(Glyph, String)),
|
302
|
-
merged_output: T::Boolean,
|
303
|
-
duplicate_output_to: IO,
|
304
|
-
block: T.proc.params(task: Task).void,
|
305
|
-
).void
|
306
|
-
end
|
303
|
+
#: (String title, ?final_glyph: ^(bool success) -> (Glyph | String), ?merged_output: bool, ?duplicate_output_to: IO) { (Task task) -> void } -> void
|
307
304
|
def add(
|
308
305
|
title,
|
309
306
|
final_glyph: DEFAULT_FINAL_GLYPH,
|
@@ -323,7 +320,7 @@ module CLI
|
|
323
320
|
end
|
324
321
|
end
|
325
322
|
|
326
|
-
|
323
|
+
#: -> void
|
327
324
|
def stop
|
328
325
|
# If we already own the mutex (called from within another synchronized block),
|
329
326
|
# set stopped directly to avoid deadlock
|
@@ -342,7 +339,7 @@ module CLI
|
|
342
339
|
@work_queue.interrupt
|
343
340
|
end
|
344
341
|
|
345
|
-
|
342
|
+
#: -> bool
|
346
343
|
def stopped?
|
347
344
|
if @m.owned?
|
348
345
|
@stopped
|
@@ -363,93 +360,88 @@ module CLI
|
|
363
360
|
# spin_group.add('Title') { |spinner| sleep 1.0 }
|
364
361
|
# spin_group.wait
|
365
362
|
#
|
366
|
-
|
363
|
+
#: (?to: io_like) -> bool
|
367
364
|
def wait(to: $stdout)
|
368
|
-
|
365
|
+
result = false #: bool
|
369
366
|
|
370
|
-
|
367
|
+
CLI::UI::ProgressReporter.with_progress(mode: :indeterminate, to: to, delay_start: true) do |reporter|
|
368
|
+
idx = 0
|
369
|
+
consumed_lines = 0
|
371
370
|
|
372
|
-
|
371
|
+
@work_queue.close if @internal_work_queue
|
373
372
|
|
374
|
-
|
375
|
-
|
373
|
+
tasks_seen = @tasks.map { false }
|
374
|
+
tasks_seen_done = @tasks.map { false }
|
376
375
|
|
377
|
-
|
378
|
-
|
376
|
+
current_mode = :indeterminate #: Symbol
|
377
|
+
first_render = true #: bool
|
379
378
|
|
380
|
-
|
379
|
+
loop do
|
380
|
+
break if stopped?
|
381
381
|
|
382
|
-
|
382
|
+
done_count = 0
|
383
|
+
width = CLI::UI::Terminal.width
|
383
384
|
|
384
|
-
|
385
|
-
|
385
|
+
# Update progress mode based on task states
|
386
|
+
current_mode = update_progress_mode(reporter, current_mode, first_render)
|
386
387
|
|
387
|
-
|
388
|
-
|
389
|
-
force_full_render = false
|
388
|
+
self.class.pause_mutex.synchronize do
|
389
|
+
next if self.class.paused?
|
390
390
|
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
message.lines.each do |line|
|
396
|
-
to.print(CLI::UI::Frame.prefix + CLI::UI.fmt(line))
|
397
|
-
end
|
398
|
-
to.print("\n")
|
399
|
-
end
|
400
|
-
# we descend with newlines rather than ANSI.cursor_down as the inserted lines may've
|
401
|
-
# pushed the spinner off the front of the buffer, so we can't move back down below it
|
402
|
-
to.print("\n" * consumed_lines) if CLI::UI.enable_cursor?
|
391
|
+
@m.synchronize do
|
392
|
+
CLI::UI.raw do
|
393
|
+
# Render any messages above the spinner
|
394
|
+
force_full_render = render_puts_above(to, consumed_lines)
|
403
395
|
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
to.print(task.render(idx, true, width: width) + "\n")
|
415
|
-
consumed_lines += 1
|
416
|
-
else
|
417
|
-
offset = consumed_lines - int_index
|
418
|
-
move_to = CLI::UI::ANSI.cursor_up(offset) + "\r"
|
419
|
-
move_from = "\r" + CLI::UI::ANSI.cursor_down(offset)
|
420
|
-
|
421
|
-
to.print(move_to + task.render(idx, idx.zero? || force_full_render, width: width) + move_from)
|
422
|
-
end
|
423
|
-
elsif !tasks_seen[int_index] || (task_done && !tasks_seen_done[int_index])
|
424
|
-
to.print(task.render(idx, true, width: width) + "\n")
|
425
|
-
end
|
426
|
-
|
427
|
-
tasks_seen[int_index] = true
|
428
|
-
tasks_seen_done[int_index] ||= task_done
|
396
|
+
# Render all tasks
|
397
|
+
done_count, consumed_lines = render_tasks(
|
398
|
+
to: to,
|
399
|
+
tasks_seen: tasks_seen,
|
400
|
+
tasks_seen_done: tasks_seen_done,
|
401
|
+
consumed_lines: consumed_lines,
|
402
|
+
idx: idx,
|
403
|
+
force_full_render: force_full_render,
|
404
|
+
width: width,
|
405
|
+
)
|
429
406
|
end
|
430
407
|
end
|
431
408
|
end
|
409
|
+
|
410
|
+
break if done_count == @tasks.size
|
411
|
+
|
412
|
+
# After first render, start the progress reporter in indeterminate mode
|
413
|
+
if first_render
|
414
|
+
reporter.force_set_indeterminate
|
415
|
+
first_render = false
|
416
|
+
end
|
417
|
+
|
418
|
+
idx = (idx + 1) % GLYPHS.size
|
419
|
+
Spinner.index = idx
|
420
|
+
sleep(PERIOD)
|
432
421
|
end
|
433
422
|
|
434
|
-
|
423
|
+
# Show error state briefly if tasks failed
|
424
|
+
success = all_succeeded?
|
425
|
+
unless success
|
426
|
+
reporter.set_error
|
427
|
+
sleep(0.5)
|
428
|
+
end
|
435
429
|
|
436
|
-
|
437
|
-
|
438
|
-
|
430
|
+
result = if @auto_debrief
|
431
|
+
debrief(to: to)
|
432
|
+
else
|
433
|
+
all_succeeded?
|
434
|
+
end
|
439
435
|
end
|
440
436
|
|
441
|
-
|
442
|
-
debrief(to: to)
|
443
|
-
else
|
444
|
-
all_succeeded?
|
445
|
-
end
|
437
|
+
result
|
446
438
|
rescue Interrupt
|
447
439
|
@work_queue.interrupt
|
448
440
|
debrief(to: to) if @interrupt_debrief
|
449
441
|
stopped? ? false : raise
|
450
442
|
end
|
451
443
|
|
452
|
-
|
444
|
+
#: (String message) -> void
|
453
445
|
def puts_above(message)
|
454
446
|
@m.synchronize do
|
455
447
|
@puts_above << message
|
@@ -457,32 +449,110 @@ module CLI
|
|
457
449
|
end
|
458
450
|
|
459
451
|
# Provide an alternative debriefing for failed tasks
|
460
|
-
|
461
|
-
params(
|
462
|
-
block: T.proc.params(title: String, exception: T.nilable(Exception), out: String, err: String).void,
|
463
|
-
).void
|
464
|
-
end
|
452
|
+
#: { (String title, Exception? exception, String out, String err) -> void } -> void
|
465
453
|
def failure_debrief(&block)
|
466
454
|
@failure_debrief = block
|
467
455
|
end
|
468
456
|
|
469
457
|
# Provide a debriefing for successful tasks
|
470
|
-
|
471
|
-
params(
|
472
|
-
block: T.proc.params(title: String, out: String, err: String).void,
|
473
|
-
).void
|
474
|
-
end
|
458
|
+
#: { (String title, String out, String err) -> void } -> void
|
475
459
|
def success_debrief(&block)
|
476
460
|
@success_debrief = block
|
477
461
|
end
|
478
462
|
|
479
|
-
|
463
|
+
#: -> bool
|
480
464
|
def all_succeeded?
|
481
465
|
@m.synchronize do
|
482
466
|
@tasks.all?(&:success)
|
483
467
|
end
|
484
468
|
end
|
485
469
|
|
470
|
+
private
|
471
|
+
|
472
|
+
# Update progress reporter mode based on task progress states
|
473
|
+
#: (CLI::UI::ProgressReporter::Reporter reporter, Symbol current_mode, bool first_render) -> Symbol
|
474
|
+
def update_progress_mode(reporter, current_mode, first_render)
|
475
|
+
# Don't emit OSC on first iteration
|
476
|
+
return current_mode if first_render
|
477
|
+
|
478
|
+
# Check if any task wants progress mode
|
479
|
+
task_with_progress = @tasks.find(&:wants_progress_mode?)
|
480
|
+
|
481
|
+
if task_with_progress
|
482
|
+
progress = task_with_progress.current_progress
|
483
|
+
if progress
|
484
|
+
reporter.force_set_progress(progress)
|
485
|
+
if current_mode != :progress
|
486
|
+
# Switch to progress mode
|
487
|
+
:progress
|
488
|
+
else
|
489
|
+
# Update progress
|
490
|
+
current_mode
|
491
|
+
end
|
492
|
+
else
|
493
|
+
current_mode
|
494
|
+
end
|
495
|
+
elsif current_mode != :indeterminate
|
496
|
+
# No task wants progress, switch back to indeterminate
|
497
|
+
reporter.force_set_indeterminate
|
498
|
+
:indeterminate
|
499
|
+
else
|
500
|
+
current_mode
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
# Render messages that should appear above the spinner
|
505
|
+
#: (io_like to, Integer consumed_lines) -> bool
|
506
|
+
def render_puts_above(to, consumed_lines)
|
507
|
+
return false if @puts_above.empty?
|
508
|
+
|
509
|
+
to.print(CLI::UI::ANSI.cursor_up(consumed_lines)) if CLI::UI.enable_cursor?
|
510
|
+
while (message = @puts_above.shift)
|
511
|
+
to.print(CLI::UI::ANSI.insert_lines(message.lines.count)) if CLI::UI.enable_cursor?
|
512
|
+
message.lines.each do |line|
|
513
|
+
to.print(CLI::UI::Frame.prefix + CLI::UI.fmt(line))
|
514
|
+
end
|
515
|
+
to.print("\n")
|
516
|
+
end
|
517
|
+
# we descend with newlines rather than ANSI.cursor_down as the inserted lines may've
|
518
|
+
# pushed the spinner off the front of the buffer, so we can't move back down below it
|
519
|
+
to.print("\n" * consumed_lines) if CLI::UI.enable_cursor?
|
520
|
+
|
521
|
+
true # force full render needed
|
522
|
+
end
|
523
|
+
|
524
|
+
# Render all tasks
|
525
|
+
#: (to: io_like, tasks_seen: Array[bool], tasks_seen_done: Array[bool], consumed_lines: Integer, idx: Integer, force_full_render: bool, width: Integer) -> [Integer, Integer]
|
526
|
+
def render_tasks(to:, tasks_seen:, tasks_seen_done:, consumed_lines:, idx:, force_full_render:, width:)
|
527
|
+
done_count = 0
|
528
|
+
|
529
|
+
@tasks.each.with_index do |task, int_index|
|
530
|
+
nat_index = int_index + 1
|
531
|
+
task_done = task.check
|
532
|
+
done_count += 1 if task_done
|
533
|
+
|
534
|
+
if CLI::UI.enable_cursor?
|
535
|
+
if nat_index > consumed_lines
|
536
|
+
to.print(task.render(idx, true, width: width) + "\n")
|
537
|
+
consumed_lines += 1
|
538
|
+
else
|
539
|
+
offset = consumed_lines - int_index
|
540
|
+
move_to = CLI::UI::ANSI.cursor_up(offset) + "\r"
|
541
|
+
move_from = "\r" + CLI::UI::ANSI.cursor_down(offset)
|
542
|
+
|
543
|
+
to.print(move_to + task.render(idx, idx.zero? || force_full_render, width: width) + move_from)
|
544
|
+
end
|
545
|
+
elsif !tasks_seen[int_index] || (task_done && !tasks_seen_done[int_index])
|
546
|
+
to.print(task.render(idx, true, width: width) + "\n")
|
547
|
+
end
|
548
|
+
|
549
|
+
tasks_seen[int_index] = true
|
550
|
+
tasks_seen_done[int_index] ||= task_done
|
551
|
+
end
|
552
|
+
|
553
|
+
[done_count, consumed_lines]
|
554
|
+
end
|
555
|
+
|
486
556
|
# Debriefs failed tasks is +auto_debrief+ is true
|
487
557
|
#
|
488
558
|
# ==== Options
|
@@ -490,7 +560,7 @@ module CLI
|
|
490
560
|
# * +:to+ - Target stream, like $stdout or $stderr. Can be anything with print and puts methods,
|
491
561
|
# or under Sorbet, IO or StringIO. Defaults to $stdout
|
492
562
|
#
|
493
|
-
|
563
|
+
#: (?to: io_like) -> bool
|
494
564
|
def debrief(to: $stdout)
|
495
565
|
@m.synchronize do
|
496
566
|
@tasks.each do |task|
|
data/lib/cli/ui/spinner.rb
CHANGED
@@ -6,8 +6,6 @@ require 'cli/ui'
|
|
6
6
|
module CLI
|
7
7
|
module UI
|
8
8
|
module Spinner
|
9
|
-
extend T::Sig
|
10
|
-
|
11
9
|
autoload :Async, 'cli/ui/spinner/async'
|
12
10
|
autoload :SpinGroup, 'cli/ui/spinner/spin_group'
|
13
11
|
|
@@ -25,9 +23,7 @@ module CLI
|
|
25
23
|
GLYPHS = colors.zip(RUNES).map { |c, r| c + r + CLI::UI::Color::RESET.code }.freeze
|
26
24
|
|
27
25
|
class << self
|
28
|
-
|
29
|
-
|
30
|
-
sig { returns(T.nilable(Integer)) }
|
26
|
+
#: Integer?
|
31
27
|
attr_accessor(:index)
|
32
28
|
|
33
29
|
# We use this from CLI::UI::Widgets::Status to render an additional
|
@@ -40,15 +36,13 @@ module CLI
|
|
40
36
|
# While it would be possible to stitch through some connection between
|
41
37
|
# the SpinGroup and the Widgets included in its title, this is simpler
|
42
38
|
# in practice and seems unlikely to cause issues in practice.
|
43
|
-
|
39
|
+
#: -> String
|
44
40
|
def current_rune
|
45
41
|
RUNES[index || 0]
|
46
42
|
end
|
47
43
|
end
|
48
44
|
|
49
45
|
class << self
|
50
|
-
extend T::Sig
|
51
|
-
|
52
46
|
# Adds a single spinner
|
53
47
|
# Uses an interactive session to allow the user to pick an answer
|
54
48
|
# Can use arrows, y/n, numbers (1/2), and vim bindings to control
|
@@ -73,14 +67,7 @@ module CLI
|
|
73
67
|
#
|
74
68
|
# CLI::UI::Spinner.spin('Title') { sleep 1.0 }
|
75
69
|
#
|
76
|
-
|
77
|
-
params(
|
78
|
-
title: String,
|
79
|
-
auto_debrief: T::Boolean,
|
80
|
-
to: IOLike,
|
81
|
-
block: T.proc.params(task: SpinGroup::Task).void,
|
82
|
-
).returns(T::Boolean)
|
83
|
-
end
|
70
|
+
#: (String title, ?auto_debrief: bool, ?to: io_like) { (SpinGroup::Task task) -> void } -> bool
|
84
71
|
def spin(title, auto_debrief: true, to: $stdout, &block)
|
85
72
|
sg = SpinGroup.new(auto_debrief: auto_debrief)
|
86
73
|
sg.add(title, &block)
|