tty-prompt 0.18.1 → 0.23.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 (131) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +95 -0
  3. data/README.md +598 -256
  4. data/lib/tty-prompt.rb +1 -2
  5. data/lib/tty/prompt.rb +192 -144
  6. data/lib/tty/prompt/answers_collector.rb +5 -5
  7. data/lib/tty/prompt/{enum_paginator.rb → block_paginator.rb} +20 -19
  8. data/lib/tty/prompt/choice.rb +9 -10
  9. data/lib/tty/prompt/choices.rb +30 -12
  10. data/lib/tty/prompt/confirm_question.rb +42 -16
  11. data/lib/tty/prompt/const.rb +17 -0
  12. data/lib/tty/prompt/converter_dsl.rb +6 -7
  13. data/lib/tty/prompt/converter_registry.rb +31 -26
  14. data/lib/tty/prompt/converters.rb +139 -32
  15. data/lib/tty/prompt/enum_list.rb +81 -26
  16. data/lib/tty/prompt/errors.rb +31 -0
  17. data/lib/tty/prompt/evaluator.rb +2 -2
  18. data/lib/tty/prompt/expander.rb +45 -15
  19. data/lib/tty/prompt/keypress.rb +33 -36
  20. data/lib/tty/prompt/list.rb +198 -63
  21. data/lib/tty/prompt/mask_question.rb +11 -8
  22. data/lib/tty/prompt/multi_list.rb +131 -28
  23. data/lib/tty/prompt/multiline.rb +9 -7
  24. data/lib/tty/prompt/paginator.rb +38 -26
  25. data/lib/tty/prompt/question.rb +92 -37
  26. data/lib/tty/prompt/question/checks.rb +20 -2
  27. data/lib/tty/prompt/question/modifier.rb +4 -2
  28. data/lib/tty/prompt/question/validation.rb +3 -3
  29. data/lib/tty/prompt/selected_choices.rb +77 -0
  30. data/lib/tty/prompt/slider.rb +125 -30
  31. data/lib/tty/prompt/statement.rb +3 -3
  32. data/lib/tty/prompt/suggestion.rb +7 -6
  33. data/lib/tty/prompt/symbols.rb +58 -34
  34. data/lib/tty/prompt/test.rb +36 -0
  35. data/lib/tty/prompt/timer.rb +75 -0
  36. data/lib/tty/prompt/utils.rb +1 -3
  37. data/lib/tty/prompt/version.rb +1 -1
  38. metadata +29 -227
  39. data/Rakefile +0 -8
  40. data/examples/ask.rb +0 -7
  41. data/examples/ask_valid.rb +0 -12
  42. data/examples/collect.rb +0 -21
  43. data/examples/echo.rb +0 -11
  44. data/examples/enum_select.rb +0 -7
  45. data/examples/enum_select_disabled.rb +0 -16
  46. data/examples/enum_select_paged.rb +0 -9
  47. data/examples/enum_select_wrapped.rb +0 -15
  48. data/examples/expand.rb +0 -29
  49. data/examples/in.rb +0 -9
  50. data/examples/inputs.rb +0 -10
  51. data/examples/key_events.rb +0 -15
  52. data/examples/keypress.rb +0 -9
  53. data/examples/mask.rb +0 -13
  54. data/examples/multi_select.rb +0 -8
  55. data/examples/multi_select_disabled.rb +0 -17
  56. data/examples/multi_select_paged.rb +0 -9
  57. data/examples/multi_select_wrapped.rb +0 -15
  58. data/examples/multiline.rb +0 -9
  59. data/examples/pause.rb +0 -9
  60. data/examples/select.rb +0 -24
  61. data/examples/select_disabled.rb +0 -18
  62. data/examples/select_enum.rb +0 -8
  63. data/examples/select_filtered.rb +0 -11
  64. data/examples/select_paginated.rb +0 -11
  65. data/examples/select_wrapped.rb +0 -15
  66. data/examples/slider.rb +0 -6
  67. data/examples/validation.rb +0 -9
  68. data/examples/yes_no.rb +0 -7
  69. data/lib/tty/prompt/messages.rb +0 -49
  70. data/lib/tty/prompt/timeout.rb +0 -78
  71. data/lib/tty/test_prompt.rb +0 -20
  72. data/spec/spec_helper.rb +0 -45
  73. data/spec/unit/ask_spec.rb +0 -132
  74. data/spec/unit/choice/eql_spec.rb +0 -22
  75. data/spec/unit/choice/from_spec.rb +0 -96
  76. data/spec/unit/choices/add_spec.rb +0 -12
  77. data/spec/unit/choices/each_spec.rb +0 -13
  78. data/spec/unit/choices/find_by_spec.rb +0 -10
  79. data/spec/unit/choices/new_spec.rb +0 -10
  80. data/spec/unit/choices/pluck_spec.rb +0 -9
  81. data/spec/unit/collect_spec.rb +0 -96
  82. data/spec/unit/converters/convert_bool_spec.rb +0 -58
  83. data/spec/unit/converters/convert_char_spec.rb +0 -11
  84. data/spec/unit/converters/convert_custom_spec.rb +0 -14
  85. data/spec/unit/converters/convert_date_spec.rb +0 -34
  86. data/spec/unit/converters/convert_file_spec.rb +0 -18
  87. data/spec/unit/converters/convert_number_spec.rb +0 -39
  88. data/spec/unit/converters/convert_path_spec.rb +0 -15
  89. data/spec/unit/converters/convert_range_spec.rb +0 -22
  90. data/spec/unit/converters/convert_regex_spec.rb +0 -12
  91. data/spec/unit/converters/convert_string_spec.rb +0 -21
  92. data/spec/unit/converters/on_error_spec.rb +0 -9
  93. data/spec/unit/distance/distance_spec.rb +0 -73
  94. data/spec/unit/enum_paginator_spec.rb +0 -75
  95. data/spec/unit/enum_select_spec.rb +0 -446
  96. data/spec/unit/error_spec.rb +0 -20
  97. data/spec/unit/evaluator_spec.rb +0 -67
  98. data/spec/unit/expand_spec.rb +0 -198
  99. data/spec/unit/keypress_spec.rb +0 -72
  100. data/spec/unit/mask_spec.rb +0 -132
  101. data/spec/unit/multi_select_spec.rb +0 -511
  102. data/spec/unit/multiline_spec.rb +0 -77
  103. data/spec/unit/new_spec.rb +0 -20
  104. data/spec/unit/ok_spec.rb +0 -10
  105. data/spec/unit/paginator_spec.rb +0 -73
  106. data/spec/unit/question/checks_spec.rb +0 -97
  107. data/spec/unit/question/default_spec.rb +0 -31
  108. data/spec/unit/question/echo_spec.rb +0 -38
  109. data/spec/unit/question/in_spec.rb +0 -115
  110. data/spec/unit/question/initialize_spec.rb +0 -12
  111. data/spec/unit/question/modifier/apply_to_spec.rb +0 -24
  112. data/spec/unit/question/modifier/letter_case_spec.rb +0 -41
  113. data/spec/unit/question/modifier/whitespace_spec.rb +0 -51
  114. data/spec/unit/question/modify_spec.rb +0 -41
  115. data/spec/unit/question/required_spec.rb +0 -92
  116. data/spec/unit/question/validate_spec.rb +0 -115
  117. data/spec/unit/question/validation/call_spec.rb +0 -31
  118. data/spec/unit/question/validation/coerce_spec.rb +0 -30
  119. data/spec/unit/result_spec.rb +0 -40
  120. data/spec/unit/say_spec.rb +0 -67
  121. data/spec/unit/select_spec.rb +0 -660
  122. data/spec/unit/slider_spec.rb +0 -100
  123. data/spec/unit/statement/initialize_spec.rb +0 -15
  124. data/spec/unit/subscribe_spec.rb +0 -22
  125. data/spec/unit/suggest_spec.rb +0 -28
  126. data/spec/unit/warn_spec.rb +0 -21
  127. data/spec/unit/yes_no_spec.rb +0 -251
  128. data/tasks/console.rake +0 -11
  129. data/tasks/coverage.rake +0 -11
  130. data/tasks/spec.rake +0 -29
  131. data/tty-prompt.gemspec +0 -33
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'converters'
4
- require_relative 'evaluator'
5
- require_relative 'question/modifier'
6
- require_relative 'question/validation'
7
- require_relative 'question/checks'
8
- require_relative 'utils'
3
+ require_relative "converters"
4
+ require_relative "evaluator"
5
+ require_relative "question/modifier"
6
+ require_relative "question/validation"
7
+ require_relative "question/checks"
8
+ require_relative "utils"
9
9
 
