tty-prompt 0.19.0 → 0.23.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +81 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +485 -233
  5. data/lib/tty-prompt.rb +1 -2
  6. data/lib/tty/prompt.rb +159 -147
  7. data/lib/tty/prompt/answers_collector.rb +5 -5
  8. data/lib/tty/prompt/block_paginator.rb +1 -1
  9. data/lib/tty/prompt/choice.rb +31 -13
  10. data/lib/tty/prompt/choices.rb +30 -12
  11. data/lib/tty/prompt/confirm_question.rb +42 -16
  12. data/lib/tty/prompt/const.rb +17 -0
  13. data/lib/tty/prompt/converter_dsl.rb +6 -7
  14. data/lib/tty/prompt/converter_registry.rb +31 -26
  15. data/lib/tty/prompt/converters.rb +139 -32
  16. data/lib/tty/prompt/enum_list.rb +58 -25
  17. data/lib/tty/prompt/errors.rb +31 -0
  18. data/lib/tty/prompt/evaluator.rb +2 -2
  19. data/lib/tty/prompt/expander.rb +27 -15
  20. data/lib/tty/prompt/keypress.rb +5 -3
  21. data/lib/tty/prompt/list.rb +101 -38
  22. data/lib/tty/prompt/mask_question.rb +9 -5
  23. data/lib/tty/prompt/multi_list.rb +108 -29
  24. data/lib/tty/prompt/multiline.rb +9 -7
  25. data/lib/tty/prompt/paginator.rb +1 -1
  26. data/lib/tty/prompt/question.rb +67 -36
  27. data/lib/tty/prompt/question/checks.rb +20 -2
  28. data/lib/tty/prompt/question/modifier.rb +4 -2
  29. data/lib/tty/prompt/question/validation.rb +3 -3
  30. data/lib/tty/prompt/selected_choices.rb +77 -0
  31. data/lib/tty/prompt/slider.rb +110 -23
  32. data/lib/tty/prompt/statement.rb +3 -3
  33. data/lib/tty/prompt/suggestion.rb +7 -6
  34. data/lib/tty/prompt/symbols.rb +58 -58
  35. data/lib/tty/prompt/test.rb +36 -0
  36. data/lib/tty/prompt/utils.rb +1 -3
  37. data/lib/tty/prompt/version.rb +1 -1
  38. metadata +27 -196
  39. data/Rakefile +0 -8
  40. data/examples/ask.rb +0 -7
  41. data/examples/ask_blank.rb +0 -9
  42. data/examples/ask_valid.rb +0 -12
  43. data/examples/collect.rb +0 -21
  44. data/examples/echo.rb +0 -11
  45. data/examples/enum_select.rb +0 -7
  46. data/examples/enum_select_disabled.rb +0 -16
  47. data/examples/enum_select_paged.rb +0 -9
  48. data/examples/enum_select_wrapped.rb +0 -15
  49. data/examples/expand.rb +0 -29
  50. data/examples/expand_auto.rb +0 -29
  51. data/examples/in.rb +0 -9
  52. data/examples/inputs.rb +0 -10
  53. data/examples/key_events.rb +0 -15
  54. data/examples/keypress.rb +0 -9
  55. data/examples/mask.rb +0 -13
  56. data/examples/multi_select.rb +0 -8
  57. data/examples/multi_select_disabled.rb +0 -17
  58. data/examples/multi_select_disabled_paged.rb +0 -22
  59. data/examples/multi_select_paged.rb +0 -9
  60. data/examples/multi_select_wrapped.rb +0 -15
  61. data/examples/multiline.rb +0 -9
  62. data/examples/pause.rb +0 -9
  63. data/examples/select.rb +0 -24
  64. data/examples/select_disabled.rb +0 -18
  65. data/examples/select_disabled_paged.rb +0 -22
  66. data/examples/select_enum.rb +0 -8
  67. data/examples/select_filtered.rb +0 -11
  68. data/examples/select_paginated.rb +0 -11
  69. data/examples/select_wrapped.rb +0 -15
  70. data/examples/slider.rb +0 -6
  71. data/examples/validation.rb +0 -9
  72. data/examples/yes_no.rb +0 -7
  73. data/lib/tty/prompt/messages.rb +0 -49
  74. data/lib/tty/test_prompt.rb +0 -20
  75. data/spec/spec_helper.rb +0 -61
  76. data/spec/unit/ask_spec.rb +0 -173
  77. data/spec/unit/block_paginator_spec.rb +0 -84
  78. data/spec/unit/choice/eql_spec.rb +0 -22
  79. data/spec/unit/choice/from_spec.rb +0 -112
  80. data/spec/unit/choices/add_spec.rb +0 -12
  81. data/spec/unit/choices/each_spec.rb +0 -13
  82. data/spec/unit/choices/find_by_spec.rb +0 -10
  83. data/spec/unit/choices/new_spec.rb +0 -10
  84. data/spec/unit/choices/pluck_spec.rb +0 -9
  85. data/spec/unit/collect_spec.rb +0 -96
  86. data/spec/unit/converters/convert_bool_spec.rb +0 -58
  87. data/spec/unit/converters/convert_char_spec.rb +0 -11
  88. data/spec/unit/converters/convert_custom_spec.rb +0 -14
  89. data/spec/unit/converters/convert_date_spec.rb +0 -34
  90. data/spec/unit/converters/convert_file_spec.rb +0 -18
  91. data/spec/unit/converters/convert_number_spec.rb +0 -39
  92. data/spec/unit/converters/convert_path_spec.rb +0 -15
  93. data/spec/unit/converters/convert_range_spec.rb +0 -22
  94. data/spec/unit/converters/convert_regex_spec.rb +0 -12
  95. data/spec/unit/converters/convert_string_spec.rb +0 -21
  96. data/spec/unit/converters/on_error_spec.rb +0 -9
  97. data/spec/unit/distance/distance_spec.rb +0 -73
  98. data/spec/unit/enum_select_spec.rb +0 -518
  99. data/spec/unit/error_spec.rb +0 -20
  100. data/spec/unit/evaluator_spec.rb +0 -67
  101. data/spec/unit/expand_spec.rb +0 -290
  102. data/spec/unit/keypress_spec.rb +0 -66
  103. data/spec/unit/mask_spec.rb +0 -140
  104. data/spec/unit/multi_select_spec.rb +0 -741
  105. data/spec/unit/multiline_spec.rb +0 -77
  106. data/spec/unit/new_spec.rb +0 -20
  107. data/spec/unit/ok_spec.rb +0 -10
  108. data/spec/unit/paginator_spec.rb +0 -92
  109. data/spec/unit/question/checks_spec.rb +0 -97
  110. data/spec/unit/question/default_spec.rb +0 -31
  111. data/spec/unit/question/echo_spec.rb +0 -38
  112. data/spec/unit/question/in_spec.rb +0 -115
  113. data/spec/unit/question/initialize_spec.rb +0 -12
  114. data/spec/unit/question/modifier/apply_to_spec.rb +0 -24
  115. data/spec/unit/question/modifier/letter_case_spec.rb +0 -41
  116. data/spec/unit/question/modifier/whitespace_spec.rb +0 -51
  117. data/spec/unit/question/modify_spec.rb +0 -41
  118. data/spec/unit/question/required_spec.rb +0 -92
  119. data/spec/unit/question/validate_spec.rb +0 -115
  120. data/spec/unit/question/validation/call_spec.rb +0 -31
  121. data/spec/unit/question/validation/coerce_spec.rb +0 -30
  122. data/spec/unit/result_spec.rb +0 -40
  123. data/spec/unit/say_spec.rb +0 -67
  124. data/spec/unit/select_spec.rb +0 -942
  125. data/spec/unit/slider_spec.rb +0 -142
  126. data/spec/unit/statement/initialize_spec.rb +0 -15
  127. data/spec/unit/subscribe_spec.rb +0 -22
  128. data/spec/unit/suggest_spec.rb +0 -28
  129. data/spec/unit/timer_spec.rb +0 -29
  130. data/spec/unit/warn_spec.rb +0 -21
  131. data/spec/unit/yes_no_spec.rb +0 -251
  132. data/tasks/console.rake +0 -11
  133. data/tasks/coverage.rake +0 -11
  134. data/tasks/spec.rake +0 -29
  135. data/tty-prompt.gemspec +0 -31
