tty-prompt 0.3.0 → 0.4.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.
- checksums.yaml +4 -4
- data/.travis.yml +4 -1
- data/CHANGELOG.md +15 -0
- data/Gemfile +2 -2
- data/README.md +185 -25
- data/examples/enum.rb +8 -0
- data/examples/enum_select.rb +7 -0
- data/examples/in.rb +3 -1
- data/examples/slider.rb +6 -0
- data/lib/tty-prompt.rb +8 -2
- data/lib/tty/prompt.rb +63 -13
- data/lib/tty/prompt/converters.rb +12 -6
- data/lib/tty/prompt/enum_list.rb +222 -0
- data/lib/tty/prompt/list.rb +48 -15
- data/lib/tty/prompt/multi_list.rb +11 -11
- data/lib/tty/prompt/question.rb +38 -14
- data/lib/tty/prompt/question/checks.rb +5 -3
- data/lib/tty/prompt/reader.rb +12 -18
- data/lib/tty/prompt/reader/codes.rb +15 -9
- data/lib/tty/prompt/reader/key_event.rb +51 -24
- data/lib/tty/prompt/slider.rb +170 -0
- data/lib/tty/prompt/symbols.rb +7 -1
- data/lib/tty/prompt/utils.rb +31 -3
- data/lib/tty/prompt/version.rb +1 -1
- data/spec/spec_helper.rb +1 -0
- data/spec/unit/converters/convert_bool_spec.rb +1 -1
- data/spec/unit/converters/convert_date_spec.rb +11 -2
- data/spec/unit/converters/convert_file_spec.rb +1 -1
- data/spec/unit/converters/convert_number_spec.rb +19 -2
- data/spec/unit/converters/convert_path_spec.rb +1 -1
- data/spec/unit/converters/convert_range_spec.rb +4 -3
- data/spec/unit/enum_select_spec.rb +93 -0
- data/spec/unit/multi_select_spec.rb +14 -12
- data/spec/unit/question/checks_spec.rb +97 -0
- data/spec/unit/reader/key_event_spec.rb +67 -0
- data/spec/unit/select_spec.rb +15 -16
- data/spec/unit/slider_spec.rb +54 -0
- data/tty-prompt.gemspec +2 -1
- metadata +31 -5
- data/.ruby-version +0 -1
data/examples/slider.rb
ADDED
data/lib/tty-prompt.rb
CHANGED
@@ -8,12 +8,14 @@ require 'tty-platform'
|
|
8
8
|
require 'tty/prompt'
|
9
9
|
require 'tty/prompt/choice'
|
10
10
|
require 'tty/prompt/choices'
|
11
|
+
require 'tty/prompt/enum_list'
|
11
12
|
require 'tty/prompt/evaluator'
|
12
13
|
require 'tty/prompt/list'
|
13
14
|
require 'tty/prompt/multi_list'
|
14
15
|
require 'tty/prompt/question'
|
15
16
|
require 'tty/prompt/mask_question'
|
16
17
|
require 'tty/prompt/reader'
|
18
|
+
require 'tty/prompt/slider'
|
17
19
|
require 'tty/prompt/statement'
|
18
20
|
require 'tty/prompt/suggestion'
|
19
21
|
require 'tty/prompt/symbols'
|
@@ -24,5 +26,9 @@ require 'tty/prompt/version'
|
|
24
26
|
# A collection of small libraries for building CLI apps,
|
25
27
|
# each following unix philosophy of focused task
|
26
28
|
module TTY
|
27
|
-
|
28
|
-
|
29
|
+
class Prompt
|
30
|
+
ConfigurationError = Class.new(StandardError)
|
31
|
+
|
32
|
+
ConversionError = Class.new(StandardError)
|
33
|
+
end
|
34
|
+
end # TTY
|
data/lib/tty/prompt.rb
CHANGED
@@ -44,6 +44,14 @@ module TTY
|
|
44
44
|
|
45
45
|
def_delegators :@output, :print, :puts, :flush
|
46
46
|
|
47
|
+
def self.messages
|
48
|
+
{
|
49
|
+
range?: 'Value %{value} must be within the range %{in}',
|
50
|
+
valid?: 'Your answer is invalid (must match %{valid})',
|
51
|
+
required?: 'Value must be provided'
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
47
55
|
# Initialize a Prompt
|
48
56
|
#
|
49
57
|
# @api public
|
@@ -77,7 +85,7 @@ module TTY
|
|
77
85
|
# @api public
|
78
86
|
def ask(message, *args, &block)
|
79
87
|
options = Utils.extract_options!(args)
|
80
|
-
|
88
|
+
options.merge!(self.class.messages)
|
81
89
|
question = Question.new(self, options)
|
82
90
|
question.call(message, &block)
|
83
91
|
end
|
@@ -143,17 +151,7 @@ module TTY
|
|
143
151
|
#
|
144
152
|
# @api public
|
145
153
|
def select(question, *args, &block)
|
146
|
-
|
147
|
-
choices = if block
|
148
|
-
[]
|
149
|
-
elsif args.empty?
|
150
|
-
options
|
151
|
-
else
|
152
|
-
args.flatten
|
153
|
-
end
|
154
|
-
|
155
|
-
list = List.new(self, options)
|
156
|
-
list.call(question, choices, &block)
|
154
|
+
invoke_select(List, question, *args, &block)
|
157
155
|
end
|
158
156
|
|
159
157
|
# Ask a question with multiple attributes activated
|
@@ -173,6 +171,40 @@ module TTY
|
|
173
171
|
#
|
174
172
|
# @api public
|
175
173
|
def multi_select(question, *args, &block)
|
174
|
+
invoke_select(MultiList, question, *args, &block)
|
175
|
+
end
|
176
|
+
|
177
|
+
# Ask a question with indexed list
|
178
|
+
#
|
179
|
+
# @example
|
180
|
+
# prompt = TTY::Prompt.new
|
181
|
+
# editors = %w(emacs nano vim)
|
182
|
+
# prompt.enum_select(EnumList, "Select editor: ", editors)
|
183
|
+
#
|
184
|
+
# @param [String] question
|
185
|
+
# the question to ask
|
186
|
+
#
|
187
|
+
# @param [Array[Object]] choices
|
188
|
+
# the choices to select from
|
189
|
+
#
|
190
|
+
# @return [String]
|
191
|
+
#
|
192
|
+
# @api public
|
193
|
+
def enum_select(question, *args, &block)
|
194
|
+
invoke_select(EnumList, question, *args, &block)
|
195
|
+
end
|
196
|
+
|
197
|
+
# Invoke a list type of prompt
|
198
|
+
#
|
199
|
+
# @example
|
200
|
+
# prompt = TTY::Prompt.new
|
201
|
+
# editors = %w(emacs nano vim)
|
202
|
+
# prompt.invoke_select(EnumList, "Select editor: ", editors)
|
203
|
+
#
|
204
|
+
# @return [String]
|
205
|
+
#
|
206
|
+
# @api public
|
207
|
+
def invoke_select(object, question, *args, &block)
|
176
208
|
options = Utils.extract_options!(args)
|
177
209
|
choices = if block
|
178
210
|
[]
|
@@ -182,7 +214,7 @@ module TTY
|
|
182
214
|
args.flatten
|
183
215
|
end
|
184
216
|
|
185
|
-
list =
|
217
|
+
list = object.new(self, options)
|
186
218
|
list.call(question, choices, &block)
|
187
219
|
end
|
188
220
|
|
@@ -203,6 +235,24 @@ module TTY
|
|
203
235
|
ask(question, *args, &block)
|
204
236
|
end
|
205
237
|
|
238
|
+
# Ask a question with a range slider
|
239
|
+
#
|
240
|
+
# @example
|
241
|
+
# prompt = TTY::Prompt.new
|
242
|
+
# prompt.slider('What size?', min: 32, max: 54, step: 2)
|
243
|
+
#
|
244
|
+
# @param [String] question
|
245
|
+
# the question to ask
|
246
|
+
#
|
247
|
+
# @return [String]
|
248
|
+
#
|
249
|
+
# @api public
|
250
|
+
def slider(question, *args, &block)
|
251
|
+
options = Utils.extract_options!(args)
|
252
|
+
slider = Slider.new(self, options)
|
253
|
+
slider.call(question, &block)
|
254
|
+
end
|
255
|
+
|
206
256
|
# A shortcut method to ask the user negative question and return
|
207
257
|
# true for 'no' reply.
|
208
258
|
#
|
@@ -17,9 +17,15 @@ module TTY
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
+
def self.on_error
|
21
|
+
yield
|
22
|
+
rescue Necromancer::ConversionTypeError => e
|
23
|
+
raise ConversionError, e.message
|
24
|
+
end
|
25
|
+
|
20
26
|
converter(:bool) do |input|
|
21
27
|
converter = Necromancer.new
|
22
|
-
converter.convert(input).to(:boolean, strict: true)
|
28
|
+
on_error { converter.convert(input).to(:boolean, strict: true) }
|
23
29
|
end
|
24
30
|
|
25
31
|
converter(:string) do |input|
|
@@ -32,27 +38,27 @@ module TTY
|
|
32
38
|
|
33
39
|
converter(:date) do |input|
|
34
40
|
converter = Necromancer.new
|
35
|
-
converter.convert(input).to(:date)
|
41
|
+
on_error { converter.convert(input).to(:date, strict: true) }
|
36
42
|
end
|
37
43
|
|
38
44
|
converter(:datetime) do |input|
|
39
45
|
converter = Necromancer.new
|
40
|
-
converter.convert(input).to(:datetime)
|
46
|
+
on_error { converter.convert(input).to(:datetime, strict: true) }
|
41
47
|
end
|
42
48
|
|
43
49
|
converter(:int) do |input|
|
44
50
|
converter = Necromancer.new
|
45
|
-
converter.convert(input).to(:integer)
|
51
|
+
on_error { converter.convert(input).to(:integer, strict: true) }
|
46
52
|
end
|
47
53
|
|
48
54
|
converter(:float) do |input|
|
49
55
|
converter = Necromancer.new
|
50
|
-
converter.convert(input).to(:float)
|
56
|
+
on_error { converter.convert(input).to(:float, strict: true) }
|
51
57
|
end
|
52
58
|
|
53
59
|
converter(:range) do |input|
|
54
60
|
converter = Necromancer.new
|
55
|
-
converter.convert(input).to(:range, strict: true)
|
61
|
+
on_error { converter.convert(input).to(:range, strict: true) }
|
56
62
|
end
|
57
63
|
|
58
64
|
converter(:regexp) do |input|
|
@@ -0,0 +1,222 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Prompt
|
5
|
+
# A class reponsible for rendering enumerated list menu.
|
6
|
+
# Used by {Prompt} to display static choice menu.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class EnumList
|
10
|
+
# Create instance of EnumList menu.
|
11
|
+
#
|
12
|
+
# @api public
|
13
|
+
def initialize(prompt, options = {})
|
14
|
+
@prompt = prompt
|
15
|
+
@done = false
|
16
|
+
@failure = false
|
17
|
+
@enum = options.fetch(:enum) { ')' }
|
18
|
+
@default = options.fetch(:default) { 1 }
|
19
|
+
@active = @default
|
20
|
+
@choices = Choices.new
|
21
|
+
@color = options.fetch(:color) { :green }
|
22
|
+
|
23
|
+
@prompt.subscribe(self)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Set default option selected
|
27
|
+
#
|
28
|
+
# @api public
|
29
|
+
def default(default)
|
30
|
+
@default = default
|
31
|
+
end
|
32
|
+
|
33
|
+
# Set selecting active index using number pad
|
34
|
+
#
|
35
|
+
# @api public
|
36
|
+
def enum(value)
|
37
|
+
@enum = value
|
38
|
+
end
|
39
|
+
|
40
|
+
# Add a single choice
|
41
|
+
#
|
42
|
+
# @api public
|
43
|
+
def choice(*value, &block)
|
44
|
+
if block
|
45
|
+
@choices << (value << block)
|
46
|
+
else
|
47
|
+
@choices << value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Add multiple choices
|
52
|
+
#
|
53
|
+
# @param [Array[Object]] values
|
54
|
+
# the values to add as choices
|
55
|
+
#
|
56
|
+
# @api public
|
57
|
+
def choices(values)
|
58
|
+
values.each { |val| choice(*val) }
|
59
|
+
end
|
60
|
+
|
61
|
+
# Call the list menu by passing question and choices
|
62
|
+
#
|
63
|
+
# @param [String] question
|
64
|
+
#
|
65
|
+
# @param
|
66
|
+
# @api public
|
67
|
+
def call(question, possibilities, &block)
|
68
|
+
choices(possibilities)
|
69
|
+
@question = question
|
70
|
+
block.call(self) if block
|
71
|
+
setup_defaults
|
72
|
+
render
|
73
|
+
end
|
74
|
+
|
75
|
+
def keypress(event)
|
76
|
+
if [:backspace, :delete].include?(event.key.name)
|
77
|
+
@input.chop! unless @input.empty?
|
78
|
+
mark_choice_as_active
|
79
|
+
elsif event.value =~ /^\d+$/
|
80
|
+
@input += event.value
|
81
|
+
mark_choice_as_active
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def keyreturn(*)
|
86
|
+
@failure = false
|
87
|
+
if (@input.to_i > 0 && @input.to_i <= @choices.size) || @input.empty?
|
88
|
+
@done = true
|
89
|
+
else
|
90
|
+
@input = ''
|
91
|
+
@failure = true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
alias_method :keyenter, :keyreturn
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def mark_choice_as_active
|
99
|
+
if !@choices[@input.to_i - 1].nil?
|
100
|
+
@active = @input.to_i
|
101
|
+
else
|
102
|
+
@active = nil
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Setup default option and active selection
|
107
|
+
#
|
108
|
+
# @api private
|
109
|
+
def setup_defaults
|
110
|
+
validate_defaults
|
111
|
+
@active = @default
|
112
|
+
end
|
113
|
+
|
114
|
+
# Validate default indexes to be within range
|
115
|
+
#
|
116
|
+
# @api private
|
117
|
+
def validate_defaults
|
118
|
+
return if @default >= 1 && @default <= @choices.size
|
119
|
+
fail PromptConfigurationError,
|
120
|
+
"default index `#{d}` out of range (1 - #{@choices.size})"
|
121
|
+
end
|
122
|
+
|
123
|
+
# Render a selection list.
|
124
|
+
#
|
125
|
+
# By default the result is printed out.
|
126
|
+
#
|
127
|
+
# @return [Object] value
|
128
|
+
# return the selected value
|
129
|
+
#
|
130
|
+
# @api private
|
131
|
+
def render
|
132
|
+
@input = ''
|
133
|
+
until @done
|
134
|
+
render_question
|
135
|
+
@prompt.read_keypress
|
136
|
+
refresh
|
137
|
+
end
|
138
|
+
render_question
|
139
|
+
render_answer
|
140
|
+
end
|
141
|
+
|
142
|
+
# Find value for the choice selected
|
143
|
+
#
|
144
|
+
# @return [nil, Object]
|
145
|
+
#
|
146
|
+
# @api private
|
147
|
+
def render_answer
|
148
|
+
@choices[@active - 1].value
|
149
|
+
end
|
150
|
+
|
151
|
+
# Determine area of the screen to clear
|
152
|
+
#
|
153
|
+
# @api private
|
154
|
+
def refresh
|
155
|
+
lines = @question.scan("\n").length + @choices.length + 2
|
156
|
+
@prompt.print(@prompt.clear_lines(lines))
|
157
|
+
@prompt.print(@prompt.cursor.clear_screen_down)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Render question with the menu options
|
161
|
+
#
|
162
|
+
# @api private
|
163
|
+
def render_question
|
164
|
+
header = "#{@prompt.prefix}#{@question} #{render_header}"
|
165
|
+
@prompt.puts(header)
|
166
|
+
return if @done
|
167
|
+
@prompt.print(render_menu)
|
168
|
+
@prompt.print(render_footer)
|
169
|
+
render_error if @failure
|
170
|
+
end
|
171
|
+
|
172
|
+
# @api private
|
173
|
+
def render_error
|
174
|
+
error = 'Please enter a valid index'
|
175
|
+
@prompt.print("\n" + @prompt.decorate('>>', :red) + ' ' + error)
|
176
|
+
@prompt.print(@prompt.cursor.prev_line)
|
177
|
+
@prompt.print(@prompt.cursor.forward(render_footer.size))
|
178
|
+
end
|
179
|
+
|
180
|
+
# Render chosen option
|
181
|
+
#
|
182
|
+
# @return [String]
|
183
|
+
#
|
184
|
+
# @api private
|
185
|
+
def render_header
|
186
|
+
return '' unless @done
|
187
|
+
return '' unless @active
|
188
|
+
selected_item = "#{@choices[@active - 1].name}"
|
189
|
+
@prompt.decorate(selected_item, @color)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Render footer for the indexed menu
|
193
|
+
#
|
194
|
+
# @return [String]
|
195
|
+
#
|
196
|
+
# @api private
|
197
|
+
def render_footer
|
198
|
+
" Choose 1-#{@choices.size} [#{@default}]: #{@input}"
|
199
|
+
end
|
200
|
+
|
201
|
+
# Render menu with indexed choices to select from
|
202
|
+
#
|
203
|
+
# @return [String]
|
204
|
+
#
|
205
|
+
# @api private
|
206
|
+
def render_menu
|
207
|
+
output = ''
|
208
|
+
@choices.each_with_index do |choice, index|
|
209
|
+
num = (index + 1).to_s + @enum + Symbols::SPACE
|
210
|
+
selected = Symbols::SPACE * 2 + num + choice.name
|
211
|
+
output << if index + 1 == @active
|
212
|
+
@prompt.decorate("#{selected}", @color)
|
213
|
+
else
|
214
|
+
selected
|
215
|
+
end
|
216
|
+
output << "\n"
|
217
|
+
end
|
218
|
+
output
|
219
|
+
end
|
220
|
+
end # EnumList
|
221
|
+
end # Prompt
|
222
|
+
end # TTY
|
data/lib/tty/prompt/list.rb
CHANGED
@@ -7,7 +7,7 @@ module TTY
|
|
7
7
|
#
|
8
8
|
# @api private
|
9
9
|
class List
|
10
|
-
HELP = '(Use arrow keys, press Enter to select)'.freeze
|
10
|
+
HELP = '(Use arrow%s keys, press Enter to select)'.freeze
|
11
11
|
|
12
12
|
# Create instance of TTY::Prompt::List menu.
|
13
13
|
#
|
@@ -19,18 +19,21 @@ module TTY
|
|
19
19
|
# the color for the selected item, defualts to :green
|
20
20
|
# @option options [Symbol] :marker
|
21
21
|
# the marker for the selected item
|
22
|
+
# @option options [String] :enum
|
23
|
+
# the delimiter for the item index
|
22
24
|
#
|
23
25
|
# @api public
|
24
26
|
def initialize(prompt, options = {})
|
25
27
|
@prompt = prompt
|
26
28
|
@first_render = true
|
27
29
|
@done = false
|
30
|
+
@enum = options.fetch(:enum) { nil }
|
28
31
|
@default = Array[options.fetch(:default) { 1 }]
|
29
32
|
@active = @default.first
|
30
33
|
@choices = Choices.new
|
31
34
|
@color = options.fetch(:color) { :green }
|
32
35
|
@marker = options.fetch(:marker) { Symbols::ITEM_SELECTED }
|
33
|
-
@help = options
|
36
|
+
@help = options[:help]
|
34
37
|
|
35
38
|
@prompt.subscribe(self)
|
36
39
|
end
|
@@ -49,6 +52,13 @@ module TTY
|
|
49
52
|
@default = default_values
|
50
53
|
end
|
51
54
|
|
55
|
+
# Set selecting active index using number pad
|
56
|
+
#
|
57
|
+
# @api public
|
58
|
+
def enum(value)
|
59
|
+
@enum = value
|
60
|
+
end
|
61
|
+
|
52
62
|
# Add a single choice
|
53
63
|
#
|
54
64
|
# @api public
|
@@ -84,23 +94,37 @@ module TTY
|
|
84
94
|
render
|
85
95
|
end
|
86
96
|
|
87
|
-
|
97
|
+
# Check if list is enumerated
|
98
|
+
#
|
99
|
+
# @return [Boolean]
|
100
|
+
def enumerate?
|
101
|
+
!@enum.nil?
|
102
|
+
end
|
103
|
+
|
104
|
+
def keynum(event)
|
105
|
+
return unless enumerate?
|
106
|
+
value = event.value.to_i
|
107
|
+
return unless (1..@choices.count).include?(value)
|
108
|
+
@active = value
|
109
|
+
end
|
110
|
+
|
111
|
+
def keyescape(*)
|
88
112
|
exit 130
|
89
113
|
end
|
90
114
|
|
91
|
-
def keyspace(
|
115
|
+
def keyspace(*)
|
92
116
|
@done = true
|
93
117
|
end
|
94
118
|
|
95
|
-
def keyreturn(
|
119
|
+
def keyreturn(*)
|
96
120
|
@done = true
|
97
121
|
end
|
98
122
|
|
99
|
-
def keyup(
|
123
|
+
def keyup(*)
|
100
124
|
@active = (@active == 1) ? @choices.length : @active - 1
|
101
125
|
end
|
102
126
|
|
103
|
-
def keydown(
|
127
|
+
def keydown(*)
|
104
128
|
@active = (@active == @choices.length) ? 1 : @active + 1
|
105
129
|
end
|
106
130
|
|
@@ -120,7 +144,7 @@ module TTY
|
|
120
144
|
def validate_defaults
|
121
145
|
@default.each do |d|
|
122
146
|
if d < 1 || d > @choices.size
|
123
|
-
fail
|
147
|
+
fail ConfigurationError,
|
124
148
|
"default index `#{d}` out of range (1 - #{@choices.size})"
|
125
149
|
end
|
126
150
|
end
|
@@ -172,7 +196,15 @@ module TTY
|
|
172
196
|
header = "#{@prompt.prefix}#{@question} #{render_header}"
|
173
197
|
@prompt.puts(header)
|
174
198
|
@first_render = false
|
175
|
-
render_menu unless @done
|
199
|
+
@prompt.print(render_menu) unless @done
|
200
|
+
end
|
201
|
+
|
202
|
+
# Provide help information
|
203
|
+
#
|
204
|
+
# @return [String]
|
205
|
+
def help
|
206
|
+
return @help unless @help.nil?
|
207
|
+
self.class::HELP % [enumerate? ? ' or number (0-9)' : '']
|
176
208
|
end
|
177
209
|
|
178
210
|
# Render initial help and selected choice
|
@@ -185,9 +217,7 @@ module TTY
|
|
185
217
|
selected_item = "#{@choices[@active - 1].name}"
|
186
218
|
@prompt.decorate(selected_item, @color)
|
187
219
|
elsif @first_render
|
188
|
-
@prompt.decorate(
|
189
|
-
else
|
190
|
-
''
|
220
|
+
@prompt.decorate(help, :bright_black)
|
191
221
|
end
|
192
222
|
end
|
193
223
|
|
@@ -195,16 +225,19 @@ module TTY
|
|
195
225
|
#
|
196
226
|
# @api private
|
197
227
|
def render_menu
|
228
|
+
output = ''
|
198
229
|
@choices.each_with_index do |choice, index|
|
230
|
+
num = enumerate? ? (index + 1).to_s + @enum + Symbols::SPACE : ''
|
199
231
|
message = if index + 1 == @active
|
200
|
-
selected = @marker + Symbols::SPACE + choice.name
|
232
|
+
selected = @marker + Symbols::SPACE + num + choice.name
|
201
233
|
@prompt.decorate("#{selected}", @color)
|
202
234
|
else
|
203
|
-
Symbols::SPACE * 2 + choice.name
|
235
|
+
Symbols::SPACE * 2 + num + choice.name
|
204
236
|
end
|
205
237
|
newline = (index == @choices.length - 1) ? '' : "\n"
|
206
|
-
|
238
|
+
output << (message + newline)
|
207
239
|
end
|
240
|
+
output
|
208
241
|
end
|
209
242
|
end # List
|
210
243
|
end # Prompt
|