10
10
  module TTY
11
11
  # A class responsible for shell prompt interactions.
@@ -34,22 +34,31 @@ module TTY
34
34
  # Initialize a Question
35
35
  #
36
36
  # @api public
37
- def initialize(prompt, options = {})
38
- @prompt = prompt
39
- @prefix = options.fetch(:prefix) { @prompt.prefix }
40
- @default = options.fetch(:default) { UndefinedSetting }
41
- @required = options.fetch(:required) { false }
42
- @echo = options.fetch(:echo) { true }
43
- @in = options.fetch(:in) { UndefinedSetting }
44
- @modifier = options.fetch(:modifier) { [] }
45
- @validation = options.fetch(:validation) { UndefinedSetting }
46
- @convert = options.fetch(:convert) { UndefinedSetting }
37
+ def initialize(prompt, **options)
38
+ # Option deprecation
39
+ if options[:validation]
40
+ warn "[DEPRECATION] The `:validation` option is deprecated. Use `:validate` instead."
41
+ options[:validate] = options[:validation]
42
+ end
43
+
44
+ @prompt = prompt
45
+ @prefix = options.fetch(:prefix) { @prompt.prefix }
46
+ @default = options.fetch(:default) { UndefinedSetting }
47
+ @required = options.fetch(:required) { false }
48
+ @echo = options.fetch(:echo) { true }
49
+ @in = options.fetch(:in) { UndefinedSetting }
50
+ @modifier = options.fetch(:modifier) { [] }
51
+ @validation = options.fetch(:validate) { UndefinedSetting }
52
+ @convert = options.fetch(:convert) { UndefinedSetting }
47
53
  @active_color = options.fetch(:active_color) { @prompt.active_color }
