tty-prompt 0.11.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/README.md +66 -7
  4. data/examples/key_events.rb +11 -0
  5. data/examples/keypress.rb +3 -5
  6. data/examples/multiline.rb +9 -0
  7. data/examples/pause.rb +7 -0
  8. data/lib/tty/prompt.rb +82 -44
  9. data/lib/tty/prompt/confirm_question.rb +20 -36
  10. data/lib/tty/prompt/enum_list.rb +32 -23
  11. data/lib/tty/prompt/expander.rb +35 -31
  12. data/lib/tty/prompt/keypress.rb +91 -0
  13. data/lib/tty/prompt/list.rb +38 -23
  14. data/lib/tty/prompt/mask_question.rb +4 -7
  15. data/lib/tty/prompt/multi_list.rb +3 -1
  16. data/lib/tty/prompt/multiline.rb +71 -0
  17. data/lib/tty/prompt/question.rb +33 -35
  18. data/lib/tty/prompt/reader.rb +154 -38
  19. data/lib/tty/prompt/reader/codes.rb +4 -4
  20. data/lib/tty/prompt/reader/console.rb +1 -1
  21. data/lib/tty/prompt/reader/history.rb +145 -0
  22. data/lib/tty/prompt/reader/key_event.rb +4 -0
  23. data/lib/tty/prompt/reader/line.rb +162 -0
  24. data/lib/tty/prompt/reader/mode.rb +2 -2
  25. data/lib/tty/prompt/reader/win_console.rb +5 -1
  26. data/lib/tty/prompt/slider.rb +18 -12
  27. data/lib/tty/prompt/timeout.rb +48 -0
  28. data/lib/tty/prompt/version.rb +1 -1
  29. data/spec/unit/ask_spec.rb +15 -0
  30. data/spec/unit/converters/convert_bool_spec.rb +1 -0
  31. data/spec/unit/keypress_spec.rb +35 -6
  32. data/spec/unit/multi_select_spec.rb +18 -0
  33. data/spec/unit/multiline_spec.rb +67 -9
  34. data/spec/unit/question/default_spec.rb +1 -0
  35. data/spec/unit/question/echo_spec.rb +8 -0
  36. data/spec/unit/question/in_spec.rb +13 -0
  37. data/spec/unit/question/required_spec.rb +31 -2
  38. data/spec/unit/question/validate_spec.rb +39 -9
  39. data/spec/unit/reader/history_spec.rb +172 -0
  40. data/spec/unit/reader/key_event_spec.rb +12 -8
  41. data/spec/unit/reader/line_spec.rb +110 -0
  42. data/spec/unit/reader/publish_keypress_event_spec.rb +11 -0
  43. data/spec/unit/reader/read_line_spec.rb +32 -2
  44. data/spec/unit/reader/read_multiline_spec.rb +21 -7
  45. data/spec/unit/select_spec.rb +40 -1
  46. data/spec/unit/yes_no_spec.rb +48 -4
  47. metadata +14 -3
  48. data/lib/tty/prompt/history.rb +0 -16
@@ -197,12 +197,17 @@ module TTY
197
197
  def render
198
198
  @input = ''
199
199
  until @done
200
- lines = render_question
200
+ question = render_question
201
+ @prompt.print(question)
202
+ @prompt.print(render_error) if @failure
203
+ if paginated? && !@done
204
+ @prompt.print(render_page_help)
205
+ end
201
206
  @prompt.read_keypress
202
- refresh(lines)
207
+ @prompt.print(refresh(question.lines.count))
203
208
  end
204
- render_question
205
- render_answer
209
+ @prompt.print(render_question)
210
+ answer
206
211
  end
207
212
 
208
213
  # Find value for the choice selected
@@ -210,7 +215,7 @@ module TTY
210
215
  # @return [nil, Object]
211
216
  #
212
217
  # @api private
213
- def render_answer
218
+ def answer
214
219
  @choices[@active - 1].value
215
220
  end
216
221
 
@@ -219,27 +224,26 @@ module TTY
219
224
  # @param [Integer] lines
220
225
  # the lines to clear
221
226
  #
227
+ # @return [String]
228
+ #
222
229
  # @api private
223
230
  def refresh(lines)
224
- @prompt.print(@prompt.clear_lines(lines))
225
- @prompt.print(@prompt.cursor.clear_screen_down)
231
+ @prompt.clear_lines(lines) +
232
+ @prompt.cursor.clear_screen_down
226
233
  end
