tty-prompt 0.18.1 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/README.md +174 -63
  4. data/Rakefile +2 -2
  5. data/examples/ask_blank.rb +9 -0
  6. data/examples/enum_select_disabled.rb +1 -1
  7. data/examples/enum_select_paged.rb +1 -1
  8. data/examples/expand_auto.rb +29 -0
  9. data/examples/mask.rb +1 -1
  10. data/examples/multi_select.rb +1 -1
  11. data/examples/multi_select_disabled_paged.rb +22 -0
  12. data/examples/multi_select_paged.rb +1 -1
  13. data/examples/select_disabled_paged.rb +22 -0
  14. data/examples/select_paginated.rb +1 -1
  15. data/lib/tty/prompt.rb +46 -10
  16. data/lib/tty/prompt/{enum_paginator.rb → block_paginator.rb} +19 -18
  17. data/lib/tty/prompt/choice.rb +1 -3
  18. data/lib/tty/prompt/enum_list.rb +31 -9
  19. data/lib/tty/prompt/expander.rb +19 -1
  20. data/lib/tty/prompt/keypress.rb +30 -35
  21. data/lib/tty/prompt/list.rb +112 -40
  22. data/lib/tty/prompt/mask_question.rb +2 -3
  23. data/lib/tty/prompt/multi_list.rb +36 -12
  24. data/lib/tty/prompt/paginator.rb +37 -25
  25. data/lib/tty/prompt/question.rb +29 -5
  26. data/lib/tty/prompt/slider.rb +16 -8
  27. data/lib/tty/prompt/symbols.rb +30 -6
  28. data/lib/tty/prompt/timer.rb +75 -0
  29. data/lib/tty/prompt/version.rb +1 -1
  30. data/spec/spec_helper.rb +18 -2
  31. data/spec/unit/ask_spec.rb +45 -4
  32. data/spec/unit/{enum_paginator_spec.rb → block_paginator_spec.rb} +14 -5
  33. data/spec/unit/choice/from_spec.rb +16 -0
  34. data/spec/unit/enum_select_spec.rb +104 -32
  35. data/spec/unit/expand_spec.rb +104 -12
  36. data/spec/unit/keypress_spec.rb +2 -8
  37. data/spec/unit/mask_spec.rb +9 -1
  38. data/spec/unit/multi_select_spec.rb +348 -118
  39. data/spec/unit/paginator_spec.rb +29 -10
  40. data/spec/unit/select_spec.rb +390 -108
  41. data/spec/unit/slider_spec.rb +48 -6
  42. data/spec/unit/timer_spec.rb +29 -0
  43. data/tty-prompt.gemspec +4 -6
  44. metadata +17 -46
  45. data/lib/tty/prompt/timeout.rb +0 -78
data/Rakefile CHANGED
@@ -1,8 +1,8 @@
1
- # encoding: utf-8
2
-
3
1
  require "bundler/gem_tasks"
4
2
 
5
3
  FileList['tasks/**/*.rake'].each(&method(:import))
6
4
 
7
5
  desc 'Run all specs'
8
6
  task ci: %w[ spec ]
7
+
8
+ task default: :spec
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../lib/tty-prompt"
4
+
5
+ prompt = TTY::Prompt.new(prefix: ">")
6
+
7
+ answer= prompt.ask
8
+
9
+ puts "Answer: \"#{answer}\""
@@ -5,8 +5,8 @@ require_relative "../lib/tty-prompt"
5
5
  prompt = TTY::Prompt.new
6
6
 
