tty-prompt 0.14.0 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/CHANGELOG.md +11 -0
- data/README.md +58 -0
- data/examples/select_filtered.rb +9 -0
- data/lib/tty/prompt/answers_collector.rb +21 -2
- data/lib/tty/prompt/list.rb +95 -19
- data/lib/tty/prompt/multi_list.rb +9 -5
- data/lib/tty/prompt/version.rb +1 -1
- data/tty-prompt.gemspec +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90f71ba30290379f0e69eb5ff8affb0276d15507
|
4
|
+
data.tar.gz: c16aecac4d1203614dacf82287c2ec7f6f4e8ffd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bfc62f62785695cf6efa2987acce5bbba00dcfcfe05c33a7641517178017f5a12ec1b600a20e650cf133babea6f6cfe2de8e926f5f9c55d7213b8fb0b0e87d05
|
7
|
+
data.tar.gz: c1e1333370fb6bed3d61fc684788be691698598399358ea5d7354954d9e05604a62871affa17c202e94d6d51d2afe2923f698661d2fb1496b1b06ab966c9f916
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
# Change log
|
2
2
|
|
3
|
+
## [v0.15.0] - 2018-02-08
|
4
|
+
|
5
|
+
### Added
|
6
|
+
* Add ability to filter list items in #select, #multi_select & #enum_selct prompts by Saverio Miroddi(@saveriomiroddi)
|
7
|
+
* Add support for array of values for an answer collector key by Danny Hadley(@dadleyy)
|
8
|
+
|
9
|
+
### Changed
|
10
|
+
* Relax dependency on timers by Andy Brody(@brodygov)
|
11
|
+
|
3
12
|
## [v0.14.0] - 2018-01-01
|
4
13
|
|
5
14
|
### Added
|
@@ -236,6 +245,8 @@
|
|
236
245
|
|
237
246
|
* Initial implementation and release
|
238
247
|
|
248
|
+
[v0.15.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.14.0...v0.15.0
|
249
|
+
[v0.14.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.13.2...v0.14.0
|
239
250
|
[v0.13.2]: https://github.com/piotrmurach/tty-prompt/compare/v0.13.1...v0.13.2
|
240
251
|
[v0.13.1]: https://github.com/piotrmurach/tty-prompt/compare/v0.13.0...v0.13.1
|
241
252
|
[v0.13.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.12.0...v0.13.0
|
data/README.md
CHANGED
@@ -76,6 +76,7 @@ Or install it yourself as:
|
|
76
76
|
* [2.6.1 select](#261-select)
|
77
77
|
* [2.6.2 multi_select](#262-multi_select)
|
78
78
|
* [2.6.2.1 echo](#2621-echo)
|
79
|
+
* [2.6.2.2 filter](#2622-filter)
|
79
80
|
* [2.6.3 enum_select](#263-enum_select)
|
80
81
|
* [2.7 expand](#27-expand)
|
81
82
|
* [2.8 collect](#28-collect)
|
@@ -778,6 +779,36 @@ prompt.multi_select("Select drinks?", choices, echo: false)
|
|
778
779
|
# ‣ ⬢ 5) bourbon
|
779
780
|
```
|
780
781
|
|
782
|
+
### 2.6.2.2 filter
|
783
|
+
|
784
|
+
To activate dynamic list filtering on letter/number typing, use the :filter option:
|
785
|
+
|
786
|
+
```ruby
|
787
|
+
choices = %w(vodka beer wine whisky bourbon)
|
788
|
+
prompt.multi_select("Select drinks?", choices, filter: true)
|
789
|
+
# =>
|
790
|
+
# Select drinks? (Use arrow keys, press Space to select and Enter to finish, and letter keys to filter)
|
791
|
+
# ‣ ⬡ vodka
|
792
|
+
# ⬡ beer
|
793
|
+
# ⬡ wine
|
794
|
+
# ⬡ whisky
|
795
|
+
# ⬡ bourbon
|
796
|
+
```
|
797
|
+
|
798
|
+
After the user presses "w":
|
799
|
+
|
800
|
+
```
|
801
|
+
# Select drinks? (Filter: "w")
|
802
|
+
# ‣ ⬡ wine
|
803
|
+
# ⬡ whisky
|
804
|
+
```
|
805
|
+
|
806
|
+
Filter characters can be deleted partially or entirely via, respectively, Backspace and Canc.
|
807
|
+
|
808
|
+
If the user changes or deletes a filter, the choices previously selected remain selected.
|
809
|
+
|
810
|
+
The `filter` option is not compatible with `enum`.
|
811
|
+
|
781
812
|
### 2.6.3 enum_select
|
782
813
|
|
783
814
|
In order to ask for standard selection from indexed list you can use `enum_select` and pass question together with possible choices:
|
@@ -938,6 +969,33 @@ end
|
|
938
969
|
# {:name => "Piotr", :age => 30, :address => {:street => "Street", :city => "City", :zip => "123"}}
|
939
970
|
```
|
940
971
|
|
972
|
+
In order to collect _mutliple values_ for a given key in a loop, chain `values` onto the `key` desired:
|
973
|
+
|
974
|
+
```rb
|
975
|
+
result = prompt.collect do
|
976
|
+
key(:name).ask('Name?')
|
977
|
+
|
978
|
+
key(:age).ask('Age?', convert: :int)
|
979
|
+
|
980
|
+
while prompt.yes?("continue?")
|
981
|
+
key(:addresses).values do
|
982
|
+
key(:street).ask('Street?', required: true)
|
983
|
+
key(:city).ask('City?')
|
984
|
+
key(:zip).ask('Zip?', validate: /\A\d{3}\Z/)
|
985
|
+
end
|
986
|
+
end
|
987
|
+
end
|
988
|
+
# =>
|
989
|
+
# {
|
990
|
+
# :name => "Piotr",
|
991
|
+
# :age => 30,
|
992
|
+
# :addresses => [
|
993
|
+
# {:street => "Street", :city => "City", :zip => "123"},
|
994
|
+
# {:street => "Street", :city => "City", :zip => "234"}
|
995
|
+
# ]
|
996
|
+
# }
|
997
|
+
```
|
998
|
+
|
941
999
|
### 2.9 suggest
|
942
1000
|
|
943
1001
|
To suggest possible matches for the user input use `suggest` method like so:
|
@@ -31,7 +31,22 @@ module TTY
|
|
31
31
|
def key(name, &block)
|
32
32
|
@name = name
|
33
33
|
if block
|
34
|
-
answer = create_collector.(&block)
|
34
|
+
answer = create_collector.call(&block)
|
35
|
+
add_answer(answer)
|
36
|
+
end
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
# Change to collect all values for a key
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# key(:colors).values.ask('Color?')
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
def values(&block)
|
47
|
+
@answers[@name] = Array(@answers[@name])
|
48
|
+
if block
|
49
|
+
answer = create_collector.call(&block)
|
35
50
|
add_answer(answer)
|
36
51
|
end
|
37
52
|
self
|
@@ -44,7 +59,11 @@ module TTY
|
|
44
59
|
|
45
60
|
# @api public
|
46
61
|
def add_answer(answer)
|
47
|
-
@answers[@name]
|
62
|
+
if @answers[@name].is_a?(Array)
|
63
|
+
@answers[@name] << answer
|
64
|
+
else
|
65
|
+
@answers[@name] = answer
|
66
|
+
end
|
48
67
|
end
|
49
68
|
|
50
69
|
private
|
data/lib/tty/prompt/list.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
+
require "English"
|
4
|
+
|
3
5
|
require_relative 'choices'
|
4
6
|
require_relative 'paginator'
|
5
7
|
require_relative 'symbols'
|
@@ -13,10 +15,13 @@ module TTY
|
|
13
15
|
class List
|
14
16
|
include Symbols
|
15
17
|
|
16
|
-
HELP = '(Use arrow%s keys, press Enter to select)'
|
18
|
+
HELP = '(Use arrow%s keys, press Enter to select%s)'
|
17
19
|
|
18
20
|
PAGE_HELP = '(Move up or down to reveal more choices)'
|
19
21
|
|
22
|
+
# Allowed keys for filter, along with backspace and canc.
|
23
|
+
FILTER_KEYS_MATCHER = /\A\w\Z/
|
24
|
+
|
20
25
|
# Create instance of TTY::Prompt::List menu.
|
21
26
|
#
|
22
27
|
# @param Hash options
|
@@ -32,6 +37,8 @@ module TTY
|
|
32
37
|
#
|
33
38
|
# @api public
|
34
39
|
def initialize(prompt, options = {})
|
40
|
+
check_options_consistency(options)
|
41
|
+
|
35
42
|
@prompt = prompt
|
36
43
|
@prefix = options.fetch(:prefix) { @prompt.prefix }
|
37
44
|
@enum = options.fetch(:enum) { nil }
|
@@ -42,6 +49,7 @@ module TTY
|
|
42
49
|
@help_color = options.fetch(:help_color) { @prompt.help_color }
|
43
50
|
@marker = options.fetch(:marker) { symbols[:pointer] }
|
44
51
|
@cycle = options.fetch(:cycle) { false }
|
52
|
+
@filter = options.fetch(:filter) { false } ? "" : nil
|
45
53
|
@help = options[:help]
|
46
54
|
@first_render = true
|
47
55
|
@done = false
|
@@ -83,7 +91,7 @@ module TTY
|
|
83
91
|
#
|
84
92
|
# @api private
|
85
93
|
def paginated?
|
86
|
-
|
94
|
+
choices.size > page_size
|
87
95
|
end
|
88
96
|
|
89
97
|
# @param [String] text
|
@@ -111,7 +119,16 @@ module TTY
|
|
111
119
|
#
|
112
120
|
# @api public
|
113
121
|
def default_help
|
114
|
-
|
122
|
+
# Note that enumeration and filter are mutually exclusive
|
123
|
+
tokens = if enumerate?
|
124
|
+
[" or number (1-#{choices.size})", ""]
|
125
|
+
elsif @filter
|
126
|
+
["", ", and letter keys to filter"]
|
127
|
+
else
|
128
|
+
["", ""]
|
129
|
+
end
|
130
|
+
|
131
|
+
format(self.class::HELP, *tokens)
|
115
132
|
end
|
116
133
|
|
117
134
|
# Set selecting active index using number pad
|
@@ -132,14 +149,25 @@ module TTY
|
|
132
149
|
end
|
133
150
|
end
|
134
151
|
|
135
|
-
# Add multiple choices
|
152
|
+
# Add multiple choices, or return them.
|
136
153
|
#
|
137
154
|
# @param [Array[Object]] values
|
138
|
-
# the values to add as choices
|
155
|
+
# the values to add as choices; if not passed, the current
|
156
|
+
# choices are displayed.
|
139
157
|
#
|
140
158
|
# @api public
|
141
|
-
def choices(values)
|
142
|
-
|
159
|
+
def choices(values = (not_set = true))
|
160
|
+
if not_set
|
161
|
+
if @filter.to_s.empty?
|
162
|
+
@choices
|
163
|
+
else
|
164
|
+
@choices.select do |_choice|
|
165
|
+
_choice.name.downcase.include?(@filter.downcase)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
else
|
169
|
+
Array(values).each { |val| choice(*val) }
|
170
|
+
end
|
143
171
|
end
|
144
172
|
|
145
173
|
# Call the list menu by passing question and choices
|
@@ -166,26 +194,26 @@ module TTY
|
|
166
194
|
def keynum(event)
|
167
195
|
return unless enumerate?
|
168
196
|
value = event.value.to_i
|
169
|
-
return unless (1
|
197
|
+
return unless (1..choices.count).cover?(value)
|
170
198
|
@active = value
|
171
199
|
end
|
172
200
|
|
173
201
|
def keyenter(*)
|
174
|
-
@done = true
|
202
|
+
@done = true unless choices.empty?
|
175
203
|
end
|
176
204
|
alias keyreturn keyenter
|
177
205
|
alias keyspace keyenter
|
178
206
|
|
179
207
|
def keyup(*)
|
180
208
|
if @active == 1
|
181
|
-
@active =
|
209
|
+
@active = choices.length if @cycle
|
182
210
|
else
|
183
211
|
@active -= 1
|
184
212
|
end
|
185
213
|
end
|
186
214
|
|
187
215
|
def keydown(*)
|
188
|
-
if @active ==
|
216
|
+
if @active == choices.length
|
189
217
|
@active = 1 if @cycle
|
190
218
|
else
|
191
219
|
@active += 1
|
@@ -193,8 +221,38 @@ module TTY
|
|
193
221
|
end
|
194
222
|
alias keytab keydown
|
195
223
|
|
224
|
+
def keypress(event)
|
225
|
+
return unless @filter
|
226
|
+
|
227
|
+
if event.value =~ FILTER_KEYS_MATCHER
|
228
|
+
@filter += event.value
|
229
|
+
@active = 1
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def keydelete(*)
|
234
|
+
return unless @filter
|
235
|
+
|
236
|
+
@filter = ""
|
237
|
+
@active = 1
|
238
|
+
end
|
239
|
+
|
240
|
+
def keybackspace(*)
|
241
|
+
return unless @filter
|
242
|
+
|
243
|
+
@filter.slice!(-1)
|
244
|
+
@active = 1
|
245
|
+
end
|
246
|
+
|
196
247
|
private
|
197
248
|
|
249
|
+
def check_options_consistency(options)
|
250
|
+
if options.key?(:enum) && options.key?(:filter)
|
251
|
+
raise ConfigurationError,
|
252
|
+
"Enumeration can't be used with filter"
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
198
256
|
# Setup default option and active selection
|
199
257
|
#
|
200
258
|
# @api private
|
@@ -210,11 +268,11 @@ module TTY
|
|
210
268
|
@default.each do |d|
|
211
269
|
if d.nil? || d.to_s.empty?
|
212
270
|
raise ConfigurationError,
|
213
|
-
"default index must be an integer in range (1 - #{
|
271
|
+
"default index must be an integer in range (1 - #{choices.size})"
|
214
272
|
end
|
215
|
-
if d < 1 || d >
|
273
|
+
if d < 1 || d > choices.size
|
216
274
|
raise ConfigurationError,
|
217
|
-
"default index `#{d}` out of range (1 - #{
|
275
|
+
"default index `#{d}` out of range (1 - #{choices.size})"
|
218
276
|
end
|
219
277
|
end
|
220
278
|
end
|
@@ -233,7 +291,12 @@ module TTY
|
|
233
291
|
question = render_question
|
234
292
|
@prompt.print(question)
|
235
293
|
@prompt.read_keypress
|
236
|
-
|
294
|
+
|
295
|
+
# Split manually; if the second line is blank (when there are no
|
296
|
+
# matching lines), it won't be included by using String#lines.
|
297
|
+
question_lines = question.split($INPUT_RECORD_SEPARATOR, -1)
|
298
|
+
|
299
|
+
@prompt.print(refresh(question_lines.count))
|
237
300
|
end
|
238
301
|
@prompt.print(render_question)
|
239
302
|
answer
|
@@ -247,7 +310,7 @@ module TTY
|
|
247
310
|
#
|
248
311
|
# @api private
|
249
312
|
def answer
|
250
|
-
|
313
|
+
choices[@active - 1].value
|
251
314
|
end
|
252
315
|
|
253
316
|
# Clear screen lines
|
@@ -273,6 +336,15 @@ module TTY
|
|
273
336
|
header
|
274
337
|
end
|
275
338
|
|
339
|
+
# Header part showing the current filter
|
340
|
+
#
|
341
|
+
# @return String
|
342
|
+
#
|
343
|
+
# @api private
|
344
|
+
def filter_help
|
345
|
+
"(Filter: #{@filter.inspect})"
|
346
|
+
end
|
347
|
+
|
276
348
|
# Render initial help and selected choice
|
277
349
|
#
|
278
350
|
# @return [String]
|
@@ -280,10 +352,12 @@ module TTY
|
|
280
352
|
# @api private
|
281
353
|
def render_header
|
282
354
|
if @done
|
283
|
-
selected_item = "#{
|
355
|
+
selected_item = "#{choices[@active - 1].name}"
|
284
356
|
@prompt.decorate(selected_item, @active_color)
|
285
357
|
elsif @first_render
|
286
358
|
@prompt.decorate(help, @help_color)
|
359
|
+
elsif @filter.to_s != ""
|
360
|
+
@prompt.decorate(filter_help, @help_color)
|
287
361
|
end
|
288
362
|
end
|
289
363
|
|
@@ -294,7 +368,8 @@ module TTY
|
|
294
368
|
# @api private
|
295
369
|
def render_menu
|
296
370
|
output = ''
|
297
|
-
|
371
|
+
|
372
|
+
@paginator.paginate(choices, @active, @per_page) do |choice, index|
|
298
373
|
num = enumerate? ? (index + 1).to_s + @enum + ' ' : ''
|
299
374
|
message = if index + 1 == @active
|
300
375
|
selected = @marker + ' ' + num + choice.name
|
@@ -302,10 +377,11 @@ module TTY
|
|
302
377
|
else
|
303
378
|
' ' * 2 + num + choice.name
|
304
379
|
end
|
305
|
-
max_index = paginated? ? @paginator.max_index :
|
380
|
+
max_index = paginated? ? @paginator.max_index : choices.size - 1
|
306
381
|
newline = (index == max_index) ? '' : "\n"
|
307
382
|
output << (message + newline)
|
308
383
|
end
|
384
|
+
|
309
385
|
output
|
310
386
|
end
|
311
387
|
|
@@ -9,7 +9,7 @@ module TTY
|
|
9
9
|
#
|
10
10
|
# @api private
|
11
11
|
class MultiList < List
|
12
|
-
HELP = '(Use arrow%s keys, press Space to select and Enter to finish)'.freeze
|
12
|
+
HELP = '(Use arrow%s keys, press Space to select and Enter to finish%s)'.freeze
|
13
13
|
|
14
14
|
# Create instance of TTY::Prompt::MultiList menu.
|
15
15
|
#
|
@@ -29,7 +29,7 @@ module TTY
|
|
29
29
|
#
|
30
30
|
# @api private
|
31
31
|
def keyspace(*)
|
32
|
-
active_choice =
|
32
|
+
active_choice = choices[@active - 1]
|
33
33
|
if @selected.include?(active_choice)
|
34
34
|
@selected.delete(active_choice)
|
35
35
|
else
|
@@ -44,6 +44,7 @@ module TTY
|
|
44
44
|
# @api private
|
45
45
|
def setup_defaults
|
46
46
|
validate_defaults
|
47
|
+
# At this stage, @choices matches all the visible choices.
|
47
48
|
@selected = @choices.values_at(*@default.map { |d| d - 1 })
|
48
49
|
@active = @default.last unless @selected.empty?
|
49
50
|
end
|
@@ -65,9 +66,12 @@ module TTY
|
|
65
66
|
if @done && @echo
|
66
67
|
@prompt.decorate(selected_names, @active_color)
|
67
68
|
elsif @selected.size.nonzero? && @echo
|
68
|
-
|
69
|
+
help_suffix = @filter.to_s != "" ? " #{filter_help}" : ""
|
70
|
+
selected_names + (@first_render ? " #{instructions}" : help_suffix)
|
69
71
|
elsif @first_render
|
70
72
|
instructions
|
73
|
+
elsif @filter.to_s != ""
|
74
|
+
filter_help
|
71
75
|
end
|
72
76
|
end
|
73
77
|
|
@@ -87,7 +91,7 @@ module TTY
|
|
87
91
|
# @api private
|
88
92
|
def render_menu
|
89
93
|
output = ''
|
90
|
-
@paginator.paginate(
|
94
|
+
@paginator.paginate(choices, @active, @per_page) do |choice, index|
|
91
95
|
num = enumerate? ? (index + 1).to_s + @enum + ' ' : ''
|
92
96
|
indicator = (index + 1 == @active) ? @marker : ' '
|
93
97
|
indicator += ' '
|
@@ -97,7 +101,7 @@ module TTY
|
|
97
101
|
else
|
98
102
|
symbols[:radio_off] + ' ' + num + choice.name
|
99
103
|
end
|
100
|
-
max_index = paginated? ? @paginator.max_index :
|
104
|
+
max_index = paginated? ? @paginator.max_index : choices.size - 1
|
101
105
|
newline = (index == max_index) ? '' : "\n"
|
102
106
|
output << indicator + message + newline
|
103
107
|
end
|
data/lib/tty/prompt/version.rb
CHANGED
data/tty-prompt.gemspec
CHANGED
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
|
|
24
24
|
|
25
25
|
spec.add_dependency 'necromancer', '~> 0.4.0'
|
26
26
|
spec.add_dependency 'pastel', '~> 0.7.0'
|
27
|
-
spec.add_dependency 'timers', '~> 4.
|
27
|
+
spec.add_dependency 'timers', '~> 4.0'
|
28
28
|
spec.add_dependency 'tty-cursor', '~> 0.5.0'
|
29
29
|
spec.add_dependency 'tty-reader', '~> 0.2.0'
|
30
30
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tty-prompt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.15.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Murach
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-02-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: necromancer
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 4.
|
47
|
+
version: '4.0'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 4.
|
54
|
+
version: '4.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: tty-cursor
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -165,6 +165,7 @@ files:
|
|
165
165
|
- examples/multiline.rb
|
166
166
|
- examples/pause.rb
|
167
167
|
- examples/select.rb
|
168
|
+
- examples/select_filtered.rb
|
168
169
|
- examples/select_paginated.rb
|
169
170
|
- examples/slider.rb
|
170
171
|
- examples/validation.rb
|