48
- @help_color = options.fetch(:help_color) { @prompt.help_color }
49
- @error_color = options.fetch(:error_color) { :red }
50
- @messages = Utils.deep_copy(options.fetch(:messages) { { } })
51
- @done = false
52
- @input = nil
54
+ @help_color = options.fetch(:help_color) { @prompt.help_color }
55
+ @error_color = options.fetch(:error_color) { :red }
56
+ @value = options.fetch(:value) { UndefinedSetting }
57
+ @quiet = options.fetch(:quiet) { @prompt.quiet }
58
+ @messages = Utils.deep_copy(options.fetch(:messages) { {} })
59
+ @done = false
60
+ @first_render = true
61
+ @input = nil
53
62
 
54
63
  @evaluator = Evaluator.new(self)
55
64
 
@@ -58,6 +67,7 @@ module TTY
58
67
  @evaluator << CheckRange
59
68
  @evaluator << CheckValidation
60
69
  @evaluator << CheckModifier
70
+ @evaluator << CheckConversion
61
71
  end
62
72
 
63
73
  # Stores all the error messages displayed to user
@@ -83,7 +93,7 @@ module TTY
83
93
  if template && !template.match(/\%\{/).nil?
84
94
  [template % tokens]
85
95
  else
86
- [template || '']
96
+ [template || ""]
87
97
  end
88
98
  end
89
99
 
@@ -94,8 +104,7 @@ module TTY
94
104
  # @return [self]
95
105
  #
96
106
  # @api public
97
- def call(message, &block)
98
- return if Utils.blank?(message)
107
+ def call(message = "", &block)
99
108
  @message = message
100
109
  block.call(self) if block
101
110
  @prompt.subscribe(self) do
@@ -121,8 +130,8 @@ module TTY
121
130
  total_lines = @prompt.count_screen_lines(input_line)
122
131
  @prompt.print(refresh(question.lines.count, total_lines))
123
132
  end
124
- @prompt.print(render_question)
125
- convert_result(result.value)
133
+ @prompt.print(render_question) unless @quiet
134
+ result.value
126
135
  end
127
136
 
128
137
  # Render question
@@ -131,13 +140,16 @@ module TTY
131
140
  #
132
141
  # @api private
133
142
  def render_question
134
- header = ["#{@prefix}#{message} "]
143
+ header = []
144
+ if !Utils.blank?(@prefix) || !Utils.blank?(message)
145
+ header << "#{@prefix}#{message} "
146
+ end
135
147
  if !echo?
136
148
  header
137
149
  elsif @done
138
150
  header << @prompt.decorate(@input.to_s, @active_color)
139
151
  elsif default? && !Utils.blank?(@default)
140
- header << @prompt.decorate("(#{default})", @help_color) + ' '
152
+ header << @prompt.decorate("(#{default})", @help_color) + " "
141
153
  end
142
154
  header << "\n" if @done
143
155
  header.join
@@ -158,7 +170,12 @@ module TTY
158
170
  #
159
171
  # @api private
160
172
  def read_input(question)
161
- @prompt.read_line(question, echo: echo).chomp
173
+ options = { echo: echo }
174
+ if value? && @first_render
175
+ options[:value] = @value
176
+ @first_render = false
177
+ end
178
+ @prompt.read_line(question, **options).chomp
162
179
  end
163
180
 
164
181
  # Handle error condition
@@ -168,7 +185,7 @@ module TTY
168
185
  # @api private
169
186
  def render_error(errors)
170
187
  errors.reduce([]) do |acc, err|
171
- acc << @prompt.decorate('>>', :red) + ' ' + err
188
+ acc << @prompt.decorate(">>", :red) + " " + err
172
189
  acc
173
190
  end.join("\n")
174
191
  end
@@ -202,8 +219,13 @@ module TTY
202
219
  #
203
220
  # @api private
204
221
  def convert_result(value)
205
- if convert? & !Utils.blank?(value)
206
- Converters.convert(@convert, value)
222
+ if convert? && !Utils.blank?(value)
223
+ case @convert
224
+ when Proc
225
+ @convert.call(value)
226
+ else
227
+ Converters.convert(@convert, value)
228
+ end
207
229
  else
208
230
  value
209
231
  end
@@ -212,8 +234,13 @@ module TTY
212
234
  # Specify answer conversion
213
235
  #
214
236
  # @api public
215
- def convert(value)
216
- @convert = value
237
+ def convert(value = (not_set = true), message = nil)
238
+ messages[:convert?] = message if message
239
+ if not_set
240
+ @convert
241
+ else
242
+ @convert = value
243
+ end
217
244
  end
218
245
 
219
246
  # Check if conversion is set
@@ -230,6 +257,7 @@ module TTY
230
257
  # @api public
231
258
  def default(value = (not_set = true))
232
259
  return @default if not_set
260
+
233
261
  @default = value
234
262
  end
235
263
 
@@ -250,9 +278,10 @@ module TTY
250
278
  def required(value = (not_set = true), message = nil)
251
279
  messages[:required?] = message if message
252
280
  return @required if not_set
281
+
253
282
  @required = value
254
283
  end
255
- alias_method :required?, :required
284
+ alias required? required
256
285
 
257
286
  # Set validation rule for an argument
258
287
  #
@@ -266,6 +295,22 @@ module TTY
266
295
  @validation = (value || block)
267
296
  end
268
297
 
298
+ # Prepopulate input with custom content
299
+ #
300
+ # @api public
301
+ def value(val)
302
+ return @value if val.nil?
303
+
304
+ @value = val
305
+ end
306
+
307
+ # Check if custom value is present
308
+ #
309
+ # @api private
310
+ def value?
311
+ @value != UndefinedSetting
312
+ end
313
+
269
314
  def validation?
270
315
  @validation != UndefinedSetting
271
316
  end
@@ -285,18 +330,20 @@ module TTY
285
330
  # @api public
286
331
  def echo(value = nil)
287
332
  return @echo if value.nil?
333
+
288
334
  @echo = value
289
335
  end
290
- alias_method :echo?, :echo
336
+ alias echo? echo
291
337
 
292
338
  # Turn raw mode on or off. This enables character-based input.
293
339
  #
294
340
  # @api public
295
341
  def raw(value = nil)
296
342
  return @raw if value.nil?
343
+
297
344
  @raw = value
298
345
  end
299
- alias_method :raw?, :raw
346
+ alias raw? raw
300
347
 
301
348
  # Set expected range of values
302
349
  #
@@ -309,6 +356,7 @@ module TTY
309
356
  @in = Converters.convert(:range, @in)
310
357
  end
311
358
  return @in if not_set
359
+
312
360
  @in = Converters.convert(:range, value)
313
361
  end
314
362
 
@@ -321,6 +369,13 @@ module TTY
321
369
  @in != UndefinedSetting
322
370
  end
323
371
 
372
+ # Set quiet mode.
373
+ #
374
+ # @api public
375
+ def quiet(value)
376
+ @quiet = value
377
+ end
378
+
324
379
  # @api public
325
380
  def to_s
326
381
  message.to_s
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../const"
4
+
3
5
  module TTY
4
6
  class Prompt
5
7
  class Question
@@ -40,7 +42,7 @@ module TTY
40
42
  (question.in? && question.in.include?(cast(value)))
41
43
  [value]
42
44
  else
43
- tokens = {value: value, in: question.in}
45
+ tokens = { value: value, in: question.in }
44
46
  [value, question.message_for(:range?, tokens)]
45
47
  end
46
48
  end
@@ -54,7 +56,7 @@ module TTY
54
56
  Validation.new(question.validation).call(value))
