cli-ui 1.5.0 → 2.0.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.
@@ -0,0 +1,157 @@
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
+ class << self
74
+ def [](type); end
75
+ end
76
+ end
77
+
78
+ Boolean = Object.new.freeze
79
+
80
+ module Configuration
81
+ class << self
82
+ def call_validation_error_handler(signature, opts); end
83
+ def call_validation_error_handler=(value); end
84
+ def default_checked_level=(default_checked_level); end
85
+ def enable_checking_for_sigs_marked_checked_tests; end
86
+ def enable_final_checks_on_hooks; end
87
+ def enable_legacy_t_enum_migration_mode; end
88
+ def reset_final_checks_on_hooks; end
89
+ def hard_assert_handler(str, extra); end
90
+ def hard_assert_handler=(value); end
91
+ def inline_type_error_handler(error); end
92
+ def inline_type_error_handler=(value); end
93
+ def log_info_handler(str, extra); end
94
+ def log_info_handler=(value); end
95
+ def scalar_types; end
96
+ def scalar_types=(values); end
97
+ # rubocop:disable Naming/InclusiveLanguage
98
+ def sealed_violation_whitelist; end
99
+ def sealed_violation_whitelist=(sealed_violation_whitelist); end
100
+ # rubocop:enable Naming/InclusiveLanguage
101
+ def sig_builder_error_handler(error, location); end
102
+ def sig_builder_error_handler=(value); end
103
+ def sig_validation_error_handler(error, opts); end
104
+ def sig_validation_error_handler=(value); end
105
+ def soft_assert_handler(str, extra); end
106
+ def soft_assert_handler=(value); end
107
+ end
108
+ end
109
+
110
+ module Enumerable
111
+ class << self
112
+ def [](type); end
113
+ end
114
+ end
115
+
116
+ module Enumerator
117
+ class << self
118
+ def [](type); end
119
+ end
120
+ end
121
+
122
+ module Hash
123
+ class << self
124
+ def [](keys, values); end
125
+ end
126
+ end
127
+
128
+ class Proc
129
+ def bind(*_)
130
+ self
131
+ end
132
+
133
+ def params(*_param)
134
+ self
135
+ end
136
+
137
+ def void
138
+ self
139
+ end
140
+
141
+ def returns(_type)
142
+ self
143
+ end
144
+ end
145
+
146
+ module Range
147
+ class << self
148
+ def [](type); end
149
+ end
150
+ end
151
+
152
+ module Set
153
+ class << self
154
+ def [](type); end
155
+ end
156
+ end
157
+ end
@@ -1,11 +1,20 @@
1
+ # typed: true
2
+
1
3
  module CLI
2
4
  module UI
3
5
  module Spinner
4
6
  class Async
5
- # Convenience method for +initialize+
6
- #
7
- def self.start(title)
8
- new(title)
7
+ extend T::Sig
8
+
9
+ class << self
10
+ extend T::Sig
11
+
12
+ # Convenience method for +initialize+
13
+ #
14
+ sig { params(title: String).returns(Async) }
15
+ def start(title)
16
+ new(title)
17
+ end
9
18
  end
10
19
 
11
20
  # Initializes a new asynchronous spinner with no specific end.
@@ -19,6 +28,7 @@ module CLI
19
28
  #
20
29
  # CLI::UI::Spinner::Async.new('Title')
21
30
  #
31
+ sig { params(title: String).void }
22
32
  def initialize(title)
23
33
  require 'thread'
24
34
  sg = CLI::UI::Spinner::SpinGroup.new
@@ -30,6 +40,7 @@ module CLI
30
40
 
31
41
  # Stops an asynchronous spinner
32
42
  #
43
+ sig { returns(T::Boolean) }
33
44
  def stop
34
45
  @m.synchronize { @cv.signal }
35
46
  @t.value
@@ -1,7 +1,11 @@
1
+ # typed: true
2
+
1
3
  module CLI
2
4
  module UI
3
5
  module Spinner
4
6
  class SpinGroup
7
+ extend T::Sig
8
+
5
9
  # Initializes a new spin group
6
10
  # This lets you add +Task+ objects to the group to multi-thread work
