tty-prompt 0.11.0 → 0.12.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.
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