55
57
  [value]
56
58
  else
57
- tokens = {valid: question.validation.inspect}
59
+ tokens = { valid: question.validation.inspect }
58
60
  [value, question.message_for(:valid?, tokens)]
59
61
  end
60
62
  end
@@ -81,6 +83,22 @@ module TTY
81
83
  end
82
84
  end
83
85
  end
86
+
87
+ class CheckConversion
88
+ def self.call(question, value)
89
+ if question.convert? && !Utils.blank?(value)
90
+ result = question.convert_result(value)
91
+ if result == Const::Undefined
92
+ tokens = { value: value, type: question.convert }
93
+ [value, question.message_for(:convert?, tokens)]
94
+ else
95
+ [result]
96
+ end
97
+ else
98
+ [value]
99
+ end
100
+ end
101
+ end
84
102
  end # Checks
85
103
  end # Question
86
104
  end # Prompt
@@ -49,6 +49,7 @@ module TTY
49
49
  # @api public
50
50
  def self.letter_case(mod, value)
51
51
  return value unless value.is_a?(String)
52
+
52
53
  case mod
53
54
  when :up, :upcase, :uppercase
54
55
  value.upcase
@@ -75,15 +76,16 @@ module TTY
75
76
  # @api public
76
77
  def self.whitespace(mod, value)
