tty-prompt 0.18.1 → 0.23.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +95 -0
- data/README.md +598 -256
- data/lib/tty-prompt.rb +1 -2
- data/lib/tty/prompt.rb +192 -144
- data/lib/tty/prompt/answers_collector.rb +5 -5
- data/lib/tty/prompt/{enum_paginator.rb → block_paginator.rb} +20 -19
- data/lib/tty/prompt/choice.rb +9 -10
- data/lib/tty/prompt/choices.rb +30 -12
- data/lib/tty/prompt/confirm_question.rb +42 -16
- data/lib/tty/prompt/const.rb +17 -0
- data/lib/tty/prompt/converter_dsl.rb +6 -7
- data/lib/tty/prompt/converter_registry.rb +31 -26
- data/lib/tty/prompt/converters.rb +139 -32
- data/lib/tty/prompt/enum_list.rb +81 -26
- data/lib/tty/prompt/errors.rb +31 -0
- data/lib/tty/prompt/evaluator.rb +2 -2
- data/lib/tty/prompt/expander.rb +45 -15
- data/lib/tty/prompt/keypress.rb +33 -36
- data/lib/tty/prompt/list.rb +198 -63
- data/lib/tty/prompt/mask_question.rb +11 -8
- data/lib/tty/prompt/multi_list.rb +131 -28
- data/lib/tty/prompt/multiline.rb +9 -7
- data/lib/tty/prompt/paginator.rb +38 -26
- data/lib/tty/prompt/question.rb +92 -37
- data/lib/tty/prompt/question/checks.rb +20 -2
- data/lib/tty/prompt/question/modifier.rb +4 -2
- data/lib/tty/prompt/question/validation.rb +3 -3
- data/lib/tty/prompt/selected_choices.rb +77 -0
- data/lib/tty/prompt/slider.rb +125 -30
- data/lib/tty/prompt/statement.rb +3 -3
- data/lib/tty/prompt/suggestion.rb +7 -6
- data/lib/tty/prompt/symbols.rb +58 -34
- data/lib/tty/prompt/test.rb +36 -0
- data/lib/tty/prompt/timer.rb +75 -0
- data/lib/tty/prompt/utils.rb +1 -3
- data/lib/tty/prompt/version.rb +1 -1
- metadata +29 -227
- data/Rakefile +0 -8
- data/examples/ask.rb +0 -7
- data/examples/ask_valid.rb +0 -12
- data/examples/collect.rb +0 -21
- data/examples/echo.rb +0 -11
- data/examples/enum_select.rb +0 -7
- data/examples/enum_select_disabled.rb +0 -16
- data/examples/enum_select_paged.rb +0 -9
- data/examples/enum_select_wrapped.rb +0 -15
- data/examples/expand.rb +0 -29
- data/examples/in.rb +0 -9
- data/examples/inputs.rb +0 -10
- data/examples/key_events.rb +0 -15
- data/examples/keypress.rb +0 -9
- data/examples/mask.rb +0 -13
- data/examples/multi_select.rb +0 -8
- data/examples/multi_select_disabled.rb +0 -17
- data/examples/multi_select_paged.rb +0 -9
- data/examples/multi_select_wrapped.rb +0 -15
- data/examples/multiline.rb +0 -9
- data/examples/pause.rb +0 -9
- data/examples/select.rb +0 -24
- data/examples/select_disabled.rb +0 -18
- data/examples/select_enum.rb +0 -8
- data/examples/select_filtered.rb +0 -11
- data/examples/select_paginated.rb +0 -11
- data/examples/select_wrapped.rb +0 -15
- data/examples/slider.rb +0 -6
- data/examples/validation.rb +0 -9
- data/examples/yes_no.rb +0 -7
- data/lib/tty/prompt/messages.rb +0 -49
- data/lib/tty/prompt/timeout.rb +0 -78
- data/lib/tty/test_prompt.rb +0 -20
- data/spec/spec_helper.rb +0 -45
- data/spec/unit/ask_spec.rb +0 -132
- data/spec/unit/choice/eql_spec.rb +0 -22
- data/spec/unit/choice/from_spec.rb +0 -96
- data/spec/unit/choices/add_spec.rb +0 -12
- data/spec/unit/choices/each_spec.rb +0 -13
- data/spec/unit/choices/find_by_spec.rb +0 -10
- data/spec/unit/choices/new_spec.rb +0 -10
- data/spec/unit/choices/pluck_spec.rb +0 -9
- data/spec/unit/collect_spec.rb +0 -96
- data/spec/unit/converters/convert_bool_spec.rb +0 -58
- data/spec/unit/converters/convert_char_spec.rb +0 -11
- data/spec/unit/converters/convert_custom_spec.rb +0 -14
- data/spec/unit/converters/convert_date_spec.rb +0 -34
- data/spec/unit/converters/convert_file_spec.rb +0 -18
- data/spec/unit/converters/convert_number_spec.rb +0 -39
- data/spec/unit/converters/convert_path_spec.rb +0 -15
- data/spec/unit/converters/convert_range_spec.rb +0 -22
- data/spec/unit/converters/convert_regex_spec.rb +0 -12
- data/spec/unit/converters/convert_string_spec.rb +0 -21
- data/spec/unit/converters/on_error_spec.rb +0 -9
- data/spec/unit/distance/distance_spec.rb +0 -73
- data/spec/unit/enum_paginator_spec.rb +0 -75
- data/spec/unit/enum_select_spec.rb +0 -446
- data/spec/unit/error_spec.rb +0 -20
- data/spec/unit/evaluator_spec.rb +0 -67
- data/spec/unit/expand_spec.rb +0 -198
- data/spec/unit/keypress_spec.rb +0 -72
- data/spec/unit/mask_spec.rb +0 -132
- data/spec/unit/multi_select_spec.rb +0 -511
- data/spec/unit/multiline_spec.rb +0 -77
- data/spec/unit/new_spec.rb +0 -20
- data/spec/unit/ok_spec.rb +0 -10
- data/spec/unit/paginator_spec.rb +0 -73
- data/spec/unit/question/checks_spec.rb +0 -97
- data/spec/unit/question/default_spec.rb +0 -31
- data/spec/unit/question/echo_spec.rb +0 -38
- data/spec/unit/question/in_spec.rb +0 -115
- data/spec/unit/question/initialize_spec.rb +0 -12
- data/spec/unit/question/modifier/apply_to_spec.rb +0 -24
- data/spec/unit/question/modifier/letter_case_spec.rb +0 -41
- data/spec/unit/question/modifier/whitespace_spec.rb +0 -51
- data/spec/unit/question/modify_spec.rb +0 -41
- data/spec/unit/question/required_spec.rb +0 -92
- data/spec/unit/question/validate_spec.rb +0 -115
- data/spec/unit/question/validation/call_spec.rb +0 -31
- data/spec/unit/question/validation/coerce_spec.rb +0 -30
- data/spec/unit/result_spec.rb +0 -40
- data/spec/unit/say_spec.rb +0 -67
- data/spec/unit/select_spec.rb +0 -660
- data/spec/unit/slider_spec.rb +0 -100
- data/spec/unit/statement/initialize_spec.rb +0 -15
- data/spec/unit/subscribe_spec.rb +0 -22
- data/spec/unit/suggest_spec.rb +0 -28
- data/spec/unit/warn_spec.rb +0 -21
- data/spec/unit/yes_no_spec.rb +0 -251
- data/tasks/console.rake +0 -11
- data/tasks/coverage.rake +0 -11
- data/tasks/spec.rake +0 -29
- data/tty-prompt.gemspec +0 -33
data/lib/tty/prompt/question.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative
|
6
|
-
require_relative
|
7
|
-
require_relative
|
8
|
-
require_relative
|
3
|
+
require_relative "converters"
|
4
|
+
require_relative "evaluator"
|
5
|
+
require_relative "question/modifier"
|
6
|
+
require_relative "question/validation"
|
7
|
+
require_relative "question/checks"
|
8
|
+
require_relative "utils"
|
9
9
|
|
10
10
|
module TTY
|
11
11
|
# A class responsible for shell prompt interactions.
|
@@ -34,22 +34,31 @@ module TTY
|
|
34
34
|
# Initialize a Question
|
35
35
|
#
|
36
36
|
# @api public
|
37
|
-
def initialize(prompt, options
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
@
|
45
|
-
@
|
46
|
-
@
|
37
|
+
def initialize(prompt, **options)
|
38
|
+
# Option deprecation
|
39
|
+
if options[:validation]
|
40
|
+
warn "[DEPRECATION] The `:validation` option is deprecated. Use `:validate` instead."
|
41
|
+
options[:validate] = options[:validation]
|
42
|
+
end
|
43
|
+
|
44
|
+
@prompt = prompt
|
45
|
+
@prefix = options.fetch(:prefix) { @prompt.prefix }
|
46
|
+
@default = options.fetch(:default) { UndefinedSetting }
|
47
|
+
@required = options.fetch(:required) { false }
|
48
|
+
@echo = options.fetch(:echo) { true }
|
49
|
+
@in = options.fetch(:in) { UndefinedSetting }
|
50
|
+
@modifier = options.fetch(:modifier) { [] }
|
51
|
+
@validation = options.fetch(:validate) { UndefinedSetting }
|
52
|
+
@convert = options.fetch(:convert) { UndefinedSetting }
|
47
53
|
@active_color = options.fetch(:active_color) { @prompt.active_color }
|
48
|
-
@help_color
|
49
|
-
@error_color
|
50
|
-
@
|
51
|
-
@
|
52
|
-
@
|
54
|
+
@help_color = options.fetch(:help_color) { @prompt.help_color }
|
55
|
+
@error_color = options.fetch(:error_color) { :red }
|
56
|
+
@value = options.fetch(:value) { UndefinedSetting }
|
57
|
+
@quiet = options.fetch(:quiet) { @prompt.quiet }
|
58
|
+
@messages = Utils.deep_copy(options.fetch(:messages) { {} })
|
59
|
+
@done = false
|
60
|
+
@first_render = true
|
61
|
+
@input = nil
|
53
62
|
|
54
63
|
@evaluator = Evaluator.new(self)
|
55
64
|
|
@@ -58,6 +67,7 @@ module TTY
|
|
58
67
|
@evaluator << CheckRange
|
59
68
|
@evaluator << CheckValidation
|
60
69
|
@evaluator << CheckModifier
|
70
|
+
@evaluator << CheckConversion
|
61
71
|
end
|
62
72
|
|
63
73
|
# Stores all the error messages displayed to user
|
@@ -83,7 +93,7 @@ module TTY
|
|
83
93
|
if template && !template.match(/\%\{/).nil?
|
84
94
|
[template % tokens]
|
85
95
|
else
|
86
|
-
[template ||
|
96
|
+
[template || ""]
|
87
97
|
end
|
88
98
|
end
|
89
99
|
|
@@ -94,8 +104,7 @@ module TTY
|
|
94
104
|
# @return [self]
|
95
105
|
#
|
96
106
|
# @api public
|
97
|
-
def call(message, &block)
|
98
|
-
return if Utils.blank?(message)
|
107
|
+
def call(message = "", &block)
|
99
108
|
@message = message
|
100
109
|
block.call(self) if block
|
101
110
|
@prompt.subscribe(self) do
|
@@ -121,8 +130,8 @@ module TTY
|
|
121
130
|
total_lines = @prompt.count_screen_lines(input_line)
|
122
131
|
@prompt.print(refresh(question.lines.count, total_lines))
|
123
132
|
end
|
124
|
-
@prompt.print(render_question)
|
125
|
-
|
133
|
+
@prompt.print(render_question) unless @quiet
|
134
|
+
result.value
|
126
135
|
end
|
127
136
|
|
128
137
|
# Render question
|
@@ -131,13 +140,16 @@ module TTY
|
|
131
140
|
#
|
132
141
|
# @api private
|
133
142
|
def render_question
|
134
|
-
header = [
|
143
|
+
header = []
|
144
|
+
if !Utils.blank?(@prefix) || !Utils.blank?(message)
|
145
|
+
header << "#{@prefix}#{message} "
|
146
|
+
end
|
135
147
|
if !echo?
|
136
148
|
header
|
137
149
|
elsif @done
|
138
150
|
header << @prompt.decorate(@input.to_s, @active_color)
|
139
151
|
elsif default? && !Utils.blank?(@default)
|
140
|
-
header << @prompt.decorate("(#{default})", @help_color) +
|
152
|
+
header << @prompt.decorate("(#{default})", @help_color) + " "
|
141
153
|
end
|
142
154
|
header << "\n" if @done
|
143
155
|
header.join
|
@@ -158,7 +170,12 @@ module TTY
|
|
158
170
|
#
|
159
171
|
# @api private
|
160
172
|
def read_input(question)
|
161
|
-
|
173
|
+
options = { echo: echo }
|
174
|
+
if value? && @first_render
|
175
|
+
options[:value] = @value
|
176
|
+
@first_render = false
|
177
|
+
end
|
178
|
+
@prompt.read_line(question, **options).chomp
|
162
179
|
end
|
163
180
|
|
164
181
|
# Handle error condition
|
@@ -168,7 +185,7 @@ module TTY
|
|
168
185
|
# @api private
|
169
186
|
def render_error(errors)
|
170
187
|
errors.reduce([]) do |acc, err|
|
171
|
-
acc << @prompt.decorate(
|
188
|
+
acc << @prompt.decorate(">>", :red) + " " + err
|
172
189
|
acc
|
173
190
|
end.join("\n")
|
174
191
|
end
|
@@ -202,8 +219,13 @@ module TTY
|
|
202
219
|
#
|
203
220
|
# @api private
|
204
221
|
def convert_result(value)
|
205
|
-
if convert?
|
206
|
-
|
222
|
+
if convert? && !Utils.blank?(value)
|
223
|
+
case @convert
|
224
|
+
when Proc
|
225
|
+
@convert.call(value)
|
226
|
+
else
|
227
|
+
Converters.convert(@convert, value)
|
228
|
+
end
|
207
229
|
else
|
208
230
|
value
|
209
231
|
end
|
@@ -212,8 +234,13 @@ module TTY
|
|
212
234
|
# Specify answer conversion
|
213
235
|
#
|
214
236
|
# @api public
|
215
|
-
def convert(value)
|
216
|
-
|
237
|
+
def convert(value = (not_set = true), message = nil)
|
238
|
+
messages[:convert?] = message if message
|
239
|
+
if not_set
|
240
|
+
@convert
|
241
|
+
else
|
242
|
+
@convert = value
|
243
|
+
end
|
217
244
|
end
|
218
245
|
|
219
246
|
# Check if conversion is set
|
@@ -230,6 +257,7 @@ module TTY
|
|
230
257
|
# @api public
|
231
258
|
def default(value = (not_set = true))
|
232
259
|
return @default if not_set
|
260
|
+
|
233
261
|
@default = value
|
234
262
|
end
|
235
263
|
|
@@ -250,9 +278,10 @@ module TTY
|
|
250
278
|
def required(value = (not_set = true), message = nil)
|
251
279
|
messages[:required?] = message if message
|
252
280
|
return @required if not_set
|
281
|
+
|
253
282
|
@required = value
|
254
283
|
end
|
255
|
-
|
284
|
+
alias required? required
|
256
285
|
|
257
286
|
# Set validation rule for an argument
|
258
287
|
#
|
@@ -266,6 +295,22 @@ module TTY
|
|
266
295
|
@validation = (value || block)
|
267
296
|
end
|
268
297
|
|
298
|
+
# Prepopulate input with custom content
|
299
|
+
#
|
300
|
+
# @api public
|
301
|
+
def value(val)
|
302
|
+
return @value if val.nil?
|
303
|
+
|
304
|
+
@value = val
|
305
|
+
end
|
306
|
+
|
307
|
+
# Check if custom value is present
|
308
|
+
#
|
309
|
+
# @api private
|
310
|
+
def value?
|
311
|
+
@value != UndefinedSetting
|
312
|
+
end
|
313
|
+
|
269
314
|
def validation?
|
270
315
|
@validation != UndefinedSetting
|
271
316
|
end
|
@@ -285,18 +330,20 @@ module TTY
|
|
285
330
|
# @api public
|
286
331
|
def echo(value = nil)
|
287
332
|
return @echo if value.nil?
|
333
|
+
|
288
334
|
@echo = value
|
289
335
|
end
|
290
|
-
|
336
|
+
alias echo? echo
|
291
337
|
|
292
338
|
# Turn raw mode on or off. This enables character-based input.
|
293
339
|
#
|
294
340
|
# @api public
|
295
341
|
def raw(value = nil)
|
296
342
|
return @raw if value.nil?
|
343
|
+
|
297
344
|
@raw = value
|
298
345
|
end
|
299
|
-
|
346
|
+
alias raw? raw
|
300
347
|
|
301
348
|
# Set expected range of values
|
302
349
|
#
|
@@ -309,6 +356,7 @@ module TTY
|
|
309
356
|
@in = Converters.convert(:range, @in)
|
310
357
|
end
|
311
358
|
return @in if not_set
|
359
|
+
|
312
360
|
@in = Converters.convert(:range, value)
|
313
361
|
end
|
314
362
|
|
@@ -321,6 +369,13 @@ module TTY
|
|
321
369
|
@in != UndefinedSetting
|
322
370
|
end
|
323
371
|
|
372
|
+
# Set quiet mode.
|
373
|
+
#
|
374
|
+
# @api public
|
375
|
+
def quiet(value)
|
376
|
+
@quiet = value
|
377
|
+
end
|
378
|
+
|
324
379
|
# @api public
|
325
380
|
def to_s
|
326
381
|
message.to_s
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "../const"
|
4
|
+
|
3
5
|
module TTY
|
4
6
|
class Prompt
|
5
7
|
class Question
|
@@ -40,7 +42,7 @@ module TTY
|
|
40
42
|
(question.in? && question.in.include?(cast(value)))
|
41
43
|
[value]
|
42
44
|
else
|
43
|
-
tokens = {value: value, in: question.in}
|
45
|
+
tokens = { value: value, in: question.in }
|
44
46
|
[value, question.message_for(:range?, tokens)]
|
45
47
|
end
|
46
48
|
end
|
@@ -54,7 +56,7 @@ module TTY
|
|
54
56
|
Validation.new(question.validation).call(value))
|
55
57
|
[value]
|
56
58
|
else
|
57
|
-
tokens = {valid: question.validation.inspect}
|
59
|
+
tokens = { valid: question.validation.inspect }
|
58
60
|
[value, question.message_for(:valid?, tokens)]
|
59
61
|
end
|
60
62
|
end
|
@@ -81,6 +83,22 @@ module TTY
|
|
81
83
|
end
|
82
84
|
end
|
83
85
|
end
|
86
|
+
|
87
|
+
class CheckConversion
|
88
|
+
def self.call(question, value)
|
89
|
+
if question.convert? && !Utils.blank?(value)
|
90
|
+
result = question.convert_result(value)
|
91
|
+
if result == Const::Undefined
|
92
|
+
tokens = { value: value, type: question.convert }
|
93
|
+
[value, question.message_for(:convert?, tokens)]
|
94
|
+
else
|
95
|
+
[result]
|
96
|
+
end
|
97
|
+
else
|
98
|
+
[value]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
84
102
|
end # Checks
|
85
103
|
end # Question
|
86
104
|
end # Prompt
|
@@ -49,6 +49,7 @@ module TTY
|
|
49
49
|
# @api public
|
50
50
|
def self.letter_case(mod, value)
|
51
51
|
return value unless value.is_a?(String)
|
52
|
+
|
52
53
|
case mod
|
53
54
|
when :up, :upcase, :uppercase
|
54
55
|
value.upcase
|
@@ -75,15 +76,16 @@ module TTY
|
|
75
76
|
# @api public
|
76
77
|
def self.whitespace(mod, value)
|
77
78
|
return value unless value.is_a?(String)
|
79
|
+
|
78
80
|
case mod
|
79
81
|
when :trim, :strip
|
80
82
|
value.strip
|
81
83
|
when :chomp
|
82
84
|
value.chomp
|
83
85
|
when :collapse
|
84
|
-
value.gsub(/\s+/,
|
86
|
+
value.gsub(/\s+/, " ")
|
85
87
|
when :remove
|
86
|
-
value.gsub(/\s+/,
|
88
|
+
value.gsub(/\s+/, "")
|
87
89
|
else
|
88
90
|
value
|
89
91
|
end
|
@@ -57,11 +57,11 @@ module TTY
|
|
57
57
|
def call(input)
|
58
58
|
if pattern.is_a?(String) || pattern.is_a?(Symbol)
|
59
59
|
VALIDATORS.key?(pattern.to_sym)
|
60
|
-
!VALIDATORS[pattern.to_sym].match(input).nil?
|
60
|
+
!VALIDATORS[pattern.to_sym].match(input.to_s).nil?
|
61
61
|
elsif pattern.is_a?(Regexp)
|
62
|
-
!pattern.match(input).nil?
|
62
|
+
!pattern.match(input.to_s).nil?
|
63
63
|
elsif pattern.is_a?(Proc)
|
64
|
-
result = pattern.call(input)
|
64
|
+
result = pattern.call(input.to_s)
|
65
65
|
result.nil? ? false : result
|
66
66
|
else false
|
67
67
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Prompt
|
5
|
+
# @api private
|
6
|
+
class SelectedChoices
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
attr_reader :size
|
10
|
+
|
11
|
+
# Create selected choices
|
12
|
+
#
|
13
|
+
# @param [Array<Choice>] selected
|
14
|
+
# @param [Array<Integer>] indexes
|
15
|
+
#
|
16
|
+
# @api public
|
17
|
+
def initialize(selected = [], indexes = [])
|
18
|
+
@selected = selected
|
19
|
+
@indexes = indexes
|
20
|
+
@size = @selected.size
|
21
|
+
end
|
22
|
+
|
23
|
+
# Clear selected choices
|
24
|
+
#
|
25
|
+
# @api public
|
26
|
+
def clear
|
27
|
+
@indexes.clear
|
28
|
+
@selected.clear
|
29
|
+
@size = 0
|
30
|
+
end
|
31
|
+
|
32
|
+
# Iterate over selected choices
|
33
|
+
#
|
34
|
+
# @api public
|
35
|
+
def each(&block)
|
36
|
+
return to_enum unless block_given?
|
37
|
+
|
38
|
+
@selected.each(&block)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Insert choice at index
|
42
|
+
#
|
43
|
+
# @param [Integer] index
|
44
|
+
# @param [Choice] choice
|
45
|
+
#
|
46
|
+
# @api public
|
47
|
+
def insert(index, choice)
|
48
|
+
insert_idx = find_index_by { |i| index < @indexes[i] }
|
49
|
+
insert_idx ||= -1
|
50
|
+
@indexes.insert(insert_idx, index)
|
51
|
+
@selected.insert(insert_idx, choice)
|
52
|
+
@size += 1
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
# Delete choice at index
|
57
|
+
#
|
58
|
+
# @return [Choice]
|
59
|
+
# the deleted choice
|
60
|
+
#
|
61
|
+
# @api public
|
62
|
+
def delete_at(index)
|
63
|
+
delete_idx = @indexes.each_index.find { |i| index == @indexes[i] }
|
64
|
+
return nil unless delete_idx
|
65
|
+
|
66
|
+
@indexes.delete_at(delete_idx)
|
67
|
+
choice = @selected.delete_at(delete_idx)
|
68
|
+
@size -= 1
|
69
|
+
choice
|
70
|
+
end
|
71
|
+
|
72
|
+
def find_index_by(&search)
|
73
|
+
(0...@size).bsearch(&search)
|
74
|
+
end
|
75
|
+
end # SelectedChoices
|
76
|
+
end # Prompt
|
77
|
+
end # TTY
|
data/lib/tty/prompt/slider.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'symbols'
|
4
|
-
|
5
3
|
module TTY
|
6
4
|
# A class responsible for shell prompt interactions.
|
7
5
|
class Prompt
|
@@ -9,11 +7,9 @@ module TTY
|
|
9
7
|
#
|
10
8
|
# @api public
|
11
9
|
class Slider
|
12
|
-
|
13
|
-
|
14
|
-
HELP = '(Use arrow keys, press Enter to select)'.freeze
|
10
|
+
HELP = "(Use %s arrow keys, press Enter to select)"
|
15
11
|
|
16
|
-
FORMAT =
|
12
|
+
FORMAT = ":slider %s"
|
17
13
|
|
18
14
|
# Initailize a Slider
|
19
15
|
#
|
@@ -27,20 +23,37 @@ module TTY
|
|
27
23
|
# @option options [String] :format The display format
|
28
24
|
#
|
29
25
|
# @api public
|
30
|
-
def initialize(prompt, options
|
26
|
+
def initialize(prompt, **options)
|
31
27
|
@prompt = prompt
|
32
28
|
@prefix = options.fetch(:prefix) { @prompt.prefix }
|
33
|
-
@
|
34
|
-
@
|
35
|
-
@
|
29
|
+
@choices = Choices.new
|
30
|
+
@min = options.fetch(:min, 0)
|
31
|
+
@max = options.fetch(:max, 10)
|
32
|
+
@step = options.fetch(:step, 1)
|
36
33
|
@default = options[:default]
|
37
34
|
@active_color = options.fetch(:active_color) { @prompt.active_color }
|
38
35
|
@help_color = options.fetch(:help_color) { @prompt.help_color }
|
39
36
|
@format = options.fetch(:format) { FORMAT }
|
37
|
+
@quiet = options.fetch(:quiet) { @prompt.quiet }
|
38
|
+
@help = options[:help]
|
39
|
+
@show_help = options.fetch(:show_help) { :start }
|
40
|
+
@symbols = @prompt.symbols.merge(options.fetch(:symbols, {}))
|
40
41
|
@first_render = true
|
41
42
|
@done = false
|
42
43
|
end
|
43
44
|
|
45
|
+
# Change symbols used by this prompt
|
46
|
+
#
|
47
|
+
# @param [Hash] new_symbols
|
48
|
+
# the new symbols to use
|
49
|
+
#
|
50
|
+
# @api public
|
51
|
+
def symbols(new_symbols = (not_set = true))
|
52
|
+
return @symbols if not_set
|
53
|
+
|
54
|
+
@symbols.merge!(new_symbols)
|
55
|
+
end
|
56
|
+
|
44
57
|
# Setup initial active position
|
45
58
|
#
|
46
59
|
# @return [Integer]
|
@@ -48,19 +61,43 @@ module TTY
|
|
48
61
|
# @api private
|
49
62
|
def initial
|
50
63
|
if @default.nil?
|
51
|
-
|
64
|
+
# no default - choose the middle option
|
65
|
+
choices.size / 2
|
66
|
+
elsif default_choice = choices.find_by(:name, @default)
|
67
|
+
# found a Choice by name - use it
|
68
|
+
choices.index(default_choice)
|
52
69
|
else
|
53
|
-
|
70
|
+
# default is the index number
|
71
|
+
@default - 1
|
54
72
|
end
|
55
73
|
end
|
56
74
|
|
57
|
-
#
|
75
|
+
# Default help text
|
76
|
+
#
|
77
|
+
# @api public
|
78
|
+
def default_help
|
79
|
+
arrows = @symbols[:arrow_left] + "/" + @symbols[:arrow_right]
|
80
|
+
sprintf(HELP, arrows)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Set help text
|
58
84
|
#
|
59
|
-
# @
|
85
|
+
# @param [String] text
|
60
86
|
#
|
61
|
-
# @
|
62
|
-
def
|
63
|
-
|
87
|
+
# @api private
|
88
|
+
def help(text = (not_set = true))
|
89
|
+
return @help if !@help.nil? && not_set
|
90
|
+
|
91
|
+
@help = (@help.nil? && not_set) ? default_help : text
|
92
|
+
end
|
93
|
+
|
94
|
+
# Change when help is displayed
|
95
|
+
#
|
96
|
+
# @api public
|
97
|
+
def show_help(value = (not_set = true))
|
98
|
+
return @show_ehlp if not_set
|
99
|
+
|
100
|
+
@show_help = value
|
64
101
|
end
|
65
102
|
|
66
103
|
# @api public
|
@@ -83,19 +120,57 @@ module TTY
|
|
83
120
|
@step = value
|
84
121
|
end
|
85
122
|
|
123
|
+
# Add a single choice
|
124
|
+
#
|
125
|
+
# @api public
|
126
|
+
def choice(*value, &block)
|
127
|
+
if block
|
128
|
+
@choices << (value << block)
|
129
|
+
else
|
130
|
+
@choices << value
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Add multiple choices
|
135
|
+
#
|
136
|
+
# @param [Array[Object]] values
|
137
|
+
# the values to add as choices
|
138
|
+
#
|
139
|
+
# @api public
|
140
|
+
def choices(values = (not_set = true))
|
141
|
+
if not_set
|
142
|
+
@choices
|
143
|
+
else
|
144
|
+
values.each { |val| @choices << val }
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# @api public
|
86
149
|
def format(value)
|
87
150
|
@format = value
|
88
151
|
end
|
89
152
|
|
153
|
+
# Set quiet mode.
|
154
|
+
#
|
155
|
+
# @api public
|
156
|
+
def quiet(value)
|
157
|
+
@quiet = value
|
158
|
+
end
|
159
|
+
|
90
160
|
# Call the slider by passing question
|
91
161
|
#
|
92
162
|
# @param [String] question
|
93
163
|
# the question to ask
|
94
164
|
#
|
95
165
|
# @apu public
|
96
|
-
def call(question, &block)
|
166
|
+
def call(question, possibilities = nil, &block)
|
97
167
|
@question = question
|
168
|
+
choices(possibilities) if possibilities
|
98
169
|
block.call(self) if block
|
170
|
+
# set up a Choices collection for min, max, step
|
171
|
+
# if no possibilities were supplied
|
172
|
+
choices((@min..@max).step(@step).to_a) if @choices.empty?
|
173
|
+
|
99
174
|
@active = initial
|
100
175
|
@prompt.subscribe(self) do
|
101
176
|
render
|
@@ -108,7 +183,7 @@ module TTY
|
|
108
183
|
alias keydown keyleft
|
109
184
|
|
110
185
|
def keyright(*)
|
111
|
-
@active += 1 if (@active + 1) <
|
186
|
+
@active += 1 if (@active + 1) < choices.size
|
112
187
|
end
|
113
188
|
alias keyup keyright
|
114
189
|
|
@@ -120,6 +195,20 @@ module TTY
|
|
120
195
|
|
121
196
|
private
|
122
197
|
|
198
|
+
# Check if help is shown only on start
|
199
|
+
#
|
200
|
+
# @api private
|
201
|
+
def help_start?
|
202
|
+
@show_help =~ /start/i
|
203
|
+
end
|
204
|
+
|
205
|
+
# Check if help is always displayed
|
206
|
+
#
|
207
|
+
# @api private
|
208
|
+
def help_always?
|
209
|
+
@show_help =~ /always/i
|
210
|
+
end
|
211
|
+
|
123
212
|
# Render an interactive range slider.
|
124
213
|
#
|
125
214
|
# @api private
|
@@ -131,7 +220,7 @@ module TTY
|
|
131
220
|
@prompt.read_keypress
|
132
221
|
refresh(question.lines.count)
|
133
222
|
end
|
134
|
-
@prompt.print(render_question)
|
223
|
+
@prompt.print(render_question) unless @quiet
|
135
224
|
answer
|
136
225
|
ensure
|
137
226
|
@prompt.print(@prompt.show)
|
@@ -147,11 +236,11 @@ module TTY
|
|
147
236
|
@prompt.print(@prompt.clear_lines(lines))
|
148
237
|
end
|
149
238
|
|
150
|
-
# @return [Integer]
|
239
|
+
# @return [Integer, String]
|
151
240
|
#
|
152
241
|
# @api private
|
153
242
|
def answer
|
154
|
-
|
243
|
+
choices[@active].value
|
155
244
|
end
|
156
245
|
|
157
246
|
# Render question with the slider
|
@@ -162,13 +251,14 @@ module TTY
|
|
162
251
|
def render_question
|
163
252
|
header = ["#{@prefix}#{@question} "]
|
164
253
|
if @done
|
165
|
-
header << @prompt.decorate(
|
254
|
+
header << @prompt.decorate(choices[@active].to_s, @active_color)
|
166
255
|
header << "\n"
|
167
256
|
else
|
168
257
|
header << render_slider
|
169
258
|
end
|
170
|
-
if @first_render
|
171
|
-
|
259
|
+
if @first_render && (help_start? || help_always?) ||
|
260
|
+
(help_always? && !@done)
|
261
|
+
header << "\n" + @prompt.decorate(help, @help_color)
|
172
262
|
@first_render = false
|
173
263
|
end
|
174
264
|
header.join
|
@@ -180,11 +270,16 @@ module TTY
|
|
180
270
|
#
|
181
271
|
# @api private
|
182
272
|
def render_slider
|
183
|
-
slider = (symbols[:line] * @active) +
|
184
|
-
@prompt.decorate(symbols[:
|
185
|
-
(symbols[:line] * (
|
186
|
-
value =
|
187
|
-
@format
|
273
|
+
slider = (@symbols[:line] * @active) +
|
274
|
+
@prompt.decorate(@symbols[:bullet], @active_color) +
|
275
|
+
(@symbols[:line] * (choices.size - @active - 1))
|
276
|
+
value = choices[@active].name
|
277
|
+
case @format
|
278
|
+
when Proc
|
279
|
+
@format.call(slider, value)
|
280
|
+
else
|
281
|
+
@format.gsub(":slider", slider) % [value]
|
282
|
+
end
|
188
283
|
end
|
189
284
|
end # Slider
|
190
285
|
end # Prompt
|