7
11
  #
@@ -11,25 +15,39 @@ module CLI
11
15
  #
12
16
  # ==== Example Usage
13
17
  #
14
- # spin_group = CLI::UI::SpinGroup.new
15
- # spin_group.add('Title') { |spinner| sleep 3.0 }
16
- # spin_group.add('Title 2') { |spinner| sleep 3.0; spinner.update_title('New Title'); sleep 3.0 }
17
- # spin_group.wait
18
+ # CLI::UI::SpinGroup.new do |spin_group|
19
+ # spin_group.add('Title') { |spinner| sleep 3.0 }
20
+ # spin_group.add('Title 2') { |spinner| sleep 3.0; spinner.update_title('New Title'); sleep 3.0 }
21
+ # end
18
22
  #
19
23
  # Output:
20
24
  #
21
25
  # https://user-images.githubusercontent.com/3074765/33798558-c452fa26-dce8-11e7-9e90-b4b34df21a46.gif
22
26
  #
27
+ sig { params(auto_debrief: T::Boolean).void }
23
28
  def initialize(auto_debrief: true)
24
29
  @m = Mutex.new
25
30
  @consumed_lines = 0
26
31
  @tasks = []
27
32
  @auto_debrief = auto_debrief
28
33
  @start = Time.new
34
+ if block_given?
35
+ yield self
36
+ wait
37
+ end
29
38
  end
30
39
 
31
40
  class Task
32
- attr_reader :title, :exception, :success, :stdout, :stderr
41
+ extend T::Sig
42
+
43
+ sig { returns(String) }
44
+ attr_reader :title, :stdout, :stderr
45
+
46
+ sig { returns(T::Boolean) }
47
+ attr_reader :success
48
+
49
+ sig { returns(T.nilable(Exception)) }
50
+ attr_reader :exception
33
51
 
34
52
  # Initializes a new Task
35
53
  # This is managed entirely internally by +SpinGroup+
@@ -39,11 +57,12 @@ module CLI
39
57
  # * +title+ - Title of the task
40
58
  # * +block+ - Block for the task, will be provided with an instance of the spinner
41
59
  #
60
+ sig { params(title: String, block: T.proc.params(task: Task).returns(T.untyped)).void }
42
61
  def initialize(title, &block)
43
62
  @title = title
44
63
  @always_full_render = title =~ Formatter::SCAN_WIDGET
45
64
  @thread = Thread.new do
46
- cap = CLI::UI::StdoutRouter::Capture.new(self, with_frame_inset: false, &block)
65
+ cap = CLI::UI::StdoutRouter::Capture.new(with_frame_inset: false) { block.call(self) }
47
66
  begin
48
67
  cap.run
49
68
  ensure
@@ -61,6 +80,7 @@ module CLI
61
80
 
62
81
  # Checks if a task is finished
63
82
  #
83
+ sig { returns(T::Boolean) }
64
84
  def check
65
85
  return true if @done
66
86
  return false if @thread.alive?
@@ -96,6 +116,7 @@ module CLI
96
116
  # * +force+ - force rerender of the task
97
117
  # * +width+ - current terminal width to format for
98
118
  #
119
+ sig { params(index: Integer, force: T::Boolean, width: Integer).returns(String) }
99
120
  def render(index, force = true, width: CLI::UI::Terminal.width)
100
121
  @m.synchronize do
101
122
  if force || @always_full_render || @force_full_render
@@ -114,6 +135,7 @@ module CLI
114
135
  #
115
136
  # * +title+ - title to change the spinner to
116
137
  #
138
+ sig { params(new_title: String).void }
117
139
  def update_title(new_title)
118
140
  @m.synchronize do
119
141
  @always_full_render = new_title =~ Formatter::SCAN_WIDGET
@@ -122,8 +144,14 @@ module CLI
122
144
  end
123
145
  end
124
146
 
147
+ sig { void }
148
+ def interrupt
149
+ @thread.raise(Interrupt)
150
+ end
151
+
125
152
  private
126
153
 
154
+ sig { params(index: Integer, terminal_width: Integer).returns(String) }
127
155
  def full_render(index, terminal_width)