77
78
  return value unless value.is_a?(String)
79
+
78
80
  case mod
79
81
  when :trim, :strip
80
82
  value.strip
81
83
  when :chomp
82
84
  value.chomp
83
85
  when :collapse
84
- value.gsub(/\s+/, ' ')
86
+ value.gsub(/\s+/, " ")
85
87
  when :remove
86
- value.gsub(/\s+/, '')
88
+ value.gsub(/\s+/, "")
87
89
  else
88
90
  value
89
91
  end
@@ -57,11 +57,11 @@ module TTY
57
57
  def call(input)
58
58
  if pattern.is_a?(String) || pattern.is_a?(Symbol)
59
59
  VALIDATORS.key?(pattern.to_sym)
60
- !VALIDATORS[pattern.to_sym].match(input).nil?
60
+ !VALIDATORS[pattern.to_sym].match(input.to_s).nil?
61
61
  elsif pattern.is_a?(Regexp)
62
- !pattern.match(input).nil?
62
+ !pattern.match(input.to_s).nil?
63
63
  elsif pattern.is_a?(Proc)
64
- result = pattern.call(input)
64
+ result = pattern.call(input.to_s)
65
65
  result.nil? ? false : result
66
66
  else false
67
67
  end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTY
4
+ class Prompt
5
+ # @api private
6
+ class SelectedChoices
7
+ include Enumerable
8
+
9
+ attr_reader :size
10
+
11
+ # Create selected choices
12
+ #
13
+ # @param [Array<Choice>] selected
14
+ # @param [Array<Integer>] indexes
15
+ #
16
+ # @api public
17
+ def initialize(selected = [], indexes = [])
18
+ @selected = selected
19
+ @indexes = indexes
20
+ @size = @selected.size
21
+ end
22
+
23
+ # Clear selected choices
24
+ #
25
+ # @api public
26
+ def clear
27
+ @indexes.clear
28
+ @selected.clear
29
+ @size = 0
30
+ end
31
+
32
+ # Iterate over selected choices
33
+ #
34
+ # @api public
35
+ def each(&block)
36
+ return to_enum unless block_given?
37
+
38
+ @selected.each(&block)
39
+ end
40
+
41
+ # Insert choice at index
42
+ #
43
+ # @param [Integer] index
44
+ # @param [Choice] choice
45
+ #
46
+ # @api public
47
+ def insert(index, choice)
48
+ insert_idx = find_index_by { |i| index < @indexes[i] }
49
+ insert_idx ||= -1
50
+ @indexes.insert(insert_idx, index)
51
+ @selected.insert(insert_idx, choice)
52
+ @size += 1
53
+ self
54
+ end
55
+
56
+ # Delete choice at index
57
+ #
58
+ # @return [Choice]
59
+ # the deleted choice
60
+ #
61
+ # @api public
62
+ def delete_at(index)
63
+ delete_idx = @indexes.each_index.find { |i| index == @indexes[i] }
64
+ return nil unless delete_idx
65
+
66
+ @indexes.delete_at(delete_idx)
67
+ choice = @selected.delete_at(delete_idx)
68
+ @size -= 1
69
+ choice
70
+ end
71
+
72
+ def find_index_by(&search)
73
+ (0...@size).bsearch(&search)
74
+ end
75
+ end # SelectedChoices
76
+ end # Prompt
77
+ end # TTY
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'symbols'
4
-
5
3
  module TTY
