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 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