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.
@@ -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,16 +1,16 @@
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.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: 2023-05-01 00:00:00.000000000 Z
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.7
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: []