highline 2.0.0.pre.develop.2 → 2.0.0.pre.develop.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog.md +28 -0
- data/README.md +188 -0
- data/Rakefile +0 -11
- data/highline.gemspec +2 -4
- data/lib/highline.rb +170 -73
- data/lib/highline/builtin_styles.rb +18 -2
- data/lib/highline/color_scheme.rb +15 -5
- data/lib/highline/compatibility.rb +6 -1
- data/lib/highline/custom_errors.rb +43 -6
- data/lib/highline/import.rb +10 -3
- data/lib/highline/list.rb +95 -7
- data/lib/highline/list_renderer.rb +36 -17
- data/lib/highline/menu.rb +73 -18
- data/lib/highline/paginator.rb +10 -0
- data/lib/highline/question.rb +98 -41
- data/lib/highline/question/answer_converter.rb +19 -0
- data/lib/highline/question_asker.rb +21 -17
- data/lib/highline/simulate.rb +10 -1
- data/lib/highline/statement.rb +62 -38
- data/lib/highline/string.rb +26 -25
- data/lib/highline/string_extensions.rb +60 -32
- data/lib/highline/style.rb +125 -25
- data/lib/highline/template_renderer.rb +25 -1
- data/lib/highline/terminal.rb +124 -1
- data/lib/highline/terminal/io_console.rb +6 -75
- data/lib/highline/terminal/ncurses.rb +7 -8
- data/lib/highline/terminal/unix_stty.rb +35 -81
- data/lib/highline/version.rb +1 -1
- data/lib/highline/wrapper.rb +9 -0
- data/test/test_highline.rb +1 -1
- metadata +6 -13
- data/INSTALL +0 -59
- data/README.rdoc +0 -77
- data/setup.rb +0 -1360
data/lib/highline/menu.rb
CHANGED
@@ -12,18 +12,26 @@ require "highline/question"
|
|
12
12
|
|
13
13
|
class HighLine
|
14
14
|
#
|
15
|
-
# Menu objects encapsulate all the details of a call to HighLine
|
16
|
-
# Using the accessors and Menu
|
17
|
-
# to HighLine
|
15
|
+
# Menu objects encapsulate all the details of a call to {HighLine#choose HighLine#choose}.
|
16
|
+
# Using the accessors and {Menu#choice} and {Menu#choices}, the block passed
|
17
|
+
# to {HighLine#choose} can detail all aspects of menu display and control.
|
18
18
|
#
|
19
19
|
class Menu < Question
|
20
20
|
#
|
21
21
|
# Create an instance of HighLine::Menu. All customization is done
|
22
|
-
# through the passed block, which should call accessors
|
23
|
-
# choices
|
24
|
-
# Questions, so all that functionality is available
|
25
|
-
# well.
|
26
|
-
#
|
22
|
+
# through the passed block, which should call accessors, {#choice} and
|
23
|
+
# {#choices} as needed to define the Menu. Note that Menus are also
|
24
|
+
# {HighLine::Question Questions}, so all that functionality is available
|
25
|
+
# to the block as well.
|
26
|
+
#
|
27
|
+
# @example Implicit menu creation through HighLine#choose
|
28
|
+
# cli = HighLine.new
|
29
|
+
# answer = cli.choose do |menu|
|
30
|
+
# menu.prompt = "Please choose your favorite programming language? "
|
31
|
+
# menu.choice(:ruby) { say("Good choice!") }
|
32
|
+
# menu.choices(:python, :perl) { say("Not from around here, are you?") }
|
33
|
+
# end
|
34
|
+
|
27
35
|
def initialize( )
|
28
36
|
#
|
29
37
|
# Initialize Question objects with ignored values, we'll
|
@@ -136,6 +144,23 @@ class HighLine
|
|
136
144
|
# the current HighLine context before the action code is called and can
|
137
145
|
# thus be used for adding output and the like.
|
138
146
|
#
|
147
|
+
# @param name [#to_s] menu item title/header/name to be displayed.
|
148
|
+
# @param action [Proc] callback action to be run when the item is selected.
|
149
|
+
# @param help [String] help/hint string to be displayed.
|
150
|
+
# @return [void]
|
151
|
+
# @example (see HighLine::Menu#initialize)
|
152
|
+
# @example Use of help string on menu items
|
153
|
+
# cli = HighLine.new
|
154
|
+
# cli.choose do |menu|
|
155
|
+
# menu.shell = true
|
156
|
+
#
|
157
|
+
# menu.choice(:load, "Load a file.")
|
158
|
+
# menu.choice(:save, "Save data in file.")
|
159
|
+
# menu.choice(:quit, "Exit program.")
|
160
|
+
#
|
161
|
+
# menu.help("rules", "The rules of this system are as follows...")
|
162
|
+
# end
|
163
|
+
|
139
164
|
def choice( name, help = nil, &action )
|
140
165
|
@items << [name, action]
|
141
166
|
|
@@ -144,16 +169,25 @@ class HighLine
|
|
144
169
|
end
|
145
170
|
|
146
171
|
#
|
147
|
-
# A shortcut for multiple calls to the sister method choice
|
172
|
+
# A shortcut for multiple calls to the sister method {#choice}. <b>Be
|
148
173
|
# warned:</b> An _action_ set here will apply to *all* provided
|
149
174
|
# _names_. This is considered to be a feature, so you can easily
|
150
175
|
# hand-off interface processing to a different chunk of code.
|
151
|
-
#
|
176
|
+
# @param names [Array<#to_s>] menu item titles/headers/names to be displayed.
|
177
|
+
# @param action (see #choice)
|
178
|
+
# @return [void]
|
179
|
+
# @example (see HighLine::Menu#initialize)
|
152
180
|
def choices( *names, &action )
|
153
181
|
names.each { |n| choice(n, &action) }
|
154
182
|
end
|
155
183
|
|
156
|
-
# Identical to choice
|
184
|
+
# Identical to {#choice}, but the item will not be listed for the user.
|
185
|
+
# @see #choice
|
186
|
+
# @param name (see #choice)
|
187
|
+
# @param help (see #choice)
|
188
|
+
# @param action (see #choice)
|
189
|
+
# @return (see #choice)
|
190
|
+
|
157
191
|
def hidden( name, help = nil, &action )
|
158
192
|
@hidden_items << [name, action]
|
159
193
|
|
@@ -212,8 +246,12 @@ class HighLine
|
|
212
246
|
|
213
247
|
#
|
214
248
|
# Used to set help for arbitrary topics. Use the topic <tt>"help"</tt>
|
215
|
-
# to override the default message.
|
249
|
+
# to override the default message. Mainly for internal use.
|
216
250
|
#
|
251
|
+
# @param topic [String] the menu item header/title/name to be associated with
|
252
|
+
# a help message.
|
253
|
+
# @param help [String] the help message to be associated with the menu
|
254
|
+
# item/title/name.
|
217
255
|
def help( topic, help )
|
218
256
|
@help[topic] = help
|
219
257
|
end
|
@@ -290,19 +328,22 @@ class HighLine
|
|
290
328
|
#
|
291
329
|
# This method processes the auto-completed user selection, based on the
|
292
330
|
# rules for this Menu object. If an action was provided for the
|
293
|
-
# selection, it will be executed as described in
|
331
|
+
# selection, it will be executed as described in {#choice}.
|
294
332
|
#
|
333
|
+
# @param highline_context [HighLine] a HighLine instance to be used as context.
|
334
|
+
# @param selection [String, Integer] index or title of the selected menu item.
|
335
|
+
# @param details additional parameter to be passed when in shell mode.
|
336
|
+
# @return [nil, Object] if @nil_on_handled is set it returns +nil+,
|
337
|
+
# else it returns the action return value.
|
295
338
|
def select( highline_context, selection, details = nil )
|
296
339
|
# add in any hidden menu commands
|
297
340
|
@items.concat(@hidden_items)
|
298
341
|
|
299
342
|
# Find the selected action.
|
300
|
-
name, action = if selection =~ /^\d+$/
|
301
|
-
|
343
|
+
name, action = if selection =~ /^\d+$/ # is a number?
|
344
|
+
get_item_by_number(selection)
|
302
345
|
else
|
303
|
-
|
304
|
-
index = @items.map { "#{l_index.succ!}" }.index(selection)
|
305
|
-
@items.find { |c| c.first == selection } or @items[index]
|
346
|
+
get_item_by_letter(selection)
|
306
347
|
end
|
307
348
|
|
308
349
|
# Run or return it.
|
@@ -322,6 +363,20 @@ class HighLine
|
|
322
363
|
@items.slice!(@items.size - @hidden_items.size, @hidden_items.size)
|
323
364
|
end
|
324
365
|
|
366
|
+
# Returns the menu item referenced by its index
|
367
|
+
# @param selection [Integer] menu item's index.
|
368
|
+
def get_item_by_number(selection)
|
369
|
+
@items[selection.to_i - 1]
|
370
|
+
end
|
371
|
+
|
372
|
+
# Returns the menu item referenced by its title/header/name.
|
373
|
+
# @param selection [String] menu's title/header/name
|
374
|
+
def get_item_by_letter(selection)
|
375
|
+
l_index = "`" # character before the letter "a"
|
376
|
+
index = @items.map { "#{l_index.succ!}" }.index(selection)
|
377
|
+
@items.find { |c| c.first == selection } or @items[index]
|
378
|
+
end
|
379
|
+
|
325
380
|
#
|
326
381
|
# Allows Menu objects to pass as Arrays, for use with HighLine.list().
|
327
382
|
# This method returns all menu items to be displayed, complete with
|
data/lib/highline/paginator.rb
CHANGED
@@ -1,9 +1,17 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
3
|
class HighLine
|
4
|
+
# Take the task of paginating some piece of text given a HighLine context
|
4
5
|
class Paginator
|
6
|
+
|
7
|
+
# @return [HighLine] HighLine context
|
5
8
|
attr_reader :highline
|
6
9
|
|
10
|
+
# Returns a HighLine::Paginator instance where you can
|
11
|
+
# call {#page_print} on it.
|
12
|
+
# @param highline [HighLine] context
|
13
|
+
# @example
|
14
|
+
# HighLine::Paginator.new(highline).page_print(statement)
|
7
15
|
def initialize(highline)
|
8
16
|
@highline = highline
|
9
17
|
end
|
@@ -16,6 +24,8 @@ class HighLine
|
|
16
24
|
# Note that the final page of _output_ is *not* printed, but returned
|
17
25
|
# instead. This is to support any special handling for the final sequence.
|
18
26
|
#
|
27
|
+
# @param text [String] text to be paginated
|
28
|
+
# @return [String] last line if paging is aborted
|
19
29
|
def page_print(text)
|
20
30
|
return text unless highline.page_at
|
21
31
|
|
data/lib/highline/question.rb
CHANGED
@@ -27,6 +27,10 @@ class HighLine
|
|
27
27
|
# If _template_or_question_ is already a Question object just return it.
|
28
28
|
# If not, build it.
|
29
29
|
#
|
30
|
+
# @param template_or_question [String, Question] what to ask
|
31
|
+
# @param answer_type [Class] to what class to convert the answer
|
32
|
+
# @param details to be passed to Question.new
|
33
|
+
# @return [Question]
|
30
34
|
def self.build(template_or_question, answer_type = nil, &details)
|
31
35
|
if template_or_question.is_a? Question
|
32
36
|
template_or_question
|
@@ -42,26 +46,18 @@ class HighLine
|
|
42
46
|
# Question.convert(). If given, a block is yielded the new Question
|
43
47
|
# object to allow custom initialization.
|
44
48
|
#
|
49
|
+
# @param template [String] any String
|
50
|
+
# @param answer_type [Class] the type the answer will be converted to it.
|
45
51
|
def initialize(template, answer_type)
|
46
52
|
# initialize instance data
|
47
|
-
@template = template.dup
|
53
|
+
@template = String(template).dup
|
48
54
|
@answer_type = answer_type
|
49
55
|
@completion = @answer_type
|
50
56
|
|
51
|
-
@character = nil
|
52
|
-
@limit = nil
|
53
57
|
@echo = true
|
54
|
-
@readline = false
|
55
58
|
@whitespace = :strip
|
56
59
|
@case = nil
|
57
|
-
@default = nil
|
58
|
-
@validate = nil
|
59
|
-
@above = nil
|
60
|
-
@below = nil
|
61
60
|
@in = nil
|
62
|
-
@confirm = nil
|
63
|
-
@gather = false
|
64
|
-
@verify_match = false
|
65
61
|
@first_answer = nil
|
66
62
|
@directory = Pathname.new(File.expand_path(File.dirname($0)))
|
67
63
|
@glob = "*"
|
@@ -231,8 +227,10 @@ class HighLine
|
|
231
227
|
# Returns the provided _answer_string_ or the default answer for this
|
232
228
|
# Question if a default was set and the answer is empty.
|
233
229
|
#
|
230
|
+
# @param answer_string [String]
|
231
|
+
# @return [String] the answer itself or a default message.
|
234
232
|
def answer_or_default(answer_string)
|
235
|
-
return
|
233
|
+
return default if answer_string.empty? && default
|
236
234
|
answer_string
|
237
235
|
end
|
238
236
|
|
@@ -241,16 +239,24 @@ class HighLine
|
|
241
239
|
# responses based on the details of this Question object.
|
242
240
|
# Also used by Menu#update_responses.
|
243
241
|
#
|
242
|
+
# @return [Hash] responses Hash winner (new and old merge).
|
243
|
+
# @param message_source [Class] Array or String for example.
|
244
|
+
# Same as {#answer_type}.
|
245
|
+
# @param new_hash_wins [Boolean] merge precedence (new vs. old).
|
246
|
+
|
244
247
|
def build_responses(message_source = answer_type, new_hash_wins = false)
|
245
248
|
append_default if [::String, Symbol].include? default.class
|
246
249
|
|
247
|
-
old_hash =
|
250
|
+
old_hash = responses
|
248
251
|
|
249
252
|
new_hash = build_responses_new_hash(message_source)
|
250
253
|
|
251
254
|
@responses = new_hash_wins ? old_hash.merge(new_hash) : new_hash.merge(old_hash)
|
252
255
|
end
|
253
256
|
|
257
|
+
# When updating the responses hash, it generates the new one.
|
258
|
+
# @param message_source (see #build_responses)
|
259
|
+
# @return [Hash] responses hash
|
254
260
|
def build_responses_new_hash(message_source)
|
255
261
|
{ :ambiguous_completion => "Ambiguous choice. Please choose one of " +
|
256
262
|
choice_error_str(message_source) + '.',
|
@@ -262,7 +268,7 @@ class HighLine
|
|
262
268
|
"(#{expected_range}).",
|
263
269
|
:mismatch => "Your entries didn't match.",
|
264
270
|
:not_valid => "Your answer isn't valid (must match " +
|
265
|
-
"#{
|
271
|
+
"#{validate.inspect})." }
|
266
272
|
end
|
267
273
|
|
268
274
|
|
@@ -280,6 +286,9 @@ class HighLine
|
|
280
286
|
#
|
281
287
|
# An unrecognized choice (like <tt>:none</tt>) is treated as +nil+.
|
282
288
|
#
|
289
|
+
# @param answer_string [String]
|
290
|
+
# @return [String] upcased, downcased, capitalized
|
291
|
+
# or unchanged answer String.
|
283
292
|
def change_case(answer_string)
|
284
293
|
if [:up, :upcase].include?(@case)
|
285
294
|
answer_string.upcase
|
@@ -325,10 +334,14 @@ class HighLine
|
|
325
334
|
AnswerConverter.new(self).convert
|
326
335
|
end
|
327
336
|
|
337
|
+
# Run {#in_range?} and raise an error if not succesful
|
328
338
|
def check_range
|
329
339
|
raise NotInRangeQuestionError unless in_range?
|
330
340
|
end
|
331
341
|
|
342
|
+
# Try to auto complete answer_string
|
343
|
+
# @param answer_string [String]
|
344
|
+
# @return [String]
|
332
345
|
def choices_complete(answer_string)
|
333
346
|
# cheating, using OptionParser's Completion module
|
334
347
|
choices = selection
|
@@ -342,8 +355,8 @@ class HighLine
|
|
342
355
|
def expected_range
|
343
356
|
expected = [ ]
|
344
357
|
|
345
|
-
expected << "above #{
|
346
|
-
expected << "below #{
|
358
|
+
expected << "above #{above}" if above
|
359
|
+
expected << "below #{below}" if below
|
347
360
|
expected << "included in #{@in.inspect}" if @in
|
348
361
|
|
349
362
|
case expected.size
|
@@ -373,8 +386,8 @@ class HighLine
|
|
373
386
|
# are not checked.
|
374
387
|
#
|
375
388
|
def in_range?
|
376
|
-
(
|
377
|
-
(
|
389
|
+
(!above or answer > above) and
|
390
|
+
(!below or answer < below) and
|
378
391
|
(!@in or @in.include?(answer))
|
379
392
|
end
|
380
393
|
|
@@ -397,23 +410,29 @@ class HighLine
|
|
397
410
|
#
|
398
411
|
# This process is skipped for single character input.
|
399
412
|
#
|
413
|
+
# @param answer_string [String]
|
414
|
+
# @return [String] answer string with whitespaces removed
|
400
415
|
def remove_whitespace(answer_string)
|
401
|
-
if
|
416
|
+
if !whitespace
|
402
417
|
answer_string
|
403
|
-
elsif [:strip, :chomp].include?(
|
404
|
-
answer_string.send(
|
405
|
-
elsif
|
418
|
+
elsif [:strip, :chomp].include?(whitespace)
|
419
|
+
answer_string.send(whitespace)
|
420
|
+
elsif whitespace == :collapse
|
406
421
|
answer_string.gsub(/\s+/, " ")
|
407
|
-
elsif [:strip_and_collapse, :chomp_and_collapse].include?(
|
408
|
-
result = answer_string.send(
|
422
|
+
elsif [:strip_and_collapse, :chomp_and_collapse].include?(whitespace)
|
423
|
+
result = answer_string.send(whitespace.to_s[/^[a-z]+/])
|
409
424
|
result.gsub(/\s+/, " ")
|
410
|
-
elsif
|
425
|
+
elsif whitespace == :remove
|
411
426
|
answer_string.gsub(/\s+/, "")
|
412
427
|
else
|
413
428
|
answer_string
|
414
429
|
end
|
415
430
|
end
|
416
431
|
|
432
|
+
# Convert to String, remove whitespace and change case
|
433
|
+
# when necessary
|
434
|
+
# @param answer_string [String]
|
435
|
+
# @return [String] converted String
|
417
436
|
def format_answer(answer_string)
|
418
437
|
answer_string = String(answer_string)
|
419
438
|
answer_string = remove_whitespace(answer_string)
|
@@ -426,10 +445,10 @@ class HighLine
|
|
426
445
|
# Pathname. Any other time, this method will return an empty Array.
|
427
446
|
#
|
428
447
|
def selection
|
429
|
-
if
|
430
|
-
|
431
|
-
elsif [File, Pathname].include?(
|
432
|
-
Dir[File.join(
|
448
|
+
if completion.is_a?(Array)
|
449
|
+
completion
|
450
|
+
elsif [File, Pathname].include?(completion)
|
451
|
+
Dir[File.join(directory.to_s, glob)].map do |file|
|
433
452
|
File.basename(file)
|
434
453
|
end
|
435
454
|
else
|
@@ -439,7 +458,7 @@ class HighLine
|
|
439
458
|
|
440
459
|
# Stringifies the template to be asked.
|
441
460
|
def to_s
|
442
|
-
|
461
|
+
template
|
443
462
|
end
|
444
463
|
|
445
464
|
#
|
@@ -450,9 +469,9 @@ class HighLine
|
|
450
469
|
# and case handling.
|
451
470
|
#
|
452
471
|
def valid_answer?
|
453
|
-
|
454
|
-
(
|
455
|
-
(
|
472
|
+
!validate or
|
473
|
+
(validate.is_a?(Regexp) and answer =~ validate) or
|
474
|
+
(validate.is_a?(Proc) and validate[answer])
|
456
475
|
end
|
457
476
|
|
458
477
|
#
|
@@ -464,6 +483,9 @@ class HighLine
|
|
464
483
|
#
|
465
484
|
# Raises EOFError if input is exhausted.
|
466
485
|
#
|
486
|
+
# @param highline [HighLine] context
|
487
|
+
# @return [String] a character or line
|
488
|
+
|
467
489
|
def get_response(highline)
|
468
490
|
return first_answer if first_answer?
|
469
491
|
|
@@ -477,10 +499,21 @@ class HighLine
|
|
477
499
|
end
|
478
500
|
end
|
479
501
|
|
502
|
+
# Uses {#get_response} but returns a default answer
|
503
|
+
# using {#answer_or_default} in case no answers was
|
504
|
+
# returned.
|
505
|
+
#
|
506
|
+
# @param highline [HighLine] context
|
507
|
+
# @return [String]
|
508
|
+
|
480
509
|
def get_response_or_default(highline)
|
481
510
|
self.answer = answer_or_default(get_response(highline))
|
482
511
|
end
|
483
512
|
|
513
|
+
# Returns the String to be shown when asking for an answer confirmation.
|
514
|
+
# @param highline [HighLine] context
|
515
|
+
# @return [String] default "Are you sure?" if {#confirm} is +true+
|
516
|
+
# @return [String] {#confirm} rendered as a template if it is a String
|
484
517
|
def confirm_question(highline)
|
485
518
|
if confirm == true
|
486
519
|
"Are you sure? "
|
@@ -493,6 +526,10 @@ class HighLine
|
|
493
526
|
end
|
494
527
|
end
|
495
528
|
|
529
|
+
# Provides the String to be asked when at an error situation.
|
530
|
+
# It may be just the question itself (repeat on error).
|
531
|
+
# @return [self] if :ask_on_error on responses Hash is set to :question
|
532
|
+
# @return [String] if :ask_on_error on responses Hash is set to something else
|
496
533
|
def ask_on_error_msg
|
497
534
|
if responses[:ask_on_error] == :question
|
498
535
|
self
|
@@ -506,8 +543,28 @@ class HighLine
|
|
506
543
|
# the prompt will not be issued. And we have to account for that now.
|
507
544
|
# Also, JRuby-1.7's ConsoleReader.readLine() needs to be passed the prompt
|
508
545
|
# to handle line editing properly.
|
546
|
+
# @param highline [HighLine] context
|
547
|
+
# @return [void]
|
509
548
|
def show_question(highline)
|
510
|
-
highline.say(self) unless (
|
549
|
+
highline.say(self) unless (readline && (echo == true && !limit))
|
550
|
+
end
|
551
|
+
|
552
|
+
# Returns an echo string that is adequate for this Question settings.
|
553
|
+
# @param response [String]
|
554
|
+
# @return [String] the response itself if {#echo} is +true+.
|
555
|
+
# @return [String] echo character if {#echo} is truethy. Mainly a String.
|
556
|
+
# @return [String] empty string if {#echo} is falsy.
|
557
|
+
def get_echo_for_response(response)
|
558
|
+
# actually true, not only truethy value
|
559
|
+
if echo == true
|
560
|
+
response
|
561
|
+
# any truethy value, probably a String
|
562
|
+
elsif !!echo
|
563
|
+
echo
|
564
|
+
# any falsy value, false or nil
|
565
|
+
else
|
566
|
+
""
|
567
|
+
end
|
511
568
|
end
|
512
569
|
|
513
570
|
private
|
@@ -518,14 +575,14 @@ class HighLine
|
|
518
575
|
# not affected.
|
519
576
|
#
|
520
577
|
def append_default
|
521
|
-
if
|
522
|
-
|
523
|
-
elsif
|
524
|
-
|
525
|
-
elsif
|
526
|
-
|
578
|
+
if template =~ /([\t ]+)\Z/
|
579
|
+
template << "|#{default}|#{$1}"
|
580
|
+
elsif template == ""
|
581
|
+
template << "|#{default}| "
|
582
|
+
elsif template[-1, 1] == "\n"
|
583
|
+
template[-2, 0] = " |#{default}|"
|
527
584
|
else
|
528
|
-
|
585
|
+
template << " |#{default}|"
|
529
586
|
end
|
530
587
|
end
|
531
588
|
|