6
4
  # A class responsible for shell prompt interactions.
7
5
  class Prompt
@@ -9,11 +7,9 @@ module TTY
9
7
  #
10
8
  # @api public
11
9
  class Slider
12
- include Symbols
13
-
14
- HELP = '(Use arrow keys, press Enter to select)'.freeze
10
+ HELP = "(Use %s arrow keys, press Enter to select)"
15
11
 
16
- FORMAT = ':slider %d'.freeze
12
+ FORMAT = ":slider %s"
17
13
 
18
14
  # Initailize a Slider
19
15
  #
@@ -27,20 +23,37 @@ module TTY
27
23
  # @option options [String] :format The display format
28
24
  #
29
25
  # @api public
30
- def initialize(prompt, options = {})
26
+ def initialize(prompt, **options)
31
27
  @prompt = prompt
32
28
  @prefix = options.fetch(:prefix) { @prompt.prefix }
33
- @min = options.fetch(:min) { 0 }
34
- @max = options.fetch(:max) { 10 }
35
- @step = options.fetch(:step) { 1 }
29
+ @choices = Choices.new
30
+ @min = options.fetch(:min, 0)
31
+ @max = options.fetch(:max, 10)
32
+ @step = options.fetch(:step, 1)
36
33
  @default = options[:default]
