cli-ui 1.5.1 → 2.2.3
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/README.md +28 -17
- data/lib/cli/ui/ansi.rb +172 -129
- data/lib/cli/ui/color.rb +39 -20
- data/lib/cli/ui/formatter.rb +46 -21
- data/lib/cli/ui/frame/frame_stack.rb +30 -50
- data/lib/cli/ui/frame/frame_style/box.rb +16 -5
- data/lib/cli/ui/frame/frame_style/bracket.rb +19 -8
- data/lib/cli/ui/frame/frame_style.rb +84 -87
- data/lib/cli/ui/frame.rb +79 -32
- data/lib/cli/ui/glyph.rb +44 -31
- data/lib/cli/ui/os.rb +44 -48
- data/lib/cli/ui/printer.rb +65 -47
- data/lib/cli/ui/progress.rb +50 -33
- data/lib/cli/ui/prompt/interactive_options.rb +114 -68
- data/lib/cli/ui/prompt/options_handler.rb +8 -0
- data/lib/cli/ui/prompt.rb +168 -58
- data/lib/cli/ui/sorbet_runtime_stub.rb +169 -0
- data/lib/cli/ui/spinner/async.rb +15 -4
- data/lib/cli/ui/spinner/spin_group.rb +174 -36
- data/lib/cli/ui/spinner.rb +48 -28
- data/lib/cli/ui/stdout_router.rb +229 -47
- data/lib/cli/ui/terminal.rb +37 -25
- data/lib/cli/ui/truncater.rb +7 -2
- data/lib/cli/ui/version.rb +3 -1
- data/lib/cli/ui/widgets/base.rb +23 -4
- data/lib/cli/ui/widgets/status.rb +19 -1
- data/lib/cli/ui/widgets.rb +42 -23
- data/lib/cli/ui/wrap.rb +8 -1
- data/lib/cli/ui.rb +336 -188
- data/vendor/reentrant_mutex.rb +78 -0
- metadata +11 -9
data/lib/cli/ui/progress.rb
CHANGED
@@ -1,41 +1,54 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
1
3
|
require 'cli/ui'
|
2
4
|
|
3
5
|
module CLI
|
4
6
|
module UI
|
5
7
|
class Progress
|
8
|
+
extend T::Sig
|
9
|
+
|
6
10
|
# A Cyan filled block
|
7
11
|
FILLED_BAR = "\e[46m"
|
8
12
|
# A bright white block
|
9
13
|
UNFILLED_BAR = "\e[1;47m"
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
bar
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
15
|
+
class << self
|
16
|
+
extend T::Sig
|
17
|
+
|
18
|
+
# Add a progress bar to the terminal output
|
19
|
+
#
|
20
|
+
# https://user-images.githubusercontent.com/3074765/33799794-cc4c940e-dd00-11e7-9bdc-90f77ec9167c.gif
|
21
|
+
#
|
22
|
+
# ==== Example Usage:
|
23
|
+
#
|
24
|
+
# Set the percent to X
|
25
|
+
# CLI::UI::Progress.progress do |bar|
|
26
|
+
# bar.tick(set_percent: percent)
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# Increase the percent by 1 percent
|
30
|
+
# CLI::UI::Progress.progress do |bar|
|
31
|
+
# bar.tick
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# Increase the percent by X
|
35
|
+
# CLI::UI::Progress.progress do |bar|
|
36
|
+
# bar.tick(percent: 0.05)
|
37
|
+
# end
|
38
|
+
sig do
|
39
|
+
type_parameters(:T)
|
40
|
+
.params(width: Integer, block: T.proc.params(bar: Progress).returns(T.type_parameter(:T)))
|
41
|
+
.returns(T.type_parameter(:T))
|
42
|
+
end
|
43
|
+
def progress(width: Terminal.width, &block)
|
44
|
+
bar = Progress.new(width: width)
|
45
|
+
print(CLI::UI::ANSI.hide_cursor)
|
46
|
+
yield(bar)
|
47
|
+
ensure
|
48
|
+
puts(bar)
|
49
|
+
CLI::UI.raw do
|
50
|
+
print(ANSI.show_cursor)
|
51
|
+
end
|
39
52
|
end
|
40
53
|
end
|
41
54
|
|
@@ -46,8 +59,9 @@ module CLI
|
|
46
59
|
#
|
47
60
|
# * +:width+ - The width of the terminal
|
48
61
|
#
|
62
|
+
sig { params(width: Integer).void }
|
49
63
|
def initialize(width: Terminal.width)
|
50
|
-
@percent_done = 0
|
64
|
+
@percent_done = T.let(0, Numeric)
|
51
65
|
@max_width = width
|
52
66
|
end
|
53
67
|
|
@@ -61,18 +75,21 @@ module CLI
|
|
61
75
|
#
|
62
76
|
# *Note:* The +:percent+ and +:set_percent must be between 0.00 and 1.0
|
63
77
|
#
|
64
|
-
|
65
|
-
|
66
|
-
|
78
|
+
sig { params(percent: T.nilable(Numeric), set_percent: T.nilable(Numeric)).void }
|
79
|
+
def tick(percent: nil, set_percent: nil)
|
80
|
+
raise ArgumentError, 'percent and set_percent cannot both be specified' if percent && set_percent
|
81
|
+
|
82
|
+
@percent_done += percent || 0.01
|
67
83
|
@percent_done = set_percent if set_percent
|
68
84
|
@percent_done = [@percent_done, 1.0].min # Make sure we can't go above 1.0
|
69
85
|
|
70
|
-
print(
|
86
|
+
print(self)
|
71
87
|
print(CLI::UI::ANSI.previous_line + "\n")
|
72
88
|
end
|
73
89
|
|
74
90
|
# Format the progress bar to be printed to terminal
|
75
91
|
#
|
92
|
+
sig { returns(String) }
|
76
93
|
def to_s
|
77
94
|
suffix = " #{(@percent_done * 100).floor}%".ljust(5)
|
78
95
|
workable_width = @max_width - Frame.prefix_width - suffix.size
|
@@ -1,34 +1,48 @@
|
|
1
1
|
# coding: utf-8
|
2
|
+
|
3
|
+
# typed: true
|
4
|
+
|
2
5
|
require 'io/console'
|
3
6
|
|
4
7
|
module CLI
|
5
8
|
module UI
|
6
9
|
module Prompt
|
7
10
|
class InteractiveOptions
|
11
|
+
extend T::Sig
|
12
|
+
|
8
13
|
DONE = 'Done'
|
9
14
|
CHECKBOX_ICON = { false => '☐', true => '☑' }
|
10
15
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
16
|
+
class << self
|
17
|
+
extend T::Sig
|
18
|
+
|
19
|
+
# Prompts the user with options
|
20
|
+
# Uses an interactive session to allow the user to pick an answer
|
21
|
+
# Can use arrows, y/n, numbers (1/2), and vim bindings to control
|
22
|
+
# For more than 9 options, hitting 'e', ':', or 'G' will enter select
|
23
|
+
# mode allowing the user to type in longer numbers
|
24
|
+
# Pressing 'f' or '/' will allow the user to filter the results
|
25
|
+
#
|
26
|
+
# https://user-images.githubusercontent.com/3074765/33797984-0ebb5e64-dcdf-11e7-9e7e-7204f279cece.gif
|
27
|
+
#
|
28
|
+
# ==== Example Usage:
|
29
|
+
#
|
30
|
+
# Ask an interactive question
|
31
|
+
# CLI::UI::Prompt::InteractiveOptions.call(%w(rails go python))
|
32
|
+
#
|
33
|
+
sig do
|
34
|
+
params(options: T::Array[String], multiple: T::Boolean, default: T.nilable(T.any(String, T::Array[String])))
|
35
|
+
.returns(T.any(String, T::Array[String]))
|
36
|
+
end
|
37
|
+
def call(options, multiple: false, default: nil)
|
38
|
+
list = new(options, multiple: multiple, default: default)
|
39
|
+
selected = list.call
|
40
|
+
case selected
|
41
|
+
when Array
|
42
|
+
selected.map { |s| T.must(options[s - 1]) }
|
43
|
+
else
|
44
|
+
T.must(options[selected - 1])
|
45
|
+
end
|
32
46
|
end
|
33
47
|
end
|
34
48
|
|
@@ -39,6 +53,10 @@ module CLI
|
|
39
53
|
#
|
40
54
|
# CLI::UI::Prompt::InteractiveOptions.new(%w(rails go python))
|
41
55
|
#
|
56
|
+
sig do
|
57
|
+
params(options: T::Array[String], multiple: T::Boolean, default: T.nilable(T.any(String, T::Array[String])))
|
58
|
+
.void
|
59
|
+
end
|
42
60
|
def initialize(options, multiple: false, default: nil)
|
43
61
|
@options = options
|
44
62
|
@active = 1
|
@@ -60,12 +78,13 @@ module CLI
|
|
60
78
|
end
|
61
79
|
end
|
62
80
|
@redraw = true
|
63
|
-
@presented_options = []
|
81
|
+
@presented_options = T.let([], T::Array[[String, T.nilable(Integer)]])
|
64
82
|
end
|
65
83
|
|
66
84
|
# Calls the +InteractiveOptions+ and asks the question
|
67
85
|
# Usually used from +self.call+
|
68
86
|
#
|
87
|
+
sig { returns(T.any(Integer, T::Array[Integer])) }
|
69
88
|
def call
|
70
89
|
calculate_option_line_lengths
|
71
90
|
CLI::UI.raw { print(ANSI.hide_cursor) }
|
@@ -85,31 +104,40 @@ module CLI
|
|
85
104
|
|
86
105
|
private
|
87
106
|
|
107
|
+
sig { void }
|
88
108
|
def calculate_option_line_lengths
|
89
109
|
@terminal_width_at_calculation_time = CLI::UI::Terminal.width
|
90
110
|
# options will be an array of questions but each option can be multi-line
|
91
111
|
# so to get the # of lines, you need to join then split
|
92
112
|
|
93
113
|
# since lines may be longer than the terminal is wide, we need to
|
94
|
-
# determine how many extra lines would be taken up by them
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
114
|
+
# determine how many extra lines would be taken up by them.
|
115
|
+
#
|
116
|
+
# To accomplish this we split the string by new lines and add the
|
117
|
+
# extra characters to the first line.
|
118
|
+
# Then we calculate how many lines would be needed to render the string
|
119
|
+
# based on the terminal width
|
120
|
+
# 3 = space before the number, the . after the number, the space after the .
|
121
|
+
# multiple check is for the space for the checkbox, if rendered
|
122
|
+
# options.count.to_s.size gets us the max size of the number we will display
|
123
|
+
extra_chars = @marker.length + 3 + @options.count.to_s.size + (@multiple ? 1 : 0)
|
100
124
|
|
101
125
|
@option_lengths = @options.map do |text|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
126
|
+
next 1 if text.empty?
|
127
|
+
|
128
|
+
# Find the length of all the lines in this string
|
129
|
+
non_empty_line_lengths = text.split("\n").reject(&:empty?).map do |line|
|
130
|
+
CLI::UI.fmt(line, enable_color: false).length
|
131
|
+
end
|
132
|
+
# The first line has the marker and number, so we add that so we can take it into account
|
133
|
+
non_empty_line_lengths[0] += extra_chars
|
134
|
+
# Finally, we need to calculate how many lines each one will take. We can do that by dividing each one
|
135
|
+
# by the width of the terminal, rounding up to the nearest natural number
|
136
|
+
non_empty_line_lengths.sum { |length| (length.to_f / @terminal_width_at_calculation_time).ceil }
|
110
137
|
end
|
111
138
|
end
|
112
139
|
|
140
|
+
sig { params(number_of_lines: Integer).void }
|
113
141
|
def reset_position(number_of_lines = num_lines)
|
114
142
|
# This will put us back at the beginning of the options
|
115
143
|
# When we redraw the options, they will be overwritten
|
@@ -118,6 +146,7 @@ module CLI
|
|
118
146
|
end
|
119
147
|
end
|
120
148
|
|
149
|
+
sig { params(number_of_lines: Integer).void }
|
121
150
|
def clear_output(number_of_lines = num_lines)
|
122
151
|
CLI::UI.raw do
|
123
152
|
# Write over all lines with whitespace
|
@@ -133,22 +162,26 @@ module CLI
|
|
133
162
|
|
134
163
|
# Don't use this in place of +@displaying_metadata+, this updates too
|
135
164
|
# quickly to be useful when drawing to the screen.
|
165
|
+
sig { returns(T::Boolean) }
|
136
166
|
def display_metadata?
|
137
167
|
filtering? || selecting? || has_filter?
|
138
168
|
end
|
139
169
|
|
170
|
+
sig { returns(Integer) }
|
140
171
|
def num_lines
|
141
172
|
calculate_option_line_lengths if terminal_width_changed?
|
142
173
|
|
143
174
|
option_length = presented_options.reduce(0) do |total_length, (_, option_number)|
|
144
175
|
# Handle continuation markers and "Done" option when multiple is true
|
145
176
|
next total_length + 1 if option_number.nil? || option_number.zero?
|
177
|
+
|
146
178
|
total_length + @option_lengths[option_number - 1]
|
147
179
|
end
|
148
180
|
|
149
181
|
option_length + (@displaying_metadata ? 1 : 0)
|
150
182
|
end
|
151
183
|
|
184
|
+
sig { returns(T::Boolean) }
|
152
185
|
def terminal_width_changed?
|
153
186
|
@terminal_width_at_calculation_time != CLI::UI::Terminal.width
|
154
187
|
end
|
@@ -158,6 +191,7 @@ module CLI
|
|
158
191
|
CTRL_C = "\u0003"
|
159
192
|
CTRL_D = "\u0004"
|
160
193
|
|
194
|
+
sig { void }
|
161
195
|
def up
|
162
196
|
active_index = @filtered_options.index { |_, num| num == @active } || 0
|
163
197
|
|
@@ -168,6 +202,7 @@ module CLI
|
|
168
202
|
@redraw = true
|
169
203
|
end
|
170
204
|
|
205
|
+
sig { void }
|
171
206
|
def down
|
172
207
|
active_index = @filtered_options.index { |_, num| num == @active } || 0
|
173
208
|
|
@@ -180,6 +215,7 @@ module CLI
|
|
180
215
|
|
181
216
|
# n is 1-indexed selection
|
182
217
|
# n == 0 if "Done" was selected in @multiple mode
|
218
|
+
sig { params(n: Integer).void }
|
183
219
|
def select_n(n)
|
184
220
|
if @multiple
|
185
221
|
if n == 0
|
@@ -200,24 +236,29 @@ module CLI
|
|
200
236
|
@redraw = true
|
201
237
|
end
|
202
238
|
|
239
|
+
sig { params(char: String).void }
|
203
240
|
def select_bool(char)
|
204
|
-
return unless (@options -
|
205
|
-
|
206
|
-
|
207
|
-
@
|
241
|
+
return unless (@options - ['yes', 'no']).empty?
|
242
|
+
|
243
|
+
index = T.must(@options.index { |o| o.start_with?(char) })
|
244
|
+
@active = index + 1
|
245
|
+
@answer = index + 1
|
208
246
|
@redraw = true
|
209
247
|
end
|
210
248
|
|
249
|
+
sig { params(char: String).void }
|
211
250
|
def build_selection(char)
|
212
251
|
@active = (@active.to_s + char).to_i
|
213
252
|
@redraw = true
|
214
253
|
end
|
215
254
|
|
255
|
+
sig { void }
|
216
256
|
def chop_selection
|
217
257
|
@active = @active.to_s.chop.to_i
|
218
258
|
@redraw = true
|
219
259
|
end
|
220
260
|
|
261
|
+
sig { params(char: String).void }
|
221
262
|
def update_search(char)
|
222
263
|
@redraw = true
|
223
264
|
|
@@ -235,25 +276,28 @@ module CLI
|
|
235
276
|
end
|
236
277
|
end
|
237
278
|
|
279
|
+
sig { void }
|
238
280
|
def select_current
|
239
281
|
# Prevent selection of invisible options
|
240
282
|
return unless presented_options.any? { |_, num| num == @active }
|
283
|
+
|
241
284
|
select_n(@active)
|
242
285
|
end
|
243
286
|
|
287
|
+
sig { void }
|
244
288
|
def process_input_until_redraw_required
|
245
289
|
@redraw = false
|
246
290
|
wait_for_user_input until @redraw
|
247
291
|
end
|
248
292
|
|
249
293
|
# rubocop:disable Style/WhenThen,Layout/SpaceBeforeSemicolon,Style/Semicolon
|
294
|
+
sig { void }
|
250
295
|
def wait_for_user_input
|
251
|
-
char = read_char
|
296
|
+
char = Prompt.read_char
|
252
297
|
@last_char = char
|
253
298
|
|
254
299
|
case char
|
255
|
-
when
|
256
|
-
when CTRL_C ; raise Interrupt
|
300
|
+
when CTRL_C, nil ; raise Interrupt
|
257
301
|
end
|
258
302
|
|
259
303
|
max_digit = [@options.size, 9].min.to_s
|
@@ -302,51 +346,48 @@ module CLI
|
|
302
346
|
end
|
303
347
|
end
|
304
348
|
end
|
305
|
-
# rubocop:enable Style/WhenThen,Layout/SpaceBeforeSemicolon
|
349
|
+
# rubocop:enable Style/WhenThen,Layout/SpaceBeforeSemicolon,Style/Semicolon
|
306
350
|
|
351
|
+
sig { returns(T::Boolean) }
|
307
352
|
def selecting?
|
308
353
|
@state == :line_select
|
309
354
|
end
|
310
355
|
|
356
|
+
sig { returns(T::Boolean) }
|
311
357
|
def filtering?
|
312
358
|
@state == :filter
|
313
359
|
end
|
314
360
|
|
361
|
+
sig { returns(T::Boolean) }
|
315
362
|
def has_filter?
|
316
363
|
!@filter.empty?
|
317
364
|
end
|
318
365
|
|
366
|
+
sig { void }
|
319
367
|
def start_filter
|
320
368
|
@state = :filter
|
321
369
|
@redraw = true
|
322
370
|
end
|
323
371
|
|
372
|
+
sig { void }
|
324
373
|
def start_line_select
|
325
374
|
@state = :line_select
|
326
375
|
@active = 0
|
327
376
|
@redraw = true
|
328
377
|
end
|
329
378
|
|
379
|
+
sig { void }
|
330
380
|
def stop_line_select
|
331
381
|
@state = :root
|
332
382
|
@active = 1 if @active.zero?
|
333
383
|
@redraw = true
|
334
384
|
end
|
335
385
|
|
336
|
-
|
337
|
-
if $stdin.tty? && !ENV['TEST']
|
338
|
-
$stdin.getch # raw mode for tty
|
339
|
-
else
|
340
|
-
$stdin.getc
|
341
|
-
end
|
342
|
-
rescue IOError
|
343
|
-
"\e"
|
344
|
-
end
|
345
|
-
|
386
|
+
sig { params(recalculate: T::Boolean).returns(T::Array[[String, T.nilable(Integer)]]) }
|
346
387
|
def presented_options(recalculate: false)
|
347
388
|
return @presented_options unless recalculate
|
348
389
|
|
349
|
-
@presented_options = @options.zip(1..
|
390
|
+
@presented_options = @options.zip(1..)
|
350
391
|
if has_filter?
|
351
392
|
@presented_options.select! { |option, _| option.downcase.include?(@filter.downcase) }
|
352
393
|
end
|
@@ -385,36 +426,44 @@ module CLI
|
|
385
426
|
@presented_options
|
386
427
|
end
|
387
428
|
|
429
|
+
sig { void }
|
388
430
|
def ensure_visible_is_active
|
389
431
|
unless presented_options.any? { |_, num| num == @active }
|
390
432
|
@active = presented_options.first&.last.to_i
|
391
433
|
end
|
392
434
|
end
|
393
435
|
|
436
|
+
sig { returns(Integer) }
|
394
437
|
def distance_from_selection_to_end
|
395
438
|
@presented_options.count - index_of_active_option
|
396
439
|
end
|
397
440
|
|
441
|
+
sig { returns(Integer) }
|
398
442
|
def distance_from_start_to_selection
|
399
443
|
index_of_active_option
|
400
444
|
end
|
401
445
|
|
446
|
+
sig { returns(Integer) }
|
402
447
|
def index_of_active_option
|
403
448
|
@presented_options.index { |_, num| num == @active }.to_i
|
404
449
|
end
|
405
450
|
|
451
|
+
sig { void }
|
406
452
|
def ensure_last_item_is_continuation_marker
|
407
|
-
@presented_options.push(['...', nil]) if @presented_options.last
|
453
|
+
@presented_options.push(['...', nil]) if @presented_options.last&.last
|
408
454
|
end
|
409
455
|
|
456
|
+
sig { void }
|
410
457
|
def ensure_first_item_is_continuation_marker
|
411
|
-
@presented_options.unshift(['...', nil]) if @presented_options.first
|
458
|
+
@presented_options.unshift(['...', nil]) if @presented_options.first&.last
|
412
459
|
end
|
413
460
|
|
461
|
+
sig { returns(Integer) }
|
414
462
|
def max_lines
|
415
463
|
CLI::UI::Terminal.height - (@displaying_metadata ? 3 : 2) # Keeps a one line question visible
|
416
464
|
end
|
417
465
|
|
466
|
+
sig { void }
|
418
467
|
def render_options
|
419
468
|
previously_displayed_lines = num_lines
|
420
469
|
|
@@ -436,11 +485,7 @@ module CLI
|
|
436
485
|
"Filter: #{filter_text}"
|
437
486
|
end
|
438
487
|
|
439
|
-
if metadata_text
|
440
|
-
CLI::UI.with_frame_color(:blue) do
|
441
|
-
puts CLI::UI.fmt(" {{green:#{metadata_text}}}#{ANSI.clear_to_end_of_line}")
|
442
|
-
end
|
443
|
-
end
|
488
|
+
puts CLI::UI.fmt(" {{green:#{metadata_text}}}#{ANSI.clear_to_end_of_line}") if metadata_text
|
444
489
|
|
445
490
|
options.each do |choice, num|
|
446
491
|
is_chosen = @multiple && num && @chosen[num - 1] && num != 0
|
@@ -449,8 +494,10 @@ module CLI
|
|
449
494
|
message = " #{num}#{num ? "." : " "}#{padding}"
|
450
495
|
|
451
496
|
format = '%s'
|
452
|
-
# If multiple, bold
|
453
|
-
|
497
|
+
# If multiple, bold selected. If not multiple, do not bold any options.
|
498
|
+
# Bolding options can cause confusion as some users may perceive bold white (default color) as selected
|
499
|
+
# rather than the actual selected color.
|
500
|
+
format = "{{bold:#{format}}}" if @multiple && is_chosen
|
454
501
|
format = "{{cyan:#{format}}}" if @multiple && is_chosen && num != @active
|
455
502
|
format = " #{format}"
|
456
503
|
|
@@ -460,15 +507,14 @@ module CLI
|
|
460
507
|
if num == @active
|
461
508
|
|
462
509
|
color = filtering? || selecting? ? 'green' : 'blue'
|
463
|
-
message = message.split("\n").map { |l| "{{#{color}
|
510
|
+
message = message.split("\n").map { |l| "{{#{color}:#{@marker} #{l.strip}}}" }.join("\n")
|
464
511
|
end
|
465
512
|
|
466
|
-
CLI::UI.
|
467
|
-
puts CLI::UI.fmt(message)
|
468
|
-
end
|
513
|
+
puts CLI::UI.fmt(message)
|
469
514
|
end
|
470
515
|
end
|
471
516
|
|
517
|
+
sig { params(format: String, choice: String).returns(String) }
|
472
518
|
def format_choice(format, choice)
|
473
519
|
eol = CLI::UI::ANSI.clear_to_end_of_line
|
474
520
|
lines = choice.split("\n")
|
@@ -1,20 +1,28 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
1
3
|
module CLI
|
2
4
|
module UI
|
3
5
|
module Prompt
|
4
6
|
# A class that handles the various options of an InteractivePrompt and their callbacks
|
5
7
|
class OptionsHandler
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { void }
|
6
11
|
def initialize
|
7
12
|
@options = {}
|
8
13
|
end
|
9
14
|
|
15
|
+
sig { returns(T::Array[String]) }
|
10
16
|
def options
|
11
17
|
@options.keys
|
12
18
|
end
|
13
19
|
|
20
|
+
sig { params(option: String, handler: T.proc.params(option: String).returns(String)).void }
|
14
21
|
def option(option, &handler)
|
15
22
|
@options[option] = handler
|
16
23
|
end
|
17
24
|
|
25
|
+
sig { params(options: T.any(T::Array[String], String)).returns(String) }
|
18
26
|
def call(options)
|
19
27
|
case options
|
20
28
|
when Array
|