highline 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +23 -0
- data/Rakefile +5 -2
- data/TODO +1 -1
- data/examples/menus.rb +51 -1
- data/lib/highline.rb +208 -25
- data/lib/highline/import.rb +7 -7
- data/lib/highline/menu.rb +333 -0
- data/lib/highline/question.rb +48 -18
- data/test/tc_highline.rb +111 -8
- data/test/tc_import.rb +1 -0
- data/test/tc_menu.rb +326 -0
- data/test/ts_all.rb +1 -0
- metadata +15 -3
@@ -0,0 +1,333 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
|
3
|
+
# menu.rb
|
4
|
+
#
|
5
|
+
# Created by Gregory Thomas Brown on 2005-05-10.
|
6
|
+
# Copyright 2005 smtose.org. All rights reserved.
|
7
|
+
|
8
|
+
require "highline/question"
|
9
|
+
|
10
|
+
class HighLine
|
11
|
+
#
|
12
|
+
# Menu objects encapsulate all the details of a call to HighLine.choose().
|
13
|
+
# Using the accessors and Menu.choice() and Menu.choices(), the block passed
|
14
|
+
# to HighLine.choose() can detail all aspects of menu display and control.
|
15
|
+
#
|
16
|
+
class Menu < Question
|
17
|
+
#
|
18
|
+
# Create an instance of HighLine::Menu. All customization is done
|
19
|
+
# through the passed block, which should call accessors and choice() and
|
20
|
+
# choices() as needed to define the Menu. Note that Menus are also
|
21
|
+
# Questions, so all that functionality is available to the block as
|
22
|
+
# well.
|
23
|
+
#
|
24
|
+
def initialize( )
|
25
|
+
#
|
26
|
+
# Initialize Question objects with ignored valued, we'll
|
27
|
+
# adjust ours as needed.
|
28
|
+
#
|
29
|
+
super("Ignored", [ ], &nil) # avoiding passing to block along
|
30
|
+
|
31
|
+
@items = [ ]
|
32
|
+
|
33
|
+
@index = :number
|
34
|
+
@index_suffix = ". "
|
35
|
+
@select_by = :index_or_name
|
36
|
+
@flow = :rows
|
37
|
+
@list_option = nil
|
38
|
+
@header = nil
|
39
|
+
@prompt = "? "
|
40
|
+
@layout = :list
|
41
|
+
@shell = false
|
42
|
+
@nil_on_handled = false
|
43
|
+
|
44
|
+
# Override Questions repsonses, we'll set our own.
|
45
|
+
@responses = { }
|
46
|
+
|
47
|
+
yield self if block_given?
|
48
|
+
|
49
|
+
update_responses # rebuild responses based on our settings
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# An _index_ to append to each menu item in display. See
|
54
|
+
# Menu.index=() for details.
|
55
|
+
#
|
56
|
+
attr_reader :index
|
57
|
+
#
|
58
|
+
# The String placed between an _index_ and a menu item. Defaults to
|
59
|
+
# ". ". Switches to " ", when _index_ is set to a String (like "-").
|
60
|
+
#
|
61
|
+
attr_accessor :index_suffix
|
62
|
+
#
|
63
|
+
# The _select_by_ attribute controls how the user is allowed to pick a
|
64
|
+
# menu item. The available choices are:
|
65
|
+
#
|
66
|
+
# <tt>:index</tt>:: The user is allowed to type the numerical
|
67
|
+
# or alphetical index for their selection.
|
68
|
+
# <tt>:index_or_name</tt>:: Allows both methods from the
|
69
|
+
# <tt>:index</tt> option and the
|
70
|
+
# <tt>:name</tt> option.
|
71
|
+
# <tt>:name</tt>:: Menu items are selected by typing a portion
|
72
|
+
# of the item name that will be
|
73
|
+
# auto-completed.
|
74
|
+
#
|
75
|
+
attr_accessor :select_by
|
76
|
+
#
|
77
|
+
# This attribute is passed directly on as the mode to HighLine.list() by
|
78
|
+
# all the preset layouts. See that method for appropriate settings.
|
79
|
+
#
|
80
|
+
attr_accessor :flow
|
81
|
+
#
|
82
|
+
# This setting is passed on as the third parameter to HighLine.list()
|
83
|
+
# by all the preset layouts. See that method for details of its
|
84
|
+
# effects. Defaults to +nil+.
|
85
|
+
#
|
86
|
+
attr_accessor :list_option
|
87
|
+
#
|
88
|
+
# Used by all the preset layouts to display title and/or introductory
|
89
|
+
# information, when set. Defaults to +nil+.
|
90
|
+
#
|
91
|
+
attr_accessor :header
|
92
|
+
#
|
93
|
+
# Used by all the preset layouts to ask the actual question to fetch a
|
94
|
+
# menu selection from the user. Defaults to "? ".
|
95
|
+
#
|
96
|
+
attr_accessor :prompt
|
97
|
+
#
|
98
|
+
# An ERb _layout_ to use when displaying this Menu object. See
|
99
|
+
# Menu.layout=() for details.
|
100
|
+
#
|
101
|
+
attr_reader :layout
|
102
|
+
#
|
103
|
+
# When set to +true+, responses are allowed to be an entire line of
|
104
|
+
# input, including details beyond the command itself. Only the first
|
105
|
+
# "word" of input will be matched against the menu choices, but both the
|
106
|
+
# command selected and the rest of the line will be passed to provided
|
107
|
+
# action blocks. Defaults to +false+.
|
108
|
+
#
|
109
|
+
attr_accessor :shell
|
110
|
+
#
|
111
|
+
# When +true+, any selected item handled by provided action code, will
|
112
|
+
# return +nil+, instead of the results to the action code. This may
|
113
|
+
# prove handy when dealing with mixed menus where only the names of
|
114
|
+
# items without any code (and +nil+, of course) will be returned.
|
115
|
+
# Defaults to +false+.
|
116
|
+
#
|
117
|
+
attr_accessor :nil_on_handled
|
118
|
+
|
119
|
+
#
|
120
|
+
# Adds _name_ to the list of available menu items. Menu items will be
|
121
|
+
# displayed in the order they are added.
|
122
|
+
#
|
123
|
+
# An optional _action_ can be associated with this name and if provided,
|
124
|
+
# it will be called if the item is selected. The result of the method
|
125
|
+
# will be returned, unless _nil_on_handled_ is set (when you would get
|
126
|
+
# +nil+ instead). In _shell_ mode, a provided block will be passed the
|
127
|
+
# command chosen and any details that followed the command. Otherwise,
|
128
|
+
# just the command is passed.
|
129
|
+
#
|
130
|
+
def choice( name, &action )
|
131
|
+
@items << [name, action]
|
132
|
+
end
|
133
|
+
|
134
|
+
#
|
135
|
+
# A shortcut for multiple calls to the sister method choice(). <b>Be
|
136
|
+
# warned:</b> An _action_ set here will apply to *all* provided
|
137
|
+
# _names_. This is considered to be a feature, so you can easily
|
138
|
+
# hand-off interface processing to a different chunk of code.
|
139
|
+
#
|
140
|
+
def choices( *names, &action )
|
141
|
+
names.each { |n| choice(n, &action) }
|
142
|
+
end
|
143
|
+
|
144
|
+
#
|
145
|
+
# Sets the indexing style for this Menu object. Indexes are appended to
|
146
|
+
# menu items, when displayed in list form. The available settings are:
|
147
|
+
#
|
148
|
+
# <tt>:number</tt>:: Menu items will be indexed numerically, starting
|
149
|
+
# with 1. This is the default method of indexing.
|
150
|
+
# <tt>:letter</tt>:: Items will be indexed alphabetically, starting
|
151
|
+
# with a.
|
152
|
+
# <tt>:none</tt>:: No index will be appended to menu items.
|
153
|
+
# <i>any String</i>:: Will be used as the literal _index_.
|
154
|
+
#
|
155
|
+
# Setting the _index_ to <tt>:none</tt> a literal String, also adjusts
|
156
|
+
# _index_suffix_ to a single space and _select_by_ to <tt>:none</tt>.
|
157
|
+
# Because of this, you should make a habit of setting the _index_ first.
|
158
|
+
#
|
159
|
+
def index=( style )
|
160
|
+
@index = style
|
161
|
+
|
162
|
+
# Default settings.
|
163
|
+
if @index == :none or @index.is_a?(String)
|
164
|
+
@index_suffix = " "
|
165
|
+
@select_by = :name
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
#
|
170
|
+
# Setting a _layout_ with this method also adjusts some other attributes
|
171
|
+
# of the Menu object, to ideal defaults for the chosen _layout_. To
|
172
|
+
# account for that, you probably want to set a _layout_ first in your
|
173
|
+
# configuration block, if needed.
|
174
|
+
#
|
175
|
+
# Accepted settings for _layout_ are:
|
176
|
+
#
|
177
|
+
# <tt>:list</tt>:: The default _layout_. The _header_ if set
|
178
|
+
# will appear at the top on its own line with
|
179
|
+
# a trailing colon. Then the list of menu
|
180
|
+
# items will follow. Finally, the _prompt_
|
181
|
+
# will be used as the ask()-like question.
|
182
|
+
# <tt>:one_line</tt>:: A shorter _layout_ that fits on one line.
|
183
|
+
# The _header_ comes first followed by a
|
184
|
+
# colon and spaces, then the _prompt_ with menu
|
185
|
+
# items between trailing parenthesis.
|
186
|
+
# <tt>:menu_only</tt>:: Just the menu items, followed up by a likely
|
187
|
+
# short _prompt_.
|
188
|
+
# <i>any ERb String</i>:: Will be taken as the literal _layout_. This
|
189
|
+
# String can access <tt>@header</tt>,
|
190
|
+
# <tt>@menu</tt> and <tt>@prompt</tt>, but is
|
191
|
+
# otherwise evaluated in the typical HighLine
|
192
|
+
# context, to provide access to utilities like
|
193
|
+
# HighLine.list() primarily.
|
194
|
+
#
|
195
|
+
# If set to either <tt>:one_line</tt>, or <tt>:menu_only</tt>, _index_
|
196
|
+
# will default to <tt>:none</tt> and _flow_ will default to
|
197
|
+
# <tt>:inline</tt>.
|
198
|
+
#
|
199
|
+
def layout=( new_layout )
|
200
|
+
@layout = new_layout
|
201
|
+
|
202
|
+
# Default settings.
|
203
|
+
case @layout
|
204
|
+
when :one_line, :menu_only
|
205
|
+
self.index = :none
|
206
|
+
@flow = :inline
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
#
|
211
|
+
# This method returns all possible options for auto-completion, based
|
212
|
+
# on the settings of _index_ and _select_by_.
|
213
|
+
#
|
214
|
+
def options( )
|
215
|
+
by_index = if @index == :letter
|
216
|
+
l_index = "`"
|
217
|
+
@items.map { "#{l_index.succ!}" }
|
218
|
+
else
|
219
|
+
(1 .. @items.size).collect { |s| String(s) }
|
220
|
+
end
|
221
|
+
by_name = @items.collect { |c| c.first }
|
222
|
+
|
223
|
+
case @select_by
|
224
|
+
when :index then
|
225
|
+
by_index
|
226
|
+
when :name
|
227
|
+
by_name
|
228
|
+
else
|
229
|
+
by_index + by_name
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
#
|
234
|
+
# This method processes the auto-completed user selection, based on the
|
235
|
+
# rules for this Menu object. It an action was provided for the
|
236
|
+
# selection, it will be executed as described in Menu.choice().
|
237
|
+
#
|
238
|
+
def select( selection, details = nil )
|
239
|
+
# Find the selected action.
|
240
|
+
name, action = if selection =~ /^\d+$/
|
241
|
+
@items[selection.to_i - 1]
|
242
|
+
else
|
243
|
+
l_index = "`"
|
244
|
+
index = @items.map { "#{l_index.succ!}" }.index(selection)
|
245
|
+
@items.find { |c| c.first == selection } or @items[index]
|
246
|
+
end
|
247
|
+
|
248
|
+
# Run or return it.
|
249
|
+
if not @nil_on_handled and not action.nil?
|
250
|
+
if @shell
|
251
|
+
action.call(name, details)
|
252
|
+
else
|
253
|
+
action.call(name)
|
254
|
+
end
|
255
|
+
elsif action.nil?
|
256
|
+
name
|
257
|
+
else
|
258
|
+
nil
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
#
|
263
|
+
# Allows Menu objects to pass as Arrays, for use with HighLine.list().
|
264
|
+
# This method returns all menu items to be displayed, complete with
|
265
|
+
# indexes.
|
266
|
+
#
|
267
|
+
def to_ary( )
|
268
|
+
case @index
|
269
|
+
when :number
|
270
|
+
@items.map do |c|
|
271
|
+
"#{@items.index(c) + 1}#{@index_suffix}#{c.first}"
|
272
|
+
end
|
273
|
+
when :letter
|
274
|
+
l_index = "`"
|
275
|
+
@items.map { |c| "#{l_index.succ!}#{@index_suffix}#{c.first}" }
|
276
|
+
when :none
|
277
|
+
@items.map { |c| "#{c.first}" }
|
278
|
+
else
|
279
|
+
@items.map { |c| "#{index}#{@index_suffix}#{c.first}" }
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
#
|
284
|
+
# Allows Menu to behave as a String, just like Question. Returns the
|
285
|
+
# _layout_ to be rendered, which is used by HighLine.say().
|
286
|
+
#
|
287
|
+
def to_str( )
|
288
|
+
case @layout
|
289
|
+
when :list
|
290
|
+
'<%= if @header.nil? then '' else "#{@header}:\n" end %>' +
|
291
|
+
"<%= list( @menu, #{@flow.inspect},
|
292
|
+
#{@list_option.inspect} ) %>" +
|
293
|
+
"<%= @prompt %>"
|
294
|
+
when :one_line
|
295
|
+
'<%= if @header.nil? then '' else "#{@header}: " end %>' +
|
296
|
+
"<%= @prompt %>" +
|
297
|
+
"(<%= list( @menu, #{@flow.inspect},
|
298
|
+
#{@list_option.inspect} ) %>)" +
|
299
|
+
"<%= @prompt[/\s*$/] %>"
|
300
|
+
when :menu_only
|
301
|
+
"<%= list( @menu, #{@flow.inspect},
|
302
|
+
#{@list_option.inspect} ) %><%= @prompt %>"
|
303
|
+
else
|
304
|
+
@layout
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
#
|
309
|
+
# This method will update the intelligent responses to account for
|
310
|
+
# Menu specific differences. This overrides the work done by
|
311
|
+
# Question.build_responses().
|
312
|
+
#
|
313
|
+
def update_responses( )
|
314
|
+
append_default unless default.nil?
|
315
|
+
@responses = { :ambiguous_completion =>
|
316
|
+
"Ambiguous choice. " +
|
317
|
+
"Please choose one of #{options.inspect}.",
|
318
|
+
:ask_on_error =>
|
319
|
+
"? ",
|
320
|
+
:invalid_type =>
|
321
|
+
"You must enter a valid #{options}.",
|
322
|
+
:no_completion =>
|
323
|
+
"You must choose one of " +
|
324
|
+
"#{options.inspect}.",
|
325
|
+
:not_in_range =>
|
326
|
+
"Your answer isn't within the expected range " +
|
327
|
+
"(#{expected_range}).",
|
328
|
+
:not_valid =>
|
329
|
+
"Your answer isn't valid (must match " +
|
330
|
+
"#{@validate.inspect})." }.merge(@responses)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
data/lib/highline/question.rb
CHANGED
@@ -16,6 +16,11 @@ class HighLine
|
|
16
16
|
# process is handled according to the users wishes.
|
17
17
|
#
|
18
18
|
class Question
|
19
|
+
# An internal HighLine error. User code does not need to trap this.
|
20
|
+
class NoAutoCompleteMatch < StandardError
|
21
|
+
# do nothing, just creating a unique error type
|
22
|
+
end
|
23
|
+
|
19
24
|
#
|
20
25
|
# Create an instance of HighLine::Question. Expects a _question_ to ask
|
21
26
|
# (can be <tt>""</tt>) and an _answer_type_ to convert the answer to.
|
@@ -44,34 +49,26 @@ class HighLine
|
|
44
49
|
yield self if block_given?
|
45
50
|
|
46
51
|
# finalize responses based on settings
|
47
|
-
|
48
|
-
@responses = { :ambiguous_completion =>
|
49
|
-
"Ambiguous choice. " +
|
50
|
-
"Please choose one of #{@answer_type.inspect}.",
|
51
|
-
:ask_on_error =>
|
52
|
-
"? ",
|
53
|
-
:invalid_type =>
|
54
|
-
"You must enter a valid #{@answer_type}.",
|
55
|
-
:not_in_range =>
|
56
|
-
"Your answer isn't within the expected range " +
|
57
|
-
"(#{expected_range}).",
|
58
|
-
:not_valid =>
|
59
|
-
"Your answer isn't valid (must match " +
|
60
|
-
"#{@validate.inspect})." }.merge(@responses)
|
52
|
+
build_responses
|
61
53
|
end
|
62
54
|
|
63
55
|
# The type that will be used to convert this answer.
|
64
|
-
|
56
|
+
attr_accessor :answer_type
|
65
57
|
#
|
66
58
|
# Can be set to +true+ to use HighLine's cross-platform character reader
|
67
59
|
# instead of fetching an entire line of input. (Note: HighLine's
|
68
60
|
# character reader *ONLY* supports STDIN on Windows and Unix.) Can also
|
69
61
|
# be set to <tt>:getc</tt> to use that method on the input stream.
|
62
|
+
#
|
63
|
+
# *WARNING*: The _echo_ attribute for a question is ignored when using
|
64
|
+
# thw <tt>:getc</tt> method.
|
70
65
|
#
|
71
66
|
attr_accessor :character
|
72
67
|
#
|
73
68
|
# Can be set to +true+ or +false+ to control whether or not input will
|
74
|
-
# be echoed back to the user.
|
69
|
+
# be echoed back to the user. A setting of +true+ will cause echo to
|
70
|
+
# match input, but any other true value will be treated as to String to
|
71
|
+
# echo for each character typed.
|
75
72
|
#
|
76
73
|
# This requires HighLine's character reader. See the _character_
|
77
74
|
# attribute for details.
|
@@ -122,6 +119,9 @@ class HighLine
|
|
122
119
|
# original question.
|
123
120
|
# <tt>:invalid_type</tt>:: The error message shown when a type
|
124
121
|
# conversion fails.
|
122
|
+
# <tt>:no_completion</tt>:: Used to notify the user that their
|
123
|
+
# selection does not have a valid
|
124
|
+
# auto-completion match.
|
125
125
|
# <tt>:not_in_range</tt>:: Used to notify the user that a
|
126
126
|
# provided answer did not satisfy
|
127
127
|
# the range requirement tests.
|
@@ -142,6 +142,36 @@ class HighLine
|
|
142
142
|
end
|
143
143
|
end
|
144
144
|
|
145
|
+
#
|
146
|
+
# Called late in the initialization process to build intelligent
|
147
|
+
# responses based on the details of this Question object.
|
148
|
+
#
|
149
|
+
def build_responses( )
|
150
|
+
### WARNING: This code is quasi-duplicated in ###
|
151
|
+
### Menu.update_responses(). Check their too when ###
|
152
|
+
### making changes! ###
|
153
|
+
append_default unless default.nil?
|
154
|
+
@responses = { :ambiguous_completion =>
|
155
|
+
"Ambiguous choice. " +
|
156
|
+
"Please choose one of #{@answer_type.inspect}.",
|
157
|
+
:ask_on_error =>
|
158
|
+
"? ",
|
159
|
+
:invalid_type =>
|
160
|
+
"You must enter a valid #{@answer_type}.",
|
161
|
+
:no_completion =>
|
162
|
+
"You must choose one of " +
|
163
|
+
"#{@answer_type.inspect}.",
|
164
|
+
:not_in_range =>
|
165
|
+
"Your answer isn't within the expected range " +
|
166
|
+
"(#{expected_range}).",
|
167
|
+
:not_valid =>
|
168
|
+
"Your answer isn't valid (must match " +
|
169
|
+
"#{@validate.inspect})." }.merge(@responses)
|
170
|
+
### WARNING: This code is quasi-duplicated in ###
|
171
|
+
### Menu.update_responses(). Check their too when ###
|
172
|
+
### making changes! ###
|
173
|
+
end
|
174
|
+
|
145
175
|
#
|
146
176
|
# Returns the provided _answer_string_ after changing character case by
|
147
177
|
# the rules of this Question. Valid settings for whitespace are:
|
@@ -205,7 +235,7 @@ class HighLine
|
|
205
235
|
@answer_type.extend(OptionParser::Completion)
|
206
236
|
answer = @answer_type.complete(answer_string)
|
207
237
|
if answer.nil?
|
208
|
-
raise
|
238
|
+
raise NoAutoCompleteMatch
|
209
239
|
end
|
210
240
|
answer.last
|
211
241
|
elsif [Date, DateTime].include?(@answer_type) or
|
@@ -282,7 +312,7 @@ class HighLine
|
|
282
312
|
end
|
283
313
|
|
284
314
|
# Stringifies the question to be asked.
|
285
|
-
def
|
315
|
+
def to_str( )
|
286
316
|
@question
|
287
317
|
end
|
288
318
|
|
data/test/tc_highline.rb
CHANGED
@@ -63,6 +63,21 @@ class TestHighLine < Test::Unit::TestCase
|
|
63
63
|
q.case = :capitalize
|
64
64
|
end
|
65
65
|
assert_equal(languages.last, answer)
|
66
|
+
|
67
|
+
# poor auto-complete error message
|
68
|
+
@input.truncate(@input.rewind)
|
69
|
+
@input << "lisp\nruby\n"
|
70
|
+
@input.rewind
|
71
|
+
@output.truncate(@output.rewind)
|
72
|
+
|
73
|
+
answer = @terminal.ask( "What is your favorite programming language? ",
|
74
|
+
languages ) do |q|
|
75
|
+
q.case = :capitalize
|
76
|
+
end
|
77
|
+
assert_equal(languages.last, answer)
|
78
|
+
assert_equal( "What is your favorite programming language? " +
|
79
|
+
"You must choose one of [:Perl, :Python, :Ruby].\n" +
|
80
|
+
"? ", @output.string )
|
66
81
|
end
|
67
82
|
|
68
83
|
def test_case_changes
|
@@ -83,6 +98,30 @@ class TestHighLine < Test::Unit::TestCase
|
|
83
98
|
end
|
84
99
|
assert_equal("crazy", answer)
|
85
100
|
end
|
101
|
+
|
102
|
+
def test_character_echo
|
103
|
+
@input << "password\r"
|
104
|
+
@input.rewind
|
105
|
+
|
106
|
+
answer = @terminal.ask("Please enter your password: ") do |q|
|
107
|
+
q.echo = "*"
|
108
|
+
end
|
109
|
+
assert_equal("password", answer)
|
110
|
+
assert_equal("Please enter your password: ********\n", @output.string)
|
111
|
+
|
112
|
+
@input.truncate(@input.rewind)
|
113
|
+
@input << "2"
|
114
|
+
@input.rewind
|
115
|
+
@output.truncate(@output.rewind)
|
116
|
+
|
117
|
+
answer = @terminal.ask( "Select an option (1, 2 or 2): ",
|
118
|
+
Integer ) do |q|
|
119
|
+
q.echo = "*"
|
120
|
+
q.character = true
|
121
|
+
end
|
122
|
+
assert_equal(2, answer)
|
123
|
+
assert_equal("Select an option (1, 2 or 2): *\n", @output.string)
|
124
|
+
end
|
86
125
|
|
87
126
|
def test_character_reading
|
88
127
|
# WARNING: This method does NOT cover Unix and Windows savvy testing!
|
@@ -106,19 +145,19 @@ class TestHighLine < Test::Unit::TestCase
|
|
106
145
|
assert_equal( "This should be \e[1m\e[47mbold on white\e[0m!\n",
|
107
146
|
@output.string )
|
108
147
|
|
109
|
-
|
148
|
+
@output.truncate(@output.rewind)
|
110
149
|
|
111
|
-
|
112
|
-
|
150
|
+
@terminal.say("This should be <%= color('cyan', CYAN) %>!")
|
151
|
+
assert_equal("This should be \e[36mcyan\e[0m!\n", @output.string)
|
113
152
|
|
114
|
-
|
153
|
+
@output.truncate(@output.rewind)
|
115
154
|
|
116
|
-
|
155
|
+
@terminal.say( "This should be " +
|
117
156
|
"<%= color('blinking on red', :blink, :on_red) %>!" )
|
118
|
-
|
119
|
-
|
157
|
+
assert_equal( "This should be \e[5m\e[41mblinking on red\e[0m!\n",
|
158
|
+
@output.string )
|
120
159
|
end
|
121
|
-
|
160
|
+
|
122
161
|
def test_confirm
|
123
162
|
@input << "junk.txt\nno\nsave.txt\ny\n"
|
124
163
|
@input.rewind
|
@@ -194,6 +233,70 @@ class TestHighLine < Test::Unit::TestCase
|
|
194
233
|
@output.string )
|
195
234
|
end
|
196
235
|
|
236
|
+
def test_lists
|
237
|
+
digits = %w{Zero One Two Three Four Five Six Seven Eight Nine}
|
238
|
+
|
239
|
+
@terminal.say("<%= list(#{digits.inspect}) %>")
|
240
|
+
assert_equal(digits.map { |d| "#{d}\n" }.join, @output.string)
|
241
|
+
|
242
|
+
@output.truncate(@output.rewind)
|
243
|
+
|
244
|
+
@terminal.say("<%= list(#{digits.inspect}, :inline) %>")
|
245
|
+
assert_equal( digits[0..-2].join(", ") + " or #{digits.last}\n",
|
246
|
+
@output.string )
|
247
|
+
|
248
|
+
@output.truncate(@output.rewind)
|
249
|
+
|
250
|
+
@terminal.say("<%= list(#{digits.inspect}, :inline, ' and ') %>")
|
251
|
+
assert_equal( digits[0..-2].join(", ") + " and #{digits.last}\n",
|
252
|
+
@output.string )
|
253
|
+
|
254
|
+
@output.truncate(@output.rewind)
|
255
|
+
|
256
|
+
@terminal.say("<%= list(#{digits.inspect}, :columns_down, 3) %>")
|
257
|
+
assert_equal( "Zero Four Eight\n" +
|
258
|
+
"One Five Nine \n" +
|
259
|
+
"Two Six \n" +
|
260
|
+
"Three Seven\n",
|
261
|
+
@output.string )
|
262
|
+
|
263
|
+
colums_of_twenty = ["12345678901234567890"] * 5
|
264
|
+
|
265
|
+
@output.truncate(@output.rewind)
|
266
|
+
|
267
|
+
@terminal.say("<%= list(#{colums_of_twenty.inspect}, :columns_down) %>")
|
268
|
+
assert_equal( "12345678901234567890 12345678901234567890 " +
|
269
|
+
"12345678901234567890\n" +
|
270
|
+
"12345678901234567890 12345678901234567890\n",
|
271
|
+
@output.string )
|
272
|
+
|
273
|
+
@output.truncate(@output.rewind)
|
274
|
+
|
275
|
+
@terminal.say("<%= list(#{digits.inspect}, :columns_across, 3) %>")
|
276
|
+
assert_equal( "Zero One Two \n" +
|
277
|
+
"Three Four Five \n" +
|
278
|
+
"Six Seven Eight\n" +
|
279
|
+
"Nine \n",
|
280
|
+
@output.string )
|
281
|
+
|
282
|
+
colums_of_twenty.pop
|
283
|
+
|
284
|
+
@output.truncate(@output.rewind)
|
285
|
+
|
286
|
+
@terminal.say( "<%= list( #{colums_of_twenty.inspect},
|
287
|
+
:columns_across ) %>" )
|
288
|
+
assert_equal( "12345678901234567890 12345678901234567890 " +
|
289
|
+
"12345678901234567890\n" +
|
290
|
+
"12345678901234567890\n",
|
291
|
+
@output.string )
|
292
|
+
end
|
293
|
+
|
294
|
+
def test_mode
|
295
|
+
# *WARNING*: These tests wil only complete is "stty" mode!
|
296
|
+
assert_equal( "stty", HighLine::CHARACTER_MODE,
|
297
|
+
"Tests require \"stty\" mode." )
|
298
|
+
end
|
299
|
+
|
197
300
|
class NameClass
|
198
301
|
def self.parse( string )
|
199
302
|
if string =~ /^\s*(\w+),\s*(\w+)\s+(\w+)\s*$/
|