227
234
 
228
235
  # Render question with the menu options
229
236
  #
237
+ # @return [String]
238
+ #
230
239
  # @api private
231
240
  def render_question
232
- header = "#{@prefix}#{@question} #{render_header}"
233
- @prompt.puts(header)
234
- lines = header.lines.count
241
+ header = "#{@prefix}#{@question} #{render_header}\n"
235
242
  unless @done
236
- menu = render_menu + render_footer
237
- lines += menu.lines.count
238
- @prompt.print(menu)
243
+ header << render_menu
244
+ header << render_footer
239
245
  end
240
- render_error if @failure
241
- render_page_help if paginated? && !@done
242
- lines
246
+ header
243
247
  end
244
248
 
245
249
  # Error message when incorrect index chosen
@@ -252,13 +256,16 @@ module TTY
252
256
 
253
257
  # Render error message and return cursor to position of input
254
258
  #
259
+ # @return [String]
260
+ #
255
261
  # @api private
256
262
  def render_error
257
- @prompt.print(error_message)
263
+ error = error_message.dup
258
264
  if !paginated?
259
- @prompt.print(@prompt.cursor.prev_line)
260
- @prompt.print(@prompt.cursor.forward(render_footer.size))
265
+ error << @prompt.cursor.prev_line
266
+ error << @prompt.cursor.forward(render_footer.size)
261
267
  end
268
+ error
262
269
  end
263
270
 
264
271
  # Render chosen option
@@ -294,14 +301,16 @@ module TTY
294
301
 
295
302
  # Render page help
296
303
  #
304
+ # @return [String]
305
+ #
297
306
  # @api private
298
307
  def render_page_help
299
- @prompt.print(page_help_message)
308
+ help = page_help_message.dup
300
309
  if @failure
301
- @prompt.print(@prompt.cursor.prev_line)
310
+ help << @prompt.cursor.prev_line
302
311
  end
303
- @prompt.print(@prompt.cursor.prev_line)
304
- @prompt.print(@prompt.cursor.forward(render_footer.size))
312
+ help << @prompt.cursor.prev_line
313
+ help << @prompt.cursor.forward(render_footer.size)
305
314
  end
306
315
 
307
316
  # Render menu with indexed choices to select from
@@ -158,19 +158,25 @@ module TTY
158
158
  def render
159
159
  @input = ''
160
160
  until @done
161
- render_question
161
+ question = render_question
162
+ @prompt.print(question)
162
163
  read_input
163
- refresh
164
+ @prompt.print(refresh(question.lines.count))
164
165
  end
165
- render_question
166
- render_answer
166
+ @prompt.print(render_question)
167
+ answer
167
168
  end
168
169
 
169
170
  # @api private
170
- def render_answer
171
+ def answer
171
172
  @selected.value
172
173
  end
173
174
 
175
+ # Render message with options
176
+ #
177
+ # @return [String]
178
+ #
179
+ # @api private
174
180
  def render_header
175
181
  header = "#{@prefix}#{@message} "
176
182
  if @done
@@ -184,27 +190,34 @@ module TTY
184
190
  header
185
191
  end
186
192
 
193
+ # Show hint for selected option key
194
+ #
195
+ # return [String]
196
+ #
187
197
  # @api private
188
198
  def render_hint
189
199
  hint = "\n"
190
200
  hint << @prompt.decorate('>> ', @active_color)
191
201
  hint << @hint
192
- @prompt.print(hint)
193
- @prompt.print(@prompt.cursor.prev_line)
194
- @prompt.print(@prompt.cursor.forward(@prompt.strip(render_header).size))
202
+ hint << @prompt.cursor.prev_line
203
+ hint << @prompt.cursor.forward(@prompt.strip(render_header).size)
195
204
  end
196
205
 
206
+ # Render question with menu
207
+ #
208
+ # @return [String]
209
+ #
197
210
  # @api private
198
211
  def render_question
199
212
  header = render_header
200
- @prompt.print(header)
201
- render_hint if @hint
202
- @prompt.print("\n") if @done
213
+ header << render_hint if @hint
214
+ header << "\n" if @done
203
215
 
204
216
  if !@done && expanded?
205
- @prompt.print(render_menu)
206
- @prompt.print(render_footer)
217
+ header << render_menu
218
+ header << render_footer
207
219
  end