@@ -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, value: value }
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
@@ -7,9 +7,9 @@ module TTY
7
7
  #
8
8
  # @api public
9
9
  class Slider
10
- HELP = '(Use arrow keys, press Enter to select)'.freeze
10
+ HELP = "(Use %s arrow keys, press Enter to select)"
11
11
 
12
- FORMAT = ':slider %d'.freeze
12
+ FORMAT = ":slider %s"
13
13
 
14
14
  # Initailize a Slider
15
15
  #
@@ -26,13 +26,17 @@ module TTY
26
26
  def initialize(prompt, **options)
27
27
  @prompt = prompt
28
28
  @prefix = options.fetch(:prefix) { @prompt.prefix }
29
- @min = options.fetch(:min) { 0 }
30
- @max = options.fetch(:max) { 10 }
31
- @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)
32
33
  @default = options[:default]
33
34
  @active_color = options.fetch(:active_color) { @prompt.active_color }
34
35
  @help_color = options.fetch(:help_color) { @prompt.help_color }
35
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 }
36
40
  @symbols = @prompt.symbols.merge(options.fetch(:symbols, {}))
37
41
  @first_render = true
38
42
  @done = false
@@ -46,6 +50,7 @@ module TTY
46
50
  # @api public
47
51
  def symbols(new_symbols = (not_set = true))