37
34
  @active_color = options.fetch(:active_color) { @prompt.active_color }
38
35
  @help_color = options.fetch(:help_color) { @prompt.help_color }
39
36
  @format = options.fetch(:format) { FORMAT }
37
+ @quiet = options.fetch(:quiet) { @prompt.quiet }
38
+ @help = options[:help]
39
+ @show_help = options.fetch(:show_help) { :start }
40
+ @symbols = @prompt.symbols.merge(options.fetch(:symbols, {}))
40
41
  @first_render = true
41
42
  @done = false
42
43
  end
43
44
 
45
+ # Change symbols used by this prompt
46
+ #
47
+ # @param [Hash] new_symbols
48
+ # the new symbols to use
49
+ #
50
+ # @api public
51
+ def symbols(new_symbols = (not_set = true))
52
+ return @symbols if not_set
53
+
54
+ @symbols.merge!(new_symbols)
55
+ end
56
+
44
57
  # Setup initial active position
45
58
  #
46
59
  # @return [Integer]
@@ -48,19 +61,43 @@ module TTY
48
61
  # @api private
49
62
  def initial
50
63
  if @default.nil?
51
- range.size / 2
64
+ # no default - choose the middle option
65
+ choices.size / 2
66
+ elsif default_choice = choices.find_by(:name, @default)
67
+ # found a Choice by name - use it
68
+ choices.index(default_choice)
52
69
  else
53
- range.index(@default)
70
+ # default is the index number
71
+ @default - 1
54
72
  end
55
73
  end
56
74
 
57
- # Range of numbers to render
75
+ # Default help text
76
+ #
77
+ # @api public
78
+ def default_help
79
+ arrows = @symbols[:arrow_left] + "/" + @symbols[:arrow_right]
80
+ sprintf(HELP, arrows)
81
+ end
82
+
83
+ # Set help text
58
84
  #
59
- # @return [Array[Integer]]
85
+ # @param [String] text
60
86
  #
61
- # @apip private
62
- def range
63
- (@min..@max).step(@step).to_a
87
+ # @api private
88
+ def help(text = (not_set = true))
89
+ return @help if !@help.nil? && not_set
90
+
91
+ @help = (@help.nil? && not_set) ? default_help : text
92
+ end
93
+
94
+ # Change when help is displayed
95
+ #
96
+ # @api public
97
+ def show_help(value = (not_set = true))
98
+ return @show_ehlp if not_set
99
+
100
+ @show_help = value
64
101
  end
65
102
 
66
103
  # @api public
@@ -83,19 +120,57 @@ module TTY
83
120
  @step = value
84
121
  end
85
122
 
123
+ # Add a single choice
124
+ #
125
+ # @api public
126
+ def choice(*value, &block)
127
+ if block
128
+ @choices << (value << block)
129
+ else
130
+ @choices << value
131
+ end
132
+ end
133
+
134
+ # Add multiple choices
135
+ #
136
+ # @param [Array[Object]] values
137
+ # the values to add as choices
138
+ #
139
+ # @api public
140
+ def choices(values = (not_set = true))
141
+ if not_set
142
+ @choices
143
+ else
144
+ values.each { |val| @choices << val }
145
+ end
146
+ end
147
+
148
+ # @api public
86
149
  def format(value)
87
150
  @format = value
88
151
  end
89
152
 
