tty-prompt 0.14.0 → 0.15.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/.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
|