48
52
  return @symbols if not_set
53
+
49
54
  @symbols.merge!(new_symbols)
50
55
  end
51
56
 
@@ -56,19 +61,43 @@ module TTY
56
61
  # @api private
57
62
  def initial
58
63
  if @default.nil?
59
- 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)
60
69
  else
61
- range.index(@default)
70
+ # default is the index number
71
+ @default - 1
62
72
  end
63
73
  end
64
74
 
65
- # 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
66
84
  #
67
- # @return [Array[Integer]]
85
+ # @param [String] text
68
86
  #
69
- # @apip private
70
- def range
71
- (@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
72
101
  end
73
102
 
74
103
  # @api public
@@ -91,19 +120,57 @@ module TTY
91
120
  @step = value
92
121
  end
93
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
94
149
  def format(value)
95
150
  @format = value
96
151
  end
97
152
 
153
+ # Set quiet mode.
154
+ #
155
+ # @api public
156
+ def quiet(value)
157
+ @quiet = value
158
+ end
159
+
98
160
  # Call the slider by passing question
99
161
  #
100
162
  # @param [String] question
101
163
  # the question to ask
102
164
  #
103
165
  # @apu public
104
- def call(question, &block)
166
+ def call(question, possibilities = nil, &block)
105
167
  @question = question
168
+ choices(possibilities) if possibilities
106
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
+
107
174
  @active = initial
108
175
  @prompt.subscribe(self) do
109
176
  render
@@ -116,7 +183,7 @@ module TTY
116
183
  alias keydown keyleft
117
184
 
118
185
  def keyright(*)
119
- @active += 1 if (@active + 1) < range.size
186
+ @active += 1 if (@active + 1) < choices.size
120
187
  end
121
188
  alias keyup keyright
122
189
 
@@ -128,6 +195,20 @@ module TTY
128
195
 
129
196
  private
130
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
+
131
212
  # Render an interactive range slider.
132
213
  #
133
214
  # @api private
@@ -139,7 +220,7 @@ module TTY
139
220
  @prompt.read_keypress
140
221
  refresh(question.lines.count)
141
222
  end
142
- @prompt.print(render_question)
223
+ @prompt.print(render_question) unless @quiet
143
224
  answer
144
225
  ensure
145
226
  @prompt.print(@prompt.show)
@@ -155,11 +236,11 @@ module TTY
155
236
  @prompt.print(@prompt.clear_lines(lines))
156
237
  end
157
238
 
158
- # @return [Integer]
239
+ # @return [Integer, String]
159
240
  #
160
241
  # @api private
161
242
  def answer
162
- range[@active]
243
+ choices[@active].value
163
244
  end
164
245
 
165
246
  # Render question with the slider
@@ -170,13 +251,14 @@ module TTY
170
251
  def render_question
171
252
  header = ["#{@prefix}#{@question} "]
172
253
  if @done
173
- header << @prompt.decorate(answer.to_s, @active_color)
254
+ header << @prompt.decorate(choices[@active].to_s, @active_color)
174
255
  header << "\n"
175
256
  else
176
257
  header << render_slider
177
258
  end
178
- if @first_render
179
- 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)
180
262
  @first_render = false
