cli-ui 2.2.3 → 2.3.1

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.
@@ -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 = Terminal.width - Frame.prefix_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(title: String, auto_debrief: T::Boolean, block: T.proc.params(task: Spinner::SpinGroup::Task).void)
266
- .returns(T::Boolean)
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 output. By default, colour
333
- # is enabled when STDOUT is a TTY; that is, when output has not been
334
- # redirected to another program or to a file.
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 output in Formatter on or off.
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,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cli-ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.3
4
+ version: 2.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Burke Libbey
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2023-05-01 00:00:00.000000000 Z
13
+ date: 2025-03-17 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: minitest
@@ -72,12 +72,14 @@ 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
@@ -99,7 +101,7 @@ 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.7
104
+ rubygems_version: 3.5.9
103
105
  signing_key:
104
106
  specification_version: 4
105
107
  summary: Terminal UI framework