7
7
  choices = [
8
- "Atom",
9
8
  {name: "Emacs", disabled: '(not installed)'},
9
+ "Atom",
10
10
  "GNU nano",
11
11
  {name: "Notepad++", disabled: '(not installed)'},
12
12
  "Sublime",
@@ -6,4 +6,4 @@ prompt = TTY::Prompt.new
6
6
 
7
7
  alfabet = ('A'..'Z').to_a
8
8
 
9
- prompt.enum_select('Which letter?', alfabet, per_page: 4)
9
+ prompt.enum_select('Which letter?', alfabet, per_page: 4, cycle: true, default: 2)
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../lib/tty-prompt"
4
+
5
+ choices = [{
6
+ key: 'y',
7
+ name: 'overwrite this file',
8
+ value: :yes
9
+ }, {
10
+ key: 'n',
11
+ name: 'do not overwrite this file',
12
+ value: :no
13
+ }, {
14
+ key: 'a',
15
+ name: 'overwrite this file and all later files',
16
+ value: :all
17
+ }, {
18
+ key: 'd',
19
+ name: 'show diff',
20
+ value: :diff
21
+ }, {
22
+ key: 'q',
23
+ name: 'quit; do not overwrite this file ',
24
+ value: :quit
25
+ }]
26
+
27
+ prompt = TTY::Prompt.new
28
+
29
+ prompt.expand('Overwrite Gemfile?', choices, auto_hint: true)
@@ -4,7 +4,7 @@ require_relative "../lib/tty-prompt"
4
4
  require 'pastel'
5
5
 
6
6
  prompt = TTY::Prompt.new
7
- heart = prompt.decorate(' ', :magenta)
7
+ heart = prompt.decorate(prompt.symbols[:heart] + ' ', :magenta)
8
8
 
9
9
  res = prompt.mask('What is your secret?', mask: heart) do |q|
10
10
  q.validate(/[a-z\ ]{5,15}/)
@@ -5,4 +5,4 @@ require_relative "../lib/tty-prompt"
5
5
  prompt = TTY::Prompt.new
6
6
 
7
7
  drinks = %w(vodka beer wine whisky bourbon)
8
- prompt.multi_select('Choose your favourite drink?', drinks, help: '(Use arrow keys and Enter to finish)')
8
+ prompt.multi_select('Choose your favourite drink?', drinks)
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../lib/tty-prompt"
4
+
5
+ prompt = TTY::Prompt.new
6
+
7
+ numbers = [
8
+ {name: '1', disabled: 'out'},
9
+ '2',
10
+ {name: '3', disabled: 'out'},
11
+ '4',
12
+ '5',
13
+ {name: '6', disabled: 'out'},
14
+ '7',
15
+ '8',
16
+ '9',
17
+ {name: '10', disabled: 'out'}
18
+ ]
19
+
20
+ answer = prompt.multi_select('Which letter?', numbers, per_page: 4, cycle: true)
21
+
22
+ puts answer.inspect
@@ -6,4 +6,4 @@ prompt = TTY::Prompt.new
6
6
 
7
7
  alfabet = ('A'..'Z').to_a
8
8
 
9
- prompt.multi_select('Which letter?', alfabet, per_page: 5)
9
+ prompt.multi_select('Which letter?', alfabet, per_page: 7, max: 3)
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../lib/tty-prompt"
4
+
5
+ prompt = TTY::Prompt.new
6
+
7
+ numbers = [
8
+ {name: '1', disabled: 'out'},
9
+ '2',
10
+ {name: '3', disabled: 'out'},
11
+ '4',
12
+ '5',
13
+ {name: '6', disabled: 'out'},
14
+ '7',
15
+ '8',
16
+ '9',
17
+ {name: '10', disabled: 'out'}
18
+ ]
19
+
20
+ answer = prompt.select('Which letter?', numbers, per_page: 4, cycle: true)
21
+
22
+ puts answer.inspect
@@ -6,6 +6,6 @@ prompt = TTY::Prompt.new
6
6
 
7
7
  alfabet = ('A'..'Z').to_a
8
8
 
9
- answer = prompt.select('Which letter?', alfabet, per_page: 8)
9
+ answer = prompt.select('Which letter?', alfabet, per_page: 7, cycle: true, default: 5)
10
10
 
11
11
  puts answer.inspect
@@ -4,6 +4,7 @@ require 'forwardable'
4
4
  require 'pastel'
5
5
  require 'tty-cursor'
6
6
  require 'tty-reader'
7
+ require 'tty-screen'
7
8
 
8
9
  require_relative 'prompt/answers_collector'
9
10
  require_relative 'prompt/confirm_question'
@@ -18,6 +19,7 @@ require_relative 'prompt/question'
18
19
  require_relative 'prompt/slider'
19
20
  require_relative 'prompt/statement'
20
21
  require_relative 'prompt/suggestion'
22
+ require_relative 'prompt/symbols'
21
23
  require_relative 'prompt/utils'
22
24
  require_relative 'prompt/version'
23
25
 
@@ -69,6 +71,16 @@ module TTY
69
71
  # @api private
70
72
  attr_reader :active_color, :help_color, :error_color, :enabled_color
71
73
 
74
+ # The collection of display symbols
75
+ #
76
+ # @example
77
+ # prompt = TTY::Prompt.new(symbols: {marker: '>'})
78
+ #
79
+ # @return [Hash]
80
+ #
81
+ # @api private
82
+ attr_reader :symbols
83
+
72
84
  def_delegators :@pastel, :decorate, :strip
73
85
 
74
86
  def_delegators :@cursor, :clear_lines, :clear_line,
@@ -111,6 +123,8 @@ module TTY
111
123
  # handling of Ctrl+C key out of :signal, :exit, :noop
112
124
  # @option options [Boolean] :track_history
113
125
  # disable line history tracking, true by default
126
+ # @option options [Hash] :symbols
127
+ # the symbols displayed in prompts such as :marker, :cross
114
128
  #
115
129
  # @api public
116
130
  def initialize(*args)
@@ -125,6 +139,7 @@ module TTY
125
139
  @error_color = options.fetch(:error_color) { :red }
126
140
  @interrupt = options.fetch(:interrupt) { :error }
127
141
  @track_history = options.fetch(:track_history) { true }
142
+ @symbols = Symbols.symbols.merge(options.fetch(:symbols, {}))
128
143
 
129
144
  @cursor = TTY::Cursor
130
145
  @pastel = Pastel.new(@enabled_color.nil? ? {} : { enabled: @enabled_color })
@@ -146,8 +161,7 @@ module TTY
146
161
  # @return [String]
147
162
  #
148
163
  # @api public
149
- def invoke_question(object, message, *args, &block)
150
- options = Utils.extract_options!(args)
164
+ def invoke_question(object, message, **options, &block)
151
165
  options[:messages] = self.class.messages
152
166
  question = object.new(self, options)
153
167
  question.(message, &block)
@@ -170,8 +184,8 @@ module TTY
170
184
  # @return [TTY::Prompt::Question]
171
185
  #
172
186
  # @api public
173
- def ask(message, *args, &block)
174
- invoke_question(Question, message, *args, &block)
187
+ def ask(message = '', **options, &block)
188
+ invoke_question(Question, message, **options, &block)
175
189
  end
176
190
 
177
191
  # Ask a question with a keypress answer
@@ -179,8 +193,8 @@ module TTY
179
193
  # @see #ask
180
194
  #
181
195
  # @api public
182
- def keypress(message, *args, &block)
183
- invoke_question(Keypress, message, *args, &block)
196
+ def keypress(message = '', **options, &block)
197
+ invoke_question(Keypress, message, **options, &block)
184
198
  end
185
199
 
186
200
  # Ask a question with a multiline answer
@@ -191,8 +205,8 @@ module TTY
191
205
  # @return [Array[String]]
192
206
  #
193
207
  # @api public
194
- def multiline(message, *args, &block)
195
- invoke_question(Multiline, message, *args, &block)
208
+ def multiline(message = '', **options, &block)
209
+ invoke_question(Multiline, message, **options, &block)
196
210
  end
197
211
 
198
212
  # Invoke a list type of prompt
@@ -232,8 +246,8 @@ module TTY
232
246
  # @return [TTY::Prompt::MaskQuestion]
233
247
  #
234
248
  # @api public
235
- def mask(message, *args, &block)
236
- invoke_question(MaskQuestion, message, *args, &block)
249
+ def mask(message = '', **options, &block)
250
+ invoke_question(MaskQuestion, message, **options, &block)
237
251
  end
238
252
 
239
253
  # Ask a question with a list of options
@@ -448,6 +462,28 @@ module TTY
448
462
  args.each { |message| say message, options.merge(color: :red) }
449
463
  end
450
464
 
465
+ # Print debug information in terminal top right corner
466
+ #
467
+ # @example
468
+ # prompt.debug "info1", "info2"
469
+ #
470
+ # @param [Array] messages
471
+ #
472
+ # @retrun [nil]
473
+ #
474
+ # @api public
475
+ def debug(*messages)
476
+ longest = messages.max_by(&:length).size
477
+ width = TTY::Screen.width - longest
478
+ print cursor.save
479
+ messages.each_with_index do |msg, i|
480
+ print cursor.move_to(width, i)
481
+ print cursor.clear_line_after
482
+ print msg
483
+ end
484
+ print cursor.restore
485
+ end
486
+
451
487
  # Takes the string provided by the user and compare it with other possible
452
488
  # matches to suggest an unambigous string
453
489
  #
@@ -4,7 +4,7 @@ require_relative 'paginator'
4
4
 
5
5
  module TTY
6
6
  class Prompt
7
- class EnumPaginator < Paginator
7
+ class BlockPaginator < Paginator
8
8
  # Paginate list of choices based on current active choice.
9
9
  # Move entire pages.
10
10
  #
@@ -17,8 +17,8 @@ module TTY
17
17
 
18
18
  # Don't paginate short lists
19
19
  if list.size <= @per_page
20
- @lower_index = 0
21
- @upper_index = list.size - 1
20
+ @start_index = 0
21
+ @end_index = list.size - 1
22
22
  if block
23
23
  return list.each_with_index(&block)
24
24
  else
@@ -32,25 +32,26 @@ module TTY
32
32
  page = (@last_index / @per_page.to_f).ceil
33
33
  pages = (list.size / @per_page.to_f).ceil
34
34
  if page == 0
35
- @lower_index = 0
36
- @upper_index = @lower_index + @per_page - 1
37
- elsif page > 0 && page <= pages
38
- @lower_index = (page - 1) * @per_page
39
- @upper_index = @lower_index + @per_page - 1
35
+ @start_index = 0
36
+ @end_index = @start_index + @per_page - 1
37
+ elsif page > 0 && page < pages
38
+ @start_index = (page - 1) * @per_page
39
+ @end_index = @start_index + @per_page - 1
40
+ elsif page == pages
41
+ @start_index = (page - 1) * @per_page
42
+ @end_index = list.size - 1
40
43
  else
41
- @upper_index = list.size - 1
42
- @lower_index = @upper_index - @per_page + 1
44
+ @end_index = list.size - 1
45
+ @start_index = @end_index - @per_page + 1
43
46
  end
44
47
 
45
- sliced_list = list[@lower_index..@upper_index]
46
- indices = (@lower_index..@upper_index)
48
+ sliced_list = list[@start_index..@end_index]
49
+ page_range = (@start_index..@end_index)
47
50
 
48
- if block
49
- sliced_list.each_with_index do |item, index|
50
- block[item, @lower_index + index]
51
- end
52
- else
53
- sliced_list.zip(indices).to_enum unless block_given?
51
+ return sliced_list.zip(page_range).to_enum unless block_given?
52
+
53
+ sliced_list.each_with_index do |item, index|
54
+ block[item, @start_index + index]
54
55
  end
55
56
  end
56
57
  end # EnumPaginator
@@ -30,8 +30,6 @@ module TTY
30
30
  case val
31
31
  when Choice
32
32
  val
33
- when String, Symbol
34
- new(val, val)
35
33
  when Array
36
34
  name, value, options = *val
37
35
  if name.is_a?(Hash)
@@ -42,7 +40,7 @@ module TTY
42
40
  when Hash
43
41
  convert_hash(val)
44
42
  else
45
- raise ArgumentError, "#{val} cannot be coerced into Choice"
43
+ new(val, val)
46
44
  end
47
45
  end
48
46
 
@@ -3,9 +3,8 @@
3
3
  require 'English'
4
4
 
5
5
  require_relative 'choices'
6
- require_relative 'enum_paginator'
6
+ require_relative 'block_paginator'
7
7
  require_relative 'paginator'
8
- require_relative 'symbols'
9
8
 
10
9
  module TTY
11
10
  class Prompt
@@ -14,22 +13,21 @@ module TTY
14
13
  #
15
14
  # @api private
16
15
  class EnumList
17
- include Symbols
18
-
19
16
  PAGE_HELP = '(Press tab/right or left to reveal more choices)'
20
17
 
21
18
  # Create instance of EnumList menu.
22
19
  #
23
20
  # @api public
24
- def initialize(prompt, options = {})
21
+ def initialize(prompt, **options)
25
22
  @prompt = prompt
26
23
  @prefix = options.fetch(:prefix) { @prompt.prefix }
27
24
  @enum = options.fetch(:enum) { ')' }
28
- @default = options.fetch(:default) { 1 }
25
+ @default = options.fetch(:default) { -1 }
29
26
  @active_color = options.fetch(:active_color) { @prompt.active_color }
30
27
  @help_color = options.fetch(:help_color) { @prompt.help_color }
31
28
  @error_color = options.fetch(:error_color) { @prompt.error_color }
32
29
  @cycle = options.fetch(:cycle) { false }
30
+ @symbols = @prompt.symbols.merge(options.fetch(:symbols, {}))
33
31
  @input = nil
34
32
  @done = false
35
33
  @first_render = true
@@ -38,10 +36,21 @@ module TTY
38
36
  @choices = Choices.new
39
37
  @per_page = options[:per_page]
40
38
  @page_help = options[:page_help] || PAGE_HELP
41
- @paginator = EnumPaginator.new
39
+ @paginator = BlockPaginator.new
42
40
  @page_active = @default
43
41
  end
44
42
 
43
+ # Change symbols used by this prompt
44
+ #
45
+ # @param [Hash] new_symbols
46
+ # the new symbols to use
47
+ #
48
+ # @api public
49
+ def symbols(new_symbols = (not_set = true))
50
+ return @symbols if not_set
51
+ @symbols.merge!(new_symbols)
52
+ end
53
+
45
54
  # Set default option selected
46
55
  #
47
56
  # @api public
@@ -49,6 +58,15 @@ module TTY
49
58
  @default = default
50
59
  end
51
60
 
61
+ # Check if default value is set
62
+ #
63
+ # @return [Boolean]
64
+ #
65
+ # @api public
66
+ def default?
67
+ @default > 0
68
+ end
69
+
52
70
  # Set number of items per page
53
71
  #
54
72
  # @api public
@@ -125,7 +143,7 @@ module TTY
125
143
  end
126
144
 
127
145
  def keypress(event)
128
- if [:backspace, :delete].include?(event.key.name)
146
+ if %i[backspace delete].include?(event.key.name)
129
147
  return if @input.empty?
130
148
  @input.chop!
131
149
  mark_choice_as_active
@@ -169,6 +187,7 @@ module TTY
169
187
 
170
188
  private
171
189
 
190
+
172
191
  # Find active choice or set to default
173
192
  #
174
193
  # @return [nil]
@@ -206,6 +225,9 @@ module TTY
206
225
  #
207
226
  # @api private
208
227
  def setup_defaults
228
+ if !default?
229
+ @default = (0..choices.length).find {|i| !choices[i].disabled? } + 1
230
+ end
209
231
  validate_defaults
210
232
  mark_choice_as_active
211
233
  end
@@ -363,7 +385,7 @@ module TTY
363
385
  output << if index + 1 == @active && !choice.disabled?
364
386
  (' ' * 2) + @prompt.decorate(selected, @active_color)
365
387
  elsif choice.disabled?
366
- @prompt.decorate(symbols[:cross], :red) + ' ' +
388
+ @prompt.decorate(@symbols[:cross], :red) + ' ' +
367
389
  selected + ' ' + choice.disabled.to_s
368
390
  else
369
391
  (' ' * 2) + selected