153
+ # Set quiet mode.
154
+ #
155
+ # @api public
156
+ def quiet(value)
157
+ @quiet = value
158
+ end
159
+
90
160
  # Call the slider by passing question
91
161
  #
92
162
  # @param [String] question
93
163
  # the question to ask
94
164
  #
95
165
  # @apu public
96
- def call(question, &block)
166
+ def call(question, possibilities = nil, &block)
97
167
  @question = question
168
+ choices(possibilities) if possibilities
98
169
  block.call(self) if block
170
+ # set up a Choices collection for min, max, step
171
+ # if no possibilities were supplied
172
+ choices((@min..@max).step(@step).to_a) if @choices.empty?
173
+
99
174
  @active = initial
100
175
  @prompt.subscribe(self) do
101
176
  render
@@ -108,7 +183,7 @@ module TTY
108
183
  alias keydown keyleft
109
184
 
110
185
  def keyright(*)
111
- @active += 1 if (@active + 1) < range.size
186
+ @active += 1 if (@active + 1) < choices.size
112
187
  end
113
188
  alias keyup keyright
114
189
 
@@ -120,6 +195,20 @@ module TTY
120
195
 
121
196
  private
122
197
 
198
+ # Check if help is shown only on start
199
+ #
200
+ # @api private
201
+ def help_start?
202
+ @show_help =~ /start/i
203
+ end
204
+
205
+ # Check if help is always displayed
206
+ #
207
+ # @api private
208
+ def help_always?
209
+ @show_help =~ /always/i
210
+ end
211
+
123
212
  # Render an interactive range slider.
124
213
  #
125
214
  # @api private
@@ -131,7 +220,7 @@ module TTY
131
220
  @prompt.read_keypress
132
221
  refresh(question.lines.count)
133
222
  end
134
- @prompt.print(render_question)
223
+ @prompt.print(render_question) unless @quiet
135
224
  answer
136
225
  ensure
137
226
  @prompt.print(@prompt.show)
@@ -147,11 +236,11 @@ module TTY
147
236
  @prompt.print(@prompt.clear_lines(lines))
148
237
  end
149
238
 
150
- # @return [Integer]
239
+ # @return [Integer, String]
151
240
  #
152
241
  # @api private
153
242
  def answer
154
- range[@active]
243
+ choices[@active].value
155
244
  end
156
245
 
157
246
  # Render question with the slider
@@ -162,13 +251,14 @@ module TTY
162
251
  def render_question
163
252
  header = ["#{@prefix}#{@question} "]
164
253
  if @done
165
- header << @prompt.decorate(answer.to_s, @active_color)
254
+ header << @prompt.decorate(choices[@active].to_s, @active_color)
166
255
  header << "\n"
167
256
  else
168
257
  header << render_slider
169
258
  end
170
- if @first_render
171
- header << "\n" + @prompt.decorate(HELP, @help_color)
259
+ if @first_render && (help_start? || help_always?) ||
260
+ (help_always? && !@done)
261
+ header << "\n" + @prompt.decorate(help, @help_color)
172
262
  @first_render = false
173
263
  end
174
264
  header.join
@@ -180,11 +270,16 @@ module TTY
180
270
  #
181
271
  # @api private
182
272
  def render_slider
183
- slider = (symbols[:line] * @active) +
184
- @prompt.decorate(symbols[:handle], @active_color) +
185
- (symbols[:line] * (range.size - @active - 1))
186
- value = " #{range[@active]}"
187
- @format.gsub(':slider', slider) % [value]
273
+ slider = (@symbols[:line] * @active) +
274
+ @prompt.decorate(@symbols[:bullet], @active_color) +
275
+ (@symbols[:line] * (choices.size - @active - 1))
276
+ value = choices[@active].name
277
+ case @format
278
+ when Proc
279
+ @format.call(slider, value)
280
+ else
281
+ @format.gsub(":slider", slider) % [value]
282
+ end
188
283
  end
189
284
  end # Slider
190
285
  end # Prompt