tty-prompt 0.18.1 → 0.19.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 +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
|