cli-ui 2.2.3 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/cli/ui/ansi.rb +25 -3
- data/lib/cli/ui/color.rb +3 -0
- data/lib/cli/ui/formatter.rb +1 -0
- data/lib/cli/ui/frame/frame_stack.rb +1 -0
- data/lib/cli/ui/frame/frame_style/box.rb +15 -14
- data/lib/cli/ui/frame/frame_style/bracket.rb +14 -13
- data/lib/cli/ui/frame/frame_style.rb +3 -2
- data/lib/cli/ui/frame.rb +9 -8
- data/lib/cli/ui/glyph.rb +2 -1
- data/lib/cli/ui/os.rb +1 -0
- data/lib/cli/ui/printer.rb +1 -0
- data/lib/cli/ui/progress.rb +36 -8
- data/lib/cli/ui/prompt/interactive_options.rb +13 -12
- data/lib/cli/ui/prompt/options_handler.rb +1 -0
- data/lib/cli/ui/prompt.rb +27 -30
- data/lib/cli/ui/spinner/async.rb +1 -0
- data/lib/cli/ui/spinner/spin_group.rb +182 -51
- data/lib/cli/ui/spinner.rb +11 -5
- data/lib/cli/ui/stdout_router.rb +6 -4
- data/lib/cli/ui/table.rb +87 -0
- data/lib/cli/ui/terminal.rb +1 -0
- data/lib/cli/ui/version.rb +2 -1
- data/lib/cli/ui/widgets/base.rb +1 -0
- data/lib/cli/ui/widgets.rb +2 -1
- data/lib/cli/ui/work_queue.rb +146 -0
- data/lib/cli/ui/wrap.rb +4 -4
- data/lib/cli/ui.rb +44 -11
- metadata +8 -6
@@ -0,0 +1,146 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module UI
|
6
|
+
class WorkQueue
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
class Future
|
10
|
+
extend T::Sig
|
11
|
+
|
12
|
+
sig { void }
|
13
|
+
def initialize
|
14
|
+
@mutex = T.let(Mutex.new, Mutex)
|
15
|
+
@condition = T.let(ConditionVariable.new, ConditionVariable)
|
16
|
+
@completed = T.let(false, T::Boolean)
|
17
|
+
@started = T.let(false, T::Boolean)
|
18
|
+
@result = T.let(nil, T.untyped)
|
19
|
+
@error = T.let(nil, T.nilable(Exception))
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { params(result: T.untyped).void }
|
23
|
+
def complete(result)
|
24
|
+
@mutex.synchronize do
|
25
|
+
@completed = true
|
26
|
+
@result = result
|
27
|
+
@condition.broadcast
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
sig { params(error: Exception).void }
|
32
|
+
def fail(error)
|
33
|
+
@mutex.synchronize do
|
34
|
+
return if @completed
|
35
|
+
|
36
|
+
@completed = true
|
37
|
+
@error = error
|
38
|
+
@condition.broadcast
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
sig { returns(T.untyped) }
|
43
|
+
def value
|
44
|
+
@mutex.synchronize do
|
45
|
+
@condition.wait(@mutex) until @completed
|
46
|
+
raise @error if @error
|
47
|
+
|
48
|
+
@result
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
sig { returns(T::Boolean) }
|
53
|
+
def completed?
|
54
|
+
@mutex.synchronize { @completed }
|
55
|
+
end
|
56
|
+
|
57
|
+
sig { returns(T::Boolean) }
|
58
|
+
def started?
|
59
|
+
@mutex.synchronize { @started }
|
60
|
+
end
|
61
|
+
|
62
|
+
sig { void }
|
63
|
+
def start
|
64
|
+
@mutex.synchronize do
|
65
|
+
@started = true
|
66
|
+
@condition.broadcast
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
sig { params(max_concurrent: Integer).void }
|
72
|
+
def initialize(max_concurrent)
|
73
|
+
@max_concurrent = max_concurrent
|
74
|
+
@queue = T.let(Queue.new, Queue)
|
75
|
+
@mutex = T.let(Mutex.new, Mutex)
|
76
|
+
@condition = T.let(ConditionVariable.new, ConditionVariable)
|
77
|
+
@workers = T.let([], T::Array[Thread])
|
78
|
+
end
|
79
|
+
|
80
|
+
sig { params(block: T.proc.returns(T.untyped)).returns(Future) }
|
81
|
+
def enqueue(&block)
|
82
|
+
future = Future.new
|
83
|
+
@mutex.synchronize do
|
84
|
+
start_worker if @workers.size < @max_concurrent
|
85
|
+
end
|
86
|
+
@queue.push([future, block])
|
87
|
+
future
|
88
|
+
end
|
89
|
+
|
90
|
+
sig { void }
|
91
|
+
def close
|
92
|
+
@queue.close
|
93
|
+
end
|
94
|
+
|
95
|
+
sig { void }
|
96
|
+
def wait
|
97
|
+
@queue.close
|
98
|
+
@workers.each(&:join)
|
99
|
+
end
|
100
|
+
|
101
|
+
sig { void }
|
102
|
+
def interrupt
|
103
|
+
@mutex.synchronize do
|
104
|
+
@queue.close
|
105
|
+
# Fail any remaining tasks in the queue
|
106
|
+
until @queue.empty?
|
107
|
+
future, _block = @queue.pop(true)
|
108
|
+
future&.fail(Interrupt.new)
|
109
|
+
end
|
110
|
+
# Interrupt all worker threads
|
111
|
+
@workers.each { |worker| worker.raise(Interrupt) if worker.alive? }
|
112
|
+
@workers.each(&:join)
|
113
|
+
@workers.clear
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
sig { void }
|
120
|
+
def start_worker
|
121
|
+
@workers << Thread.new do
|
122
|
+
loop do
|
123
|
+
work = @queue.pop
|
124
|
+
break if work.nil?
|
125
|
+
|
126
|
+
future, block = work
|
127
|
+
|
128
|
+
begin
|
129
|
+
future.start
|
130
|
+
result = block.call
|
131
|
+
future.complete(result)
|
132
|
+
rescue Interrupt => e
|
133
|
+
future.fail(e)
|
134
|
+
raise # Always re-raise interrupts to terminate the worker
|
135
|
+
rescue StandardError => e
|
136
|
+
future.fail(e)
|
137
|
+
# Don't re-raise standard errors - allow worker to continue
|
138
|
+
end
|
139
|
+
end
|
140
|
+
rescue Interrupt
|
141
|
+
# Clean exit on interrupt
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/lib/cli/ui/wrap.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
|
3
2
|
# typed: true
|
3
|
+
# frozen_string_literal: true
|
4
4
|
|
5
5
|
require 'cli/ui'
|
6
6
|
require 'cli/ui/frame/frame_stack'
|
@@ -16,9 +16,9 @@ module CLI
|
|
16
16
|
@input = input
|
17
17
|
end
|
18
18
|
|
19
|
-
sig { returns(String) }
|
20
|
-
def wrap
|
21
|
-
max_width =
|
19
|
+
sig { params(total_width: Integer).returns(String) }
|
20
|
+
def wrap(total_width = Terminal.width)
|
21
|
+
max_width = total_width - Frame.prefix_width
|
22
22
|
width = T.let(0, Integer)
|
23
23
|
final = []
|
24
24
|
# Create an alternation of format codes of parameter lengths 1-20, since + and {1,n} not allowed in lookbehind
|
data/lib/cli/ui.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# typed: true
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
unless defined?(T)
|
4
5
|
require('cli/ui/sorbet_runtime_stub')
|
@@ -16,6 +17,7 @@ module CLI
|
|
16
17
|
autoload :Printer, 'cli/ui/printer'
|
17
18
|
autoload :Progress, 'cli/ui/progress'
|
18
19
|
autoload :Prompt, 'cli/ui/prompt'
|
20
|
+
autoload :Table, 'cli/ui/table'
|
19
21
|
autoload :Terminal, 'cli/ui/terminal'
|
20
22
|
autoload :Truncater, 'cli/ui/truncater'
|
21
23
|
autoload :Formatter, 'cli/ui/formatter'
|
@@ -145,10 +147,11 @@ module CLI
|
|
145
147
|
#
|
146
148
|
# * +input+ - input to format
|
147
149
|
# * +truncate_to+ - number of characters to truncate the string to (or nil)
|
150
|
+
# * +enable_color+ - should color be used? default to true unless output is redirected.
|
148
151
|
#
|
149
|
-
sig { params(input: String, truncate_to: T.nilable(Integer)).returns(String) }
|
150
|
-
def resolve_text(input, truncate_to: nil)
|
151
|
-
formatted = CLI::UI::Formatter.new(input).format
|
152
|
+
sig { params(input: String, truncate_to: T.nilable(Integer), enable_color: T::Boolean).returns(String) }
|
153
|
+
def resolve_text(input, truncate_to: nil, enable_color: enable_color?)
|
154
|
+
formatted = CLI::UI::Formatter.new(input).format(enable_color: enable_color)
|
152
155
|
return formatted unless truncate_to
|
153
156
|
|
154
157
|
CLI::UI::Truncater.call(formatted, truncate_to)
|
@@ -231,6 +234,7 @@ module CLI
|
|
231
234
|
success_text: T.nilable(String),
|
232
235
|
timing: T.any(T::Boolean, Numeric),
|
233
236
|
frame_style: FrameStylable,
|
237
|
+
to: IOLike,
|
234
238
|
block: T.nilable(T.proc.returns(T.type_parameter(:T))),
|
235
239
|
).returns(T.nilable(T.type_parameter(:T)))
|
236
240
|
end
|
@@ -241,6 +245,7 @@ module CLI
|
|
241
245
|
success_text: nil,
|
242
246
|
timing: block_given?,
|
243
247
|
frame_style: Frame.frame_style,
|
248
|
+
to: $stdout,
|
244
249
|
&block
|
245
250
|
)
|
246
251
|
CLI::UI::Frame.open(
|
@@ -250,6 +255,7 @@ module CLI
|
|
250
255
|
success_text: success_text,
|
251
256
|
timing: timing,
|
252
257
|
frame_style: frame_style,
|
258
|
+
to: to,
|
253
259
|
&block
|
254
260
|
)
|
255
261
|
end
|
@@ -262,11 +268,15 @@ module CLI
|
|
262
268
|
# * +block+ - block for +Spinner.open+
|
263
269
|
#
|
264
270
|
sig do
|
265
|
-
params(
|
266
|
-
|
271
|
+
params(
|
272
|
+
title: String,
|
273
|
+
auto_debrief: T::Boolean,
|
274
|
+
to: IOLike,
|
275
|
+
block: T.proc.params(task: Spinner::SpinGroup::Task).void,
|
276
|
+
).returns(T::Boolean)
|
267
277
|
end
|
268
|
-
def spinner(title, auto_debrief: true, &block)
|
269
|
-
CLI::UI::Spinner.spin(title, auto_debrief: auto_debrief, &block)
|
278
|
+
def spinner(title, auto_debrief: true, to: $stdout, &block)
|
279
|
+
CLI::UI::Spinner.spin(title, auto_debrief: auto_debrief, to: to, &block)
|
270
280
|
end
|
271
281
|
|
272
282
|
# Convenience Method to override frame color using +CLI::UI::Frame.with_frame_color+
|
@@ -329,16 +339,16 @@ module CLI
|
|
329
339
|
Thread.current[:no_cliui_frame_inset] = prev
|
330
340
|
end
|
331
341
|
|
332
|
-
# Check whether colour is enabled in Formatter
|
333
|
-
# is enabled when STDOUT is a TTY; that is, when output
|
334
|
-
#
|
342
|
+
# Check whether colour is enabled in Formatter, Frame, and Spinner output.
|
343
|
+
# By default, colour is enabled when STDOUT is a TTY; that is, when output
|
344
|
+
# has not been directed to another program or to a file.
|
335
345
|
#
|
336
346
|
sig { returns(T::Boolean) }
|
337
347
|
def enable_color?
|
338
348
|
@enable_color
|
339
349
|
end
|
340
350
|
|
341
|
-
# Turn colour
|
351
|
+
# Turn colour in Formatter, Frame, and Spinner output on or off.
|
342
352
|
#
|
343
353
|
# ==== Attributes
|
344
354
|
#
|
@@ -349,6 +359,26 @@ module CLI
|
|
349
359
|
@enable_color = !!bool
|
350
360
|
end
|
351
361
|
|
362
|
+
# Check whether cursor control is enabled in Formatter, Frame, and Spinner output.
|
363
|
+
# By default, cursor control is enabled when STDOUT is a TTY; that is, when output
|
364
|
+
# has not been directed to another program or to a file.
|
365
|
+
#
|
366
|
+
sig { returns(T::Boolean) }
|
367
|
+
def enable_cursor?
|
368
|
+
@enable_cursor
|
369
|
+
end
|
370
|
+
|
371
|
+
# Turn cursor control in Formatter, Frame, and Spinner output on or off.
|
372
|
+
#
|
373
|
+
# ==== Attributes
|
374
|
+
#
|
375
|
+
# * +bool+ - true or false; enable or disable cursor control.
|
376
|
+
#
|
377
|
+
sig { params(bool: T::Boolean).void }
|
378
|
+
def enable_cursor=(bool)
|
379
|
+
@enable_cursor = !!bool
|
380
|
+
end
|
381
|
+
|
352
382
|
# Set the default frame style.
|
353
383
|
# Convenience method for setting the default frame style with +CLI::UI::Frame.frame_style=+
|
354
384
|
#
|
@@ -375,6 +405,9 @@ module CLI
|
|
375
405
|
end
|
376
406
|
|
377
407
|
self.enable_color = $stdout.tty?
|
408
|
+
|
409
|
+
# Shopify's CI system supports color, but not cursor control
|
410
|
+
self.enable_cursor = T.must($stdout.tty? && ENV['CI'].nil? && ENV['JOURNAL_STREAM'].nil?)
|
378
411
|
end
|
379
412
|
end
|
380
413
|
|
metadata
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cli-ui
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Burke Libbey
|
8
8
|
- Julian Nadeau
|
9
9
|
- Lisa Ugray
|
10
|
-
autorequire:
|
10
|
+
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2024-12-10 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: minitest
|
@@ -72,19 +72,21 @@ files:
|
|
72
72
|
- lib/cli/ui/spinner/async.rb
|
73
73
|
- lib/cli/ui/spinner/spin_group.rb
|
74
74
|
- lib/cli/ui/stdout_router.rb
|
75
|
+
- lib/cli/ui/table.rb
|
75
76
|
- lib/cli/ui/terminal.rb
|
76
77
|
- lib/cli/ui/truncater.rb
|
77
78
|
- lib/cli/ui/version.rb
|
78
79
|
- lib/cli/ui/widgets.rb
|
79
80
|
- lib/cli/ui/widgets/base.rb
|
80
81
|
- lib/cli/ui/widgets/status.rb
|
82
|
+
- lib/cli/ui/work_queue.rb
|
81
83
|
- lib/cli/ui/wrap.rb
|
82
84
|
- vendor/reentrant_mutex.rb
|
83
85
|
homepage: https://github.com/shopify/cli-ui
|
84
86
|
licenses:
|
85
87
|
- MIT
|
86
88
|
metadata: {}
|
87
|
-
post_install_message:
|
89
|
+
post_install_message:
|
88
90
|
rdoc_options: []
|
89
91
|
require_paths:
|
90
92
|
- lib
|
@@ -99,8 +101,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
99
101
|
- !ruby/object:Gem::Version
|
100
102
|
version: '0'
|
101
103
|
requirements: []
|
102
|
-
rubygems_version: 3.3.
|
103
|
-
signing_key:
|
104
|
+
rubygems_version: 3.0.3.1
|
105
|
+
signing_key:
|
104
106
|
specification_version: 4
|
105
107
|
summary: Terminal UI framework
|
106
108
|
test_files: []
|