tty-prompt 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|