highline 2.0.0.pre.develop.2 → 2.0.0.pre.develop.4
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/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
|
|