128
156
  prefix = inset +
129
157
  glyph(index) +
@@ -137,10 +165,12 @@ module CLI
137
165
  "\e[K"
138
166
  end
139
167
 
168
+ sig { params(index: Integer).returns(String) }
140
169
  def partial_render(index)
141
170
  CLI::UI::ANSI.cursor_forward(inset_width) + glyph(index) + CLI::UI::Color::RESET.code
142
171
  end
143
172
 
173
+ sig { params(index: Integer).returns(String) }
144
174
  def glyph(index)
145
175
  if @done
146
176
  @success ? CLI::UI::Glyph::CHECK.to_s : CLI::UI::Glyph::X.to_s
@@ -149,10 +179,12 @@ module CLI
149
179
  end
150
180
  end
151
181
 
182
+ sig { returns(String) }
152
183
  def inset
153
184
  @inset ||= CLI::UI::Frame.prefix
154
185
  end
155
186
 
187
+ sig { returns(Integer) }
156
188
  def inset_width
157
189
  @inset_width ||= CLI::UI::ANSI.printing_width(inset)
158
190
  end
@@ -170,6 +202,7 @@ module CLI
170
202
  # spin_group.add('Title') { |spinner| sleep 1.0 }
171
203
  # spin_group.wait
172
204
  #
205
+ sig { params(title: String, block: T.proc.params(task: Task).void).void }
173
206
  def add(title, &block)
174
207
  @m.synchronize do
175
208
  @tasks << Task.new(title, &block)
@@ -183,11 +216,12 @@ module CLI
183
216
  # spin_group.add('Title') { |spinner| sleep 1.0 }
184
217
  # spin_group.wait
185
218
  #
219
+ sig { returns(T::Boolean) }
186
220
  def wait
187
221
  idx = 0
188
222
 
189
223
  loop do
190
- all_done = true
224
+ all_done = T.let(true, T::Boolean)
191
225
 
192
226
  width = CLI::UI::Terminal.width
193
227
 
@@ -222,24 +256,46 @@ module CLI
222
256
  if @auto_debrief
223
257
  debrief
224
258
  else
225
- @m.synchronize do
226
- @tasks.all?(&:success)
227
- end
259
+ all_succeeded?
260
+ end
261
+ rescue Interrupt
262
+ @tasks.each(&:interrupt)
263
+ raise
264
+ end
265
+
266
+ # Provide an alternative debriefing for failed tasks
267
+ sig do
268
+ params(
269
+ block: T.proc.params(title: String, exception: T.nilable(Exception), out: String, err: String).void,
270
+ ).void
271
+ end
272
+ def failure_debrief(&block)
273
+ @failure_debrief = block
274
+ end
275
+
276
+ sig { returns(T::Boolean) }
277
+ def all_succeeded?
278
+ @m.synchronize do
279
+ @tasks.all?(&:success)
228
280
  end
229
281
  end
230
282
 
231
283
  # Debriefs failed tasks is +auto_debrief+ is true
232
284
  #
285
+ sig { returns(T::Boolean) }
233
286
  def debrief
234
287
  @m.synchronize do
235
288
  @tasks.each do |task|
236
289
  next if task.success
237
290
 
291
+ title = task.title
238
292
  e = task.exception
239
293
  out = task.stdout
240
294
  err = task.stderr
241
295
 
242
- CLI::UI::Frame.open('Task Failed: ' + task.title, color: :red, timing: Time.new - @start) do
296
+ next @failure_debrief.call(title, e, out, err) if @failure_debrief
297
+
298
+ CLI::UI::Frame.open('Task Failed: ' + title, color: :red, timing: Time.new - @start) do
243
299
  if e
244
300
  puts "#{e.class}: #{e.message}"
245
301
  puts "\tfrom #{e.backtrace.join("\n\tfrom ")}"
@@ -1,22 +1,33 @@
1
- # frozen-string-literal: true
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
2
4
  require 'cli/ui'
3
5
 
4
6
  module CLI
5
7
  module UI
6
8
  module Spinner
9
+ extend T::Sig
10
+
7
11
  autoload :Async, 'cli/ui/spinner/async'