220
+ header
208
221
  end
209
222
 
210
223
  def render_footer
@@ -215,31 +228,22 @@ module TTY
215
228
  @prompt.read_keypress
216
229
  end
217
230
 
218
- # @api private
219
- def count_lines
220
- lines = render_header.scan("\n").length + 1
221
- if @hint
222
- lines += @hint.scan("\n").length + 1
223
- elsif expanded?
224
- lines += @choices.length
225
- lines += render_footer.scan("\n").length + 1
226
- end
227
- lines
228
- end
229
-
230
231
  # Refresh the current input
231
232
  #
233
+ # @param [Integer] lines
234
+ #
235
+ # @return [String]
236
+ #
232
237
  # @api private
233
- def refresh
234
- lines = count_lines
238
+ def refresh(lines)
235
239
  if @hint && (!@selected || @done)
236
240
  @hint = nil
237
- @prompt.print(@prompt.clear_lines(lines, :down))
238
- @prompt.print(@prompt.cursor.prev_line)
241
+ @prompt.clear_lines(lines, :down) +
242
+ @prompt.cursor.prev_line
239
243
  elsif expanded?
240
- @prompt.print(@prompt.clear_lines(lines))
244
+ @prompt.clear_lines(lines)
241
245
  else
242
- @prompt.print(@prompt.clear_line)
246
+ @prompt.clear_line
243
247
  end
244
248
  end
245
249
 
@@ -0,0 +1,91 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative 'question'
4
+ require_relative 'symbols'
5
+ require_relative 'timeout'
6
+
7
+ module TTY
8
+ class Prompt
9
+ class Keypress < Question
10
+ # Create keypress question
11
+ #
12
+ # @param [Prompt] prompt
13
+ # @param [Hash] options
14
+ #
15
+ # @api public
16
+ def initialize(prompt, options = {})
17
+ super
18
+ @echo = options.fetch(:echo) { false }
19
+ @keys = options.fetch(:keys) { UndefinedSetting }
20
+ @timeout = options.fetch(:timeout) { UndefinedSetting }
21
+ @interval = options.fetch(:interval) { 1 }
22
+ @pause = true
23
+ @countdown = @timeout
24
+ @interval_handler = proc { |time|
25
+ question = render_question
26
+ @prompt.print(refresh(question.lines.count))
27
+ countdown(time)
28
+ @prompt.print(render_question)
29
+ }
30
+
31
+ @prompt.subscribe(self)
32
+ end
33
+
34
+ def countdown(value = (not_set = true))
35
+ return @countdown if not_set
36
+ @countdown = value
37
+ end
38
+
39
+ # Check if any specific keys are set
40
+ def any_key?
41
+ @keys == UndefinedSetting
42
+ end
43
+
44
+ # Check if timeout is set
45
+ def timeout?
46
+ @timeout != UndefinedSetting
47
+ end
48
+
49
+ def keypress(event)
50
+ if any_key?
51
+ @pause = false
52
+ elsif @keys.is_a?(Array) && @keys.include?(event.key.name)
53
+ @pause = false
54
+ else
55
+ @pause = true
56
+ end
57
+ end
58
+
59
+ def render_question
60
+ header = super
61
+ header.gsub!(/:countdown/, countdown.to_s)
62
+ header
63
+ end
64
+
65
+ def process_input(question)
66
+ time do
67
+ while @pause
68
+ @input = @prompt.read_keypress
69
+ end
70
+ end
71
+ @evaluator.(@input)
72
+ end
73
+
74
+ def refresh(lines)
75
+ @prompt.clear_lines(lines)
76
+ end
77
+
78
+ def time(&block)
79
+ if timeout?
80
+ secs = Integer(@timeout)
81
+ interval = Integer(@interval)
82
+ scheduler = Timeout.new(interval_handler: @interval_handler)
83
+ scheduler.timeout(secs, interval, &block)
84
+ else
85
+ block.()
86
+ end
87
+ rescue Timeout::Error
88
+ end
89
+ end # Keypress
90
+ end # Prompt
91
+ end # TTY
@@ -92,6 +92,27 @@ module TTY
92
92
  @page_help = text
93
93
  end
94
94
 