181
263
  end
182
264
  header.join
@@ -190,9 +272,14 @@ module TTY
190
272
  def render_slider
191
273
  slider = (@symbols[:line] * @active) +
192
274
  @prompt.decorate(@symbols[:bullet], @active_color) +
193
- (@symbols[:line] * (range.size - @active - 1))
194
- value = " #{range[@active]}"
195
- @format.gsub(':slider', slider) % [value]
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
196
283
  end
197
284
  end # Slider
198
285
  end # Prompt
@@ -28,10 +28,10 @@ module TTY
28
28
  # change the message display to color
29
29
  #
30
30
  # @api public
31
- def initialize(prompt, options = {})
31
+ def initialize(prompt, newline: true, color: false)
32
32
  @prompt = prompt
33
- @newline = options.fetch(:newline) { true }
34
- @color = options.fetch(:color) { false }
33
+ @newline = newline
34
+ @color = color
35
35
  end
36
36
 
37
37
  # Output the message to the prompt
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'distance'
3
+ require_relative "distance"
4
4
 
5
5
  module TTY
6
6
  # A class responsible for terminal prompt interactions.
@@ -11,9 +11,9 @@ module TTY
11
11
  class Suggestion
12
12
  DEFAULT_INDENT = 8
13
13
 
14
- SINGLE_TEXT = 'Did you mean this?'
14
+ SINGLE_TEXT = "Did you mean this?"
15
15
 
16
- PLURAL_TEXT = 'Did you mean one of these?'
16
+ PLURAL_TEXT = "Did you mean one of these?"
17
17
 
18
18
  # Number of spaces
19
19
  #
@@ -33,7 +33,7 @@ module TTY
33
33
  # Initialize a Suggestion
34
34
  #
35
35
  # @api public
36
- def initialize(options = {})
36
+ def initialize(**options)
37
37
  @indent = options.fetch(:indent) { DEFAULT_INDENT }
38
38
  @single_text = options.fetch(:single_text) { SINGLE_TEXT }
39
39
  @plural_text = options.fetch(:plural_text) { PLURAL_TEXT }
@@ -88,6 +88,7 @@ module TTY
88
88
  # @api private
89
89
  def evaluate
90
90
  return @suggestions if @suggestions.empty?
91
+
91
92
  if @suggestions.one?
92
93
  build_single_suggestion
93
94
  else
@@ -97,14 +98,14 @@ module TTY
97
98
 
98
99
  # @api private
99
100
  def build_single_suggestion
100
- single_text + "\n" + (' ' * indent) + @suggestions.first
101
+ single_text + "\n" + (" " * indent) + @suggestions.first
101
102
  end
102
103
 
103
104
  # @api private
104
105
  def build_multiple_suggestions
105
106
  plural_text + "\n" +
106
107
  @suggestions.map do |sugest|
107
- ' ' * indent + sugest
108
+ " " * indent + sugest
108
109
  end.join("\n")
109
110
  end
110
111
  end # Suggestion