8
12
  autoload :SpinGroup, 'cli/ui/spinner/spin_group'
9
13
 
10
14
  PERIOD = 0.1 # seconds
11
15
  TASK_FAILED = :task_failed
12
16
 
13
- RUNES = CLI::UI::OS.current.supports_emoji? ? %w(⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏).freeze : %w(\\ | / - \\ | / -).freeze
17
+ RUNES = if CLI::UI::OS.current.use_emoji?
18
+ ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'].freeze
19
+ else
20
+ ['\\', '|', '/', '-', '\\', '|', '/', '-'].freeze
21
+ end
14
22
 
15
23
  colors = [CLI::UI::Color::CYAN.code] * (RUNES.size / 2).ceil +
16
24
  [CLI::UI::Color::MAGENTA.code] * (RUNES.size / 2).to_i
17
25
  GLYPHS = colors.zip(RUNES).map(&:join)
18
26
 
19
27
  class << self
28
+ extend T::Sig
29
+
30
+ sig { returns(T.nilable(Integer)) }
20
31
  attr_accessor(:index)
21
32
 
22
33
  # We use this from CLI::UI::Widgets::Status to render an additional
@@ -29,37 +40,46 @@ module CLI
29
40
  # While it would be possible to stitch through some connection between
30
41
  # the SpinGroup and the Widgets included in its title, this is simpler
31
42
  # in practice and seems unlikely to cause issues in practice.
43
+ sig { returns(String) }
32
44
  def current_rune
33
45
  RUNES[index || 0]
34
46
  end
35
47
  end
36
48
 
37
- # Adds a single spinner
38
- # Uses an interactive session to allow the user to pick an answer
39
- # Can use arrows, y/n, numbers (1/2), and vim bindings to control
40
- #
41
- # https://user-images.githubusercontent.com/3074765/33798295-d94fd822-dce3-11e7-819b-43e5502d490e.gif
42
- #
43
- # ==== Attributes
44
- #
45
- # * +title+ - Title of the spinner to use
46
- #
47
- # ==== Options
48
- #
49
- # * +:auto_debrief+ - Automatically debrief exceptions? Default to true
50
- #
51
- # ==== Block
52
- #
53
- # * *spinner+ - Instance of the spinner. Can call +update_title+ to update the user of changes
54
- #
55
- # ==== Example Usage:
56
- #
57
- # CLI::UI::Spinner.spin('Title') { sleep 1.0 }
58
- #
59
- def self.spin(title, auto_debrief: true, &block)
60
- sg = SpinGroup.new(auto_debrief: auto_debrief)
61
- sg.add(title, &block)
62
- sg.wait
49
+ class << self
50
+ extend T::Sig
51
+
52
+ # Adds a single spinner
53
+ # Uses an interactive session to allow the user to pick an answer
54
+ # Can use arrows, y/n, numbers (1/2), and vim bindings to control
55
+ #
56
+ # https://user-images.githubusercontent.com/3074765/33798295-d94fd822-dce3-11e7-819b-43e5502d490e.gif
57
+ #
58
+ # ==== Attributes
59
+ #
60
+ # * +title+ - Title of the spinner to use
61
+ #
62
+ # ==== Options
63
+ #
64
+ # * +:auto_debrief+ - Automatically debrief exceptions? Default to true
65
+ #
66
+ # ==== Block
67
+ #
68
+ # * *spinner+ - Instance of the spinner. Can call +update_title+ to update the user of changes
69
+ #
70
+ # ==== Example Usage:
71
+ #
72
+ # CLI::UI::Spinner.spin('Title') { sleep 1.0 }
73
+ #
74
+ sig do
75
+ params(title: String, auto_debrief: T::Boolean, block: T.proc.params(task: SpinGroup::Task).void)
76
+ .returns(T::Boolean)
77
+ end
78
+ def spin(title, auto_debrief: true, &block)
79
+ sg = SpinGroup.new(auto_debrief: auto_debrief)
80
+ sg.add(title, &block)
81
+ sg.wait
82
+ end
63
83
  end
64
84
  end
65
85
  end