tty-prompt 0.18.1 → 0.23.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|