tty-prompt 0.18.1 → 0.19.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 (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