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.
@@ -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.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.
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 and choice() and
23
- # choices() as needed to define the Menu. Note that Menus are also
24
- # Questions, so all that functionality is available to the block as
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(). <b>Be
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(), but the item will not be listed for the user.
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 Menu.choice().
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
- @items[selection.to_i - 1]
343
+ name, action = if selection =~ /^\d+$/ # is a number?
344
+ get_item_by_number(selection)
302
345
  else
303
- l_index = "`"
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
@@ -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
 
@@ -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 @default if answer_string.empty? && @default
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 = @responses
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
- "#{@validate.inspect})." }
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 #{@above}" if @above
346
- expected << "below #{@below}" if @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
- (!@above or answer > @above) and
377
- (!@below or answer < @below) and
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 !@whitespace
416
+ if !whitespace
402
417
  answer_string
403
- elsif [:strip, :chomp].include?(@whitespace)
404
- answer_string.send(@whitespace)
405
- elsif @whitespace == :collapse
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?(@whitespace)
408
- result = answer_string.send(@whitespace.to_s[/^[a-z]+/])
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 @whitespace == :remove
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 @completion.is_a?(Array)
430
- @completion
431
- elsif [File, Pathname].include?(@completion)
432
- Dir[File.join(@directory.to_s, @glob)].map do |file|
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
- @template
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
- !@validate or
454
- (@validate.is_a?(Regexp) and answer =~ @validate) or
455
- (@validate.is_a?(Proc) and @validate[answer])
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 (@readline && (@echo == true && !@limit))
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 @template =~ /([\t ]+)\Z/
522
- @template << "|#{@default}|#{$1}"
523
- elsif @template == ""
524
- @template << "|#{@default}| "
525
- elsif @template[-1, 1] == "\n"
526
- @template[-2, 0] = " |#{@default}|"
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
- @template << " |#{@default}|"
585
+ template << " |#{default}|"
529
586
  end
530
587
  end
531
588