95
+ # Provide help information
96
+ #
97
+ # @param [String] value
98
+ # the new help text
99
+ #
100
+ # @return [String]
101
+ #
102
+ # @api public
103
+ def help(value = (not_set = true))
104
+ return @help if !@help.nil? && not_set
105
+
106
+ @help = (@help.nil? && !not_set) ? value : default_help
107
+ end
108
+
109
+ # Default help text
110
+ #
111
+ # @api public
112
+ def default_help
113
+ self.class::HELP % [enumerate? ? " or number (1-#{@choices.size})" : '']
114
+ end
115
+
95
116
  # Set selecting active index using number pad
96
117
  #
97
118
  # @api public
@@ -117,7 +138,7 @@ module TTY
117
138
  #
118
139
  # @api public
119
140
  def choices(values)
120
- values.each { |val| choice(*val) }
141
+ Array(values).each { |val| choice(*val) }
121
142
  end
122
143
 
123
144
  # Call the list menu by passing question and choices
@@ -202,12 +223,13 @@ module TTY
202
223
  def render
203
224
  @prompt.print(@prompt.hide)
204
225
  until @done
205
- lines = render_question
226
+ question = render_question
227
+ @prompt.print(question)
206
228
  @prompt.read_keypress
207
- refresh(lines)
229
+ @prompt.print(refresh(question.lines.count))
208
230
  end
209
- render_question
210
- render_answer
231
+ @prompt.print(render_question)
232
+ answer
211
233
  ensure
212
234
  @prompt.print(@prompt.show)
213
235
  end
@@ -217,42 +239,31 @@ module TTY
217
239
  # @return [nil, Object]
218
240
  #
219
241
  # @api private
220
- def render_answer
242
+ def answer
221
243
  @choices[@active - 1].value
222
244
  end
223
245
 
224
246
  # Clear screen lines
225
247
  #
226
- # @param [Integer] lines
227
- # the lines to clear
248
+ # @param [String]
228
249
  #
229
250
  # @api private
230
251
  def refresh(lines)
231
- @prompt.print(@prompt.clear_lines(lines))
252
+ @prompt.clear_lines(lines)
232
253
  end
233
254
 
234
255
  # Render question with instructions and menu
235
256
  #
236
- # @return [Integer] The number of lines for menu
257
+ # @return [String]
237
258
  #
238
259
  # @api private
239
260
  def render_question
240
- header = "#{@prefix}#{@question} #{render_header}"
241
- @prompt.puts(header)
261
+ header = "#{@prefix}#{@question} #{render_header}\n"
242
262
  @first_render = false
243
263
  rendered_menu = render_menu
244
264
  rendered_menu << render_footer
245
- @prompt.print(rendered_menu) unless @done
246
-
247
- header.lines.count + rendered_menu.lines.count
248
- end
249
-
250
- # Provide help information
251
- #
252
- # @return [String]
253
- def help
254
- return @help unless @help.nil?
255
- self.class::HELP % [enumerate? ? " or number (1-#{@choices.size})" : '']
265
+ header << rendered_menu unless @done
266
+ header
256
267
  end
257
268
 
258
269
  # Render initial help and selected choice
@@ -271,6 +282,8 @@ module TTY
271
282
 
272
283
  # Render menu with choices to select from
273
284
  #
285
+ # @return [String]
286
+ #
274
287
  # @api private
275
288
  def render_menu
276
289
  output = ''
@@ -291,6 +304,8 @@ module TTY
291
304
 
292
305
  # Render page info footer
293
306
  #
307
+ # @return [String]
308
+ #
294
309
  # @api private
295
310
  def render_footer
296
311
  return '' unless paginated?
@@ -62,10 +62,8 @@ module TTY
62
62
  end
63
63
  header += masked
64
64
  end
65
- @prompt.print(header)
66
- @prompt.puts if @done
67
-
68
- header.lines.count + (@done ? 1 : 0)
65
+ header << "\n" if @done
66
+ header
69
67
  end
70
68
 
71
69
  def render_error(errors)
@@ -76,15 +74,14 @@ module TTY
76
74
  # Read input from user masked by character
77
75
  #
78
76
  # @private
79
- def read_input
77
+ def read_input(question)
80
78
  @done_masked = false
81
79
  @failure = false
82
80
  @input = ''
83
-
84
81
  until @done_masked
85
82
  @prompt.read_keypress
86
83
  @prompt.print(@prompt.clear_line)
87
- render_question
84
+ @prompt.print(render_question)
88
85
  end
89
86
  @prompt.puts
90
87
  @input