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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 914b91c8d5090e18ffa6da38c4e4ba7f26a7f50a
4
- data.tar.gz: 0dea47a0af899dc93a532fc4371710807e8ef472
3
+ metadata.gz: 90f71ba30290379f0e69eb5ff8affb0276d15507
4
+ data.tar.gz: c16aecac4d1203614dacf82287c2ec7f6f4e8ffd
5
5
  SHA512:
6
- metadata.gz: 9476326073c28e1c92c8b1a48fe66ce4769589ad44a15758ac376512a20f77065a5f78dc51a4b2c897f7825cf9d325d0c285700961c4a7f942c7a2bd8351bde8
7
- data.tar.gz: 7206776eb52eb9451641be93db1de554bbbf94cdab014d8f5f21a4f46aa52acbd4cb50745806b6eb078f88eb863b97645483833d2df441cbb995245a64901912
6
+ metadata.gz: bfc62f62785695cf6efa2987acce5bbba00dcfcfe05c33a7641517178017f5a12ec1b600a20e650cf133babea6f6cfe2de8e926f5f9c55d7213b8fb0b0e87d05
7
+ data.tar.gz: c1e1333370fb6bed3d61fc684788be691698598399358ea5d7354954d9e05604a62871affa17c202e94d6d51d2afe2923f698661d2fb1496b1b06ab966c9f916
data/.gitignore CHANGED
@@ -1,4 +1,6 @@
1
1
  /.bundle/
2
+ /.ruby-version
3
+ /.ruby-gemset
2
4
  /.yardoc
3
5
  /Gemfile.lock
4
6
  /_yardoc/
@@ -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:
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative "../lib/tty-prompt"
4
+
5
+ prompt = TTY::Prompt.new
6
+
7
+ warriors = %w(Scorpion Kano Jax Kitana Raiden)
8
+
9
+ prompt.select('Choose your destiny?', warriors, filter: true)
@@ -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] = answer
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
@@ -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
- @choices.size > page_size
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
- self.class::HELP % [enumerate? ? " or number (1-#{@choices.size})" : '']
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
- Array(values).each { |val| choice(*val) }
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..@choices.count).cover?(value)
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 = @choices.length if @cycle
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 == @choices.length
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 - #{@choices.size})"
271
+ "default index must be an integer in range (1 - #{choices.size})"
214
272
  end
215
- if d < 1 || d > @choices.size
273
+ if d < 1 || d > choices.size
216
274
  raise ConfigurationError,
217
- "default index `#{d}` out of range (1 - #{@choices.size})"
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
- @prompt.print(refresh(question.lines.count))
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
- @choices[@active - 1].value
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 = "#{@choices[@active - 1].name}"
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
- @paginator.paginate(@choices, @active, @per_page) do |choice, index|
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 : @choices.size - 1
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 = @choices[@active - 1]
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
- selected_names + (@first_render ? " #{instructions}" : '')
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(@choices, @active, @per_page) do |choice, index|
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 : @choices.size - 1
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module TTY
4
4
  class Prompt
5
- VERSION = '0.14.0'.freeze
5
+ VERSION = '0.15.0'.freeze
6
6
  end # Prompt
7
7
  end # TTY
@@ -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.1.2'
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.14.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-01-01 00:00:00.000000000 Z
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.1.2
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.1.2
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