tty-prompt 0.18.1 → 0.19.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 +23 -0
- data/README.md +174 -63
- data/Rakefile +2 -2
- data/examples/ask_blank.rb +9 -0
- data/examples/enum_select_disabled.rb +1 -1
- data/examples/enum_select_paged.rb +1 -1
- data/examples/expand_auto.rb +29 -0
- data/examples/mask.rb +1 -1
- data/examples/multi_select.rb +1 -1
- data/examples/multi_select_disabled_paged.rb +22 -0
- data/examples/multi_select_paged.rb +1 -1
- data/examples/select_disabled_paged.rb +22 -0
- data/examples/select_paginated.rb +1 -1
- data/lib/tty/prompt.rb +46 -10
- data/lib/tty/prompt/{enum_paginator.rb → block_paginator.rb} +19 -18
- data/lib/tty/prompt/choice.rb +1 -3
- data/lib/tty/prompt/enum_list.rb +31 -9
- data/lib/tty/prompt/expander.rb +19 -1
- data/lib/tty/prompt/keypress.rb +30 -35
- data/lib/tty/prompt/list.rb +112 -40
- data/lib/tty/prompt/mask_question.rb +2 -3
- data/lib/tty/prompt/multi_list.rb +36 -12
- data/lib/tty/prompt/paginator.rb +37 -25
- data/lib/tty/prompt/question.rb +29 -5
- data/lib/tty/prompt/slider.rb +16 -8
- data/lib/tty/prompt/symbols.rb +30 -6
- data/lib/tty/prompt/timer.rb +75 -0
- data/lib/tty/prompt/version.rb +1 -1
- data/spec/spec_helper.rb +18 -2
- data/spec/unit/ask_spec.rb +45 -4
- data/spec/unit/{enum_paginator_spec.rb → block_paginator_spec.rb} +14 -5
- data/spec/unit/choice/from_spec.rb +16 -0
- data/spec/unit/enum_select_spec.rb +104 -32
- data/spec/unit/expand_spec.rb +104 -12
- data/spec/unit/keypress_spec.rb +2 -8
- data/spec/unit/mask_spec.rb +9 -1
- data/spec/unit/multi_select_spec.rb +348 -118
- data/spec/unit/paginator_spec.rb +29 -10
- data/spec/unit/select_spec.rb +390 -108
- data/spec/unit/slider_spec.rb +48 -6
- data/spec/unit/timer_spec.rb +29 -0
- data/tty-prompt.gemspec +4 -6
- metadata +17 -46
- data/lib/tty/prompt/timeout.rb +0 -78
data/Rakefile
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../lib/tty-prompt"
|
4
|
+
|
5
|
+
choices = [{
|
6
|
+
key: 'y',
|
7
|
+
name: 'overwrite this file',
|
8
|
+
value: :yes
|
9
|
+
}, {
|
10
|
+
key: 'n',
|
11
|
+
name: 'do not overwrite this file',
|
12
|
+
value: :no
|
13
|
+
}, {
|
14
|
+
key: 'a',
|
15
|
+
name: 'overwrite this file and all later files',
|
16
|
+
value: :all
|
17
|
+
}, {
|
18
|
+
key: 'd',
|
19
|
+
name: 'show diff',
|
20
|
+
value: :diff
|
21
|
+
}, {
|
22
|
+
key: 'q',
|
23
|
+
name: 'quit; do not overwrite this file ',
|
24
|
+
value: :quit
|
25
|
+
}]
|
26
|
+
|
27
|
+
prompt = TTY::Prompt.new
|
28
|
+
|
29
|
+
prompt.expand('Overwrite Gemfile?', choices, auto_hint: true)
|
data/examples/mask.rb
CHANGED
@@ -4,7 +4,7 @@ require_relative "../lib/tty-prompt"
|
|
4
4
|
require 'pastel'
|
5
5
|
|
6
6
|
prompt = TTY::Prompt.new
|
7
|
-
heart = prompt.decorate('
|
7
|
+
heart = prompt.decorate(prompt.symbols[:heart] + ' ', :magenta)
|
8
8
|
|
9
9
|
res = prompt.mask('What is your secret?', mask: heart) do |q|
|
10
10
|
q.validate(/[a-z\ ]{5,15}/)
|
data/examples/multi_select.rb
CHANGED
@@ -5,4 +5,4 @@ require_relative "../lib/tty-prompt"
|
|
5
5
|
prompt = TTY::Prompt.new
|
6
6
|
|
7
7
|
drinks = %w(vodka beer wine whisky bourbon)
|
8
|
-
prompt.multi_select('Choose your favourite drink?', drinks
|
8
|
+
prompt.multi_select('Choose your favourite drink?', drinks)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../lib/tty-prompt"
|
4
|
+
|
5
|
+
prompt = TTY::Prompt.new
|
6
|
+
|
7
|
+
numbers = [
|
8
|
+
{name: '1', disabled: 'out'},
|
9
|
+
'2',
|
10
|
+
{name: '3', disabled: 'out'},
|
11
|
+
'4',
|
12
|
+
'5',
|
13
|
+
{name: '6', disabled: 'out'},
|
14
|
+
'7',
|
15
|
+
'8',
|
16
|
+
'9',
|
17
|
+
{name: '10', disabled: 'out'}
|
18
|
+
]
|
19
|
+
|
20
|
+
answer = prompt.multi_select('Which letter?', numbers, per_page: 4, cycle: true)
|
21
|
+
|
22
|
+
puts answer.inspect
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../lib/tty-prompt"
|
4
|
+
|
5
|
+
prompt = TTY::Prompt.new
|
6
|
+
|
7
|
+
numbers = [
|
8
|
+
{name: '1', disabled: 'out'},
|
9
|
+
'2',
|
10
|
+
{name: '3', disabled: 'out'},
|
11
|
+
'4',
|
12
|
+
'5',
|
13
|
+
{name: '6', disabled: 'out'},
|
14
|
+
'7',
|
15
|
+
'8',
|
16
|
+
'9',
|
17
|
+
{name: '10', disabled: 'out'}
|
18
|
+
]
|
19
|
+
|
20
|
+
answer = prompt.select('Which letter?', numbers, per_page: 4, cycle: true)
|
21
|
+
|
22
|
+
puts answer.inspect
|
data/lib/tty/prompt.rb
CHANGED
@@ -4,6 +4,7 @@ require 'forwardable'
|
|
4
4
|
require 'pastel'
|
5
5
|
require 'tty-cursor'
|
6
6
|
require 'tty-reader'
|
7
|
+
require 'tty-screen'
|
7
8
|
|
8
9
|
require_relative 'prompt/answers_collector'
|
9
10
|
require_relative 'prompt/confirm_question'
|
@@ -18,6 +19,7 @@ require_relative 'prompt/question'
|
|
18
19
|
require_relative 'prompt/slider'
|
19
20
|
require_relative 'prompt/statement'
|
20
21
|
require_relative 'prompt/suggestion'
|
22
|
+
require_relative 'prompt/symbols'
|
21
23
|
require_relative 'prompt/utils'
|
22
24
|
require_relative 'prompt/version'
|
23
25
|
|
@@ -69,6 +71,16 @@ module TTY
|
|
69
71
|
# @api private
|
70
72
|
attr_reader :active_color, :help_color, :error_color, :enabled_color
|
71
73
|
|
74
|
+
# The collection of display symbols
|
75
|
+
#
|
76
|
+
# @example
|
77
|
+
# prompt = TTY::Prompt.new(symbols: {marker: '>'})
|
78
|
+
#
|
79
|
+
# @return [Hash]
|
80
|
+
#
|
81
|
+
# @api private
|
82
|
+
attr_reader :symbols
|
83
|
+
|
72
84
|
def_delegators :@pastel, :decorate, :strip
|
73
85
|
|
74
86
|
def_delegators :@cursor, :clear_lines, :clear_line,
|
@@ -111,6 +123,8 @@ module TTY
|
|
111
123
|
# handling of Ctrl+C key out of :signal, :exit, :noop
|
112
124
|
# @option options [Boolean] :track_history
|
113
125
|
# disable line history tracking, true by default
|
126
|
+
# @option options [Hash] :symbols
|
127
|
+
# the symbols displayed in prompts such as :marker, :cross
|
114
128
|
#
|
115
129
|
# @api public
|
116
130
|
def initialize(*args)
|
@@ -125,6 +139,7 @@ module TTY
|
|
125
139
|
@error_color = options.fetch(:error_color) { :red }
|
126
140
|
@interrupt = options.fetch(:interrupt) { :error }
|
127
141
|
@track_history = options.fetch(:track_history) { true }
|
142
|
+
@symbols = Symbols.symbols.merge(options.fetch(:symbols, {}))
|
128
143
|
|
129
144
|
@cursor = TTY::Cursor
|
130
145
|
@pastel = Pastel.new(@enabled_color.nil? ? {} : { enabled: @enabled_color })
|
@@ -146,8 +161,7 @@ module TTY
|
|
146
161
|
# @return [String]
|
147
162
|
#
|
148
163
|
# @api public
|
149
|
-
def invoke_question(object, message,
|
150
|
-
options = Utils.extract_options!(args)
|
164
|
+
def invoke_question(object, message, **options, &block)
|
151
165
|
options[:messages] = self.class.messages
|
152
166
|
question = object.new(self, options)
|
153
167
|
question.(message, &block)
|
@@ -170,8 +184,8 @@ module TTY
|
|
170
184
|
# @return [TTY::Prompt::Question]
|
171
185
|
#
|
172
186
|
# @api public
|
173
|
-
def ask(message,
|
174
|
-
invoke_question(Question, message,
|
187
|
+
def ask(message = '', **options, &block)
|
188
|
+
invoke_question(Question, message, **options, &block)
|
175
189
|
end
|
176
190
|
|
177
191
|
# Ask a question with a keypress answer
|
@@ -179,8 +193,8 @@ module TTY
|
|
179
193
|
# @see #ask
|
180
194
|
#
|
181
195
|
# @api public
|
182
|
-
def keypress(message,
|
183
|
-
invoke_question(Keypress, message,
|
196
|
+
def keypress(message = '', **options, &block)
|
197
|
+
invoke_question(Keypress, message, **options, &block)
|
184
198
|
end
|
185
199
|
|
186
200
|
# Ask a question with a multiline answer
|
@@ -191,8 +205,8 @@ module TTY
|
|
191
205
|
# @return [Array[String]]
|
192
206
|
#
|
193
207
|
# @api public
|
194
|
-
def multiline(message,
|
195
|
-
invoke_question(Multiline, message,
|
208
|
+
def multiline(message = '', **options, &block)
|
209
|
+
invoke_question(Multiline, message, **options, &block)
|
196
210
|
end
|
197
211
|
|
198
212
|
# Invoke a list type of prompt
|
@@ -232,8 +246,8 @@ module TTY
|
|
232
246
|
# @return [TTY::Prompt::MaskQuestion]
|
233
247
|
#
|
234
248
|
# @api public
|
235
|
-
def mask(message,
|
236
|
-
invoke_question(MaskQuestion, message,
|
249
|
+
def mask(message = '', **options, &block)
|
250
|
+
invoke_question(MaskQuestion, message, **options, &block)
|
237
251
|
end
|
238
252
|
|
239
253
|
# Ask a question with a list of options
|
@@ -448,6 +462,28 @@ module TTY
|
|
448
462
|
args.each { |message| say message, options.merge(color: :red) }
|
449
463
|
end
|
450
464
|
|
465
|
+
# Print debug information in terminal top right corner
|
466
|
+
#
|
467
|
+
# @example
|
468
|
+
# prompt.debug "info1", "info2"
|
469
|
+
#
|
470
|
+
# @param [Array] messages
|
471
|
+
#
|
472
|
+
# @retrun [nil]
|
473
|
+
#
|
474
|
+
# @api public
|
475
|
+
def debug(*messages)
|
476
|
+
longest = messages.max_by(&:length).size
|
477
|
+
width = TTY::Screen.width - longest
|
478
|
+
print cursor.save
|
479
|
+
messages.each_with_index do |msg, i|
|
480
|
+
print cursor.move_to(width, i)
|
481
|
+
print cursor.clear_line_after
|
482
|
+
print msg
|
483
|
+
end
|
484
|
+
print cursor.restore
|
485
|
+
end
|
486
|
+
|
451
487
|
# Takes the string provided by the user and compare it with other possible
|
452
488
|
# matches to suggest an unambigous string
|
453
489
|
#
|
@@ -4,7 +4,7 @@ require_relative 'paginator'
|
|
4
4
|
|
5
5
|
module TTY
|
6
6
|
class Prompt
|
7
|
-
class
|
7
|
+
class BlockPaginator < Paginator
|
8
8
|
# Paginate list of choices based on current active choice.
|
9
9
|
# Move entire pages.
|
10
10
|
#
|
@@ -17,8 +17,8 @@ module TTY
|
|
17
17
|
|
18
18
|
# Don't paginate short lists
|
19
19
|
if list.size <= @per_page
|
20
|
-
@
|
21
|
-
@
|
20
|
+
@start_index = 0
|
21
|
+
@end_index = list.size - 1
|
22
22
|
if block
|
23
23
|
return list.each_with_index(&block)
|
24
24
|
else
|
@@ -32,25 +32,26 @@ module TTY
|
|
32
32
|
page = (@last_index / @per_page.to_f).ceil
|
33
33
|
pages = (list.size / @per_page.to_f).ceil
|
34
34
|
if page == 0
|
35
|
-
@
|
36
|
-
@
|
37
|
-
elsif page > 0 && page
|
38
|
-
@
|
39
|
-
@
|
35
|
+
@start_index = 0
|
36
|
+
@end_index = @start_index + @per_page - 1
|
37
|
+
elsif page > 0 && page < pages
|
38
|
+
@start_index = (page - 1) * @per_page
|
39
|
+
@end_index = @start_index + @per_page - 1
|
40
|
+
elsif page == pages
|
41
|
+
@start_index = (page - 1) * @per_page
|
42
|
+
@end_index = list.size - 1
|
40
43
|
else
|
41
|
-
@
|
42
|
-
@
|
44
|
+
@end_index = list.size - 1
|
45
|
+
@start_index = @end_index - @per_page + 1
|
43
46
|
end
|
44
47
|
|
45
|
-
sliced_list = list[@
|
46
|
-
|
48
|
+
sliced_list = list[@start_index..@end_index]
|
49
|
+
page_range = (@start_index..@end_index)
|
47
50
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
else
|
53
|
-
sliced_list.zip(indices).to_enum unless block_given?
|
51
|
+
return sliced_list.zip(page_range).to_enum unless block_given?
|
52
|
+
|
53
|
+
sliced_list.each_with_index do |item, index|
|
54
|
+
block[item, @start_index + index]
|
54
55
|
end
|
55
56
|
end
|
56
57
|
end # EnumPaginator
|
data/lib/tty/prompt/choice.rb
CHANGED
@@ -30,8 +30,6 @@ module TTY
|
|
30
30
|
case val
|
31
31
|
when Choice
|
32
32
|
val
|
33
|
-
when String, Symbol
|
34
|
-
new(val, val)
|
35
33
|
when Array
|
36
34
|
name, value, options = *val
|
37
35
|
if name.is_a?(Hash)
|
@@ -42,7 +40,7 @@ module TTY
|
|
42
40
|
when Hash
|
43
41
|
convert_hash(val)
|
44
42
|
else
|
45
|
-
|
43
|
+
new(val, val)
|
46
44
|
end
|
47
45
|
end
|
48
46
|
|
data/lib/tty/prompt/enum_list.rb
CHANGED
@@ -3,9 +3,8 @@
|
|
3
3
|
require 'English'
|
4
4
|
|
5
5
|
require_relative 'choices'
|
6
|
-
require_relative '
|
6
|
+
require_relative 'block_paginator'
|
7
7
|
require_relative 'paginator'
|
8
|
-
require_relative 'symbols'
|
9
8
|
|
10
9
|
module TTY
|
11
10
|
class Prompt
|
@@ -14,22 +13,21 @@ module TTY
|
|
14
13
|
#
|
15
14
|
# @api private
|
16
15
|
class EnumList
|
17
|
-
include Symbols
|
18
|
-
|
19
16
|
PAGE_HELP = '(Press tab/right or left to reveal more choices)'
|
20
17
|
|
21
18
|
# Create instance of EnumList menu.
|
22
19
|
#
|
23
20
|
# @api public
|
24
|
-
def initialize(prompt, options
|
21
|
+
def initialize(prompt, **options)
|
25
22
|
@prompt = prompt
|
26
23
|
@prefix = options.fetch(:prefix) { @prompt.prefix }
|
27
24
|
@enum = options.fetch(:enum) { ')' }
|
28
|
-
@default = options.fetch(:default) { 1 }
|
25
|
+
@default = options.fetch(:default) { -1 }
|
29
26
|
@active_color = options.fetch(:active_color) { @prompt.active_color }
|
30
27
|
@help_color = options.fetch(:help_color) { @prompt.help_color }
|
31
28
|
@error_color = options.fetch(:error_color) { @prompt.error_color }
|
32
29
|
@cycle = options.fetch(:cycle) { false }
|
30
|
+
@symbols = @prompt.symbols.merge(options.fetch(:symbols, {}))
|
33
31
|
@input = nil
|
34
32
|
@done = false
|
35
33
|
@first_render = true
|
@@ -38,10 +36,21 @@ module TTY
|
|
38
36
|
@choices = Choices.new
|
39
37
|
@per_page = options[:per_page]
|
40
38
|
@page_help = options[:page_help] || PAGE_HELP
|
41
|
-
@paginator =
|
39
|
+
@paginator = BlockPaginator.new
|
42
40
|
@page_active = @default
|
43
41
|
end
|
44
42
|
|
43
|
+
# Change symbols used by this prompt
|
44
|
+
#
|
45
|
+
# @param [Hash] new_symbols
|
46
|
+
# the new symbols to use
|
47
|
+
#
|
48
|
+
# @api public
|
49
|
+
def symbols(new_symbols = (not_set = true))
|
50
|
+
return @symbols if not_set
|
51
|
+
@symbols.merge!(new_symbols)
|
52
|
+
end
|
53
|
+
|
45
54
|
# Set default option selected
|
46
55
|
#
|
47
56
|
# @api public
|
@@ -49,6 +58,15 @@ module TTY
|
|
49
58
|
@default = default
|
50
59
|
end
|
51
60
|
|
61
|
+
# Check if default value is set
|
62
|
+
#
|
63
|
+
# @return [Boolean]
|
64
|
+
#
|
65
|
+
# @api public
|
66
|
+
def default?
|
67
|
+
@default > 0
|
68
|
+
end
|
69
|
+
|
52
70
|
# Set number of items per page
|
53
71
|
#
|
54
72
|
# @api public
|
@@ -125,7 +143,7 @@ module TTY
|
|
125
143
|
end
|
126
144
|
|
127
145
|
def keypress(event)
|
128
|
-
if [
|
146
|
+
if %i[backspace delete].include?(event.key.name)
|
129
147
|
return if @input.empty?
|
130
148
|
@input.chop!
|
131
149
|
mark_choice_as_active
|
@@ -169,6 +187,7 @@ module TTY
|
|
169
187
|
|
170
188
|
private
|
171
189
|
|
190
|
+
|
172
191
|
# Find active choice or set to default
|
173
192
|
#
|
174
193
|
# @return [nil]
|
@@ -206,6 +225,9 @@ module TTY
|
|
206
225
|
#
|
207
226
|
# @api private
|
208
227
|
def setup_defaults
|
228
|
+
if !default?
|
229
|
+
@default = (0..choices.length).find {|i| !choices[i].disabled? } + 1
|
230
|
+
end
|
209
231
|
validate_defaults
|
210
232
|
mark_choice_as_active
|
211
233
|
end
|
@@ -363,7 +385,7 @@ module TTY
|
|
363
385
|
output << if index + 1 == @active && !choice.disabled?
|
364
386
|
(' ' * 2) + @prompt.decorate(selected, @active_color)
|
365
387
|
elsif choice.disabled?
|
366
|
-
@prompt.decorate(symbols[:cross], :red) + ' ' +
|
388
|
+
@prompt.decorate(@symbols[:cross], :red) + ' ' +
|
367
389
|
selected + ' ' + choice.disabled.to_s
|
368
390
|
else
|
369
391
|
(' ' * 2) + selected
|