Class HighLine
In: lib/highline.rb
lib/highline/system_extensions.rb
lib/highline/question.rb
lib/highline/color_scheme.rb
lib/highline/menu.rb
Parent: Object
HighLine\n[lib/highline.rb\nlib/highline/color_scheme.rb\nlib/highline/menu.rb\nlib/highline/question.rb\nlib/highline/system_extensions.rb] HighLine::SystemExtensions dot/f_4.png

system_extensions.rb

 Created by James Edward Gray II on 2006-06-14.
 Copyright 2006 Gray Productions. All rights reserved.

 This is Free Software.  See LICENSE and COPYING for details.

Methods

Included Modules

HighLine::SystemExtensions

Classes and Modules

Module HighLine::SystemExtensions
Class HighLine::ColorScheme
Class HighLine::Menu
Class HighLine::Question
Class HighLine::QuestionError
Class HighLine::SampleColorScheme

Constants

VERSION = "1.4.0".freeze   The version of the installed library.
CLEAR = "\e[0m"   Embed in a String to clear all previous ANSI sequences. This MUST be done before the program exits!
RESET = CLEAR   An alias for CLEAR.
ERASE_LINE = "\e[K"   Erase the current line of terminal output.
ERASE_CHAR = "\e[P"   Erase the character under the cursor.
BOLD = "\e[1m"   The start of an ANSI bold sequence.
DARK = "\e[2m"   The start of an ANSI dark sequence. (Terminal support uncommon.)
UNDERLINE = "\e[4m"   The start of an ANSI underline sequence.
UNDERSCORE = UNDERLINE   An alias for UNDERLINE.
BLINK = "\e[5m"   The start of an ANSI blink sequence. (Terminal support uncommon.)
REVERSE = "\e[7m"   The start of an ANSI reverse sequence.
CONCEALED = "\e[8m"   The start of an ANSI concealed sequence. (Terminal support uncommon.)
BLACK = "\e[30m"   Set the terminal‘s foreground ANSI color to black.
RED = "\e[31m"   Set the terminal‘s foreground ANSI color to red.
GREEN = "\e[32m"   Set the terminal‘s foreground ANSI color to green.
YELLOW = "\e[33m"   Set the terminal‘s foreground ANSI color to yellow.
BLUE = "\e[34m"   Set the terminal‘s foreground ANSI color to blue.
MAGENTA = "\e[35m"   Set the terminal‘s foreground ANSI color to magenta.
CYAN = "\e[36m"   Set the terminal‘s foreground ANSI color to cyan.
WHITE = "\e[37m"   Set the terminal‘s foreground ANSI color to white.
ON_BLACK = "\e[40m"   Set the terminal‘s background ANSI color to black.
ON_RED = "\e[41m"   Set the terminal‘s background ANSI color to red.
ON_GREEN = "\e[42m"   Set the terminal‘s background ANSI color to green.
ON_YELLOW = "\e[43m"   Set the terminal‘s background ANSI color to yellow.
ON_BLUE = "\e[44m"   Set the terminal‘s background ANSI color to blue.
ON_MAGENTA = "\e[45m"   Set the terminal‘s background ANSI color to magenta.
ON_CYAN = "\e[46m"   Set the terminal‘s background ANSI color to cyan.
ON_WHITE = "\e[47m"   Set the terminal‘s background ANSI color to white.

Attributes

page_at  [R]  The current row setting for paging output.
wrap_at  [R]  The current column setting for wrapping output.

Public Class methods

Returns the current color scheme.

[Source]

    # File lib/highline.rb, line 74
74:   def self.color_scheme
75:     @@color_scheme
76:   end

Pass ColorScheme to setting to turn set a HighLine color scheme.

[Source]

    # File lib/highline.rb, line 69
69:   def self.color_scheme=( setting )
70:     @@color_scheme = setting
71:   end

Create an instance of HighLine, connected to the streams input and output.

[Source]

     # File lib/highline.rb, line 147
147:   def initialize( input = $stdin, output = $stdout,
148:                   wrap_at = nil, page_at = nil )
149:     @input   = input
150:     @output  = output
151:     
152:     self.wrap_at = wrap_at
153:     self.page_at = page_at
154:     
155:     @question = nil
156:     @answer   = nil
157:     @menu     = nil
158:     @header   = nil
159:     @prompt   = nil
160:     @gather   = nil
161:     @answers  = nil
162:     @key      = nil
163:   end

Pass false to setting to turn off HighLine‘s EOF tracking.

[Source]

    # File lib/highline.rb, line 56
56:   def self.track_eof=( setting )
57:     @@track_eof = setting
58:   end

Returns true if HighLine is currently tracking EOF for input.

[Source]

    # File lib/highline.rb, line 61
61:   def self.track_eof?
62:     @@track_eof
63:   end

Pass false to setting to turn off HighLine‘s color escapes.

[Source]

    # File lib/highline.rb, line 43
43:   def self.use_color=( setting )
44:     @@use_color = setting
45:   end

Returns true if HighLine is currently using color escapes.

[Source]

    # File lib/highline.rb, line 48
48:   def self.use_color?
49:     @@use_color
50:   end

Returns true if HighLine is currently using a color scheme.

[Source]

    # File lib/highline.rb, line 79
79:   def self.using_color_scheme?
80:     not @@color_scheme.nil?
81:   end

Public Instance methods

A shortcut to HighLine.ask() a question that only accepts "yes" or "no" answers ("y" and "n" are allowed) and returns true or false (true for "yes"). If provided a true value, character will cause HighLine to fetch a single character response.

Raises EOFError if input is exhausted.

[Source]

     # File lib/highline.rb, line 180
180:   def agree( yes_or_no_question, character = nil )
181:     ask(yes_or_no_question, lambda { |yn| yn.downcase[0] == ?y}) do |q|
182:       q.validate                 = /\Ay(?:es)?|no?\Z/i
183:       q.responses[:not_valid]    = 'Please enter "yes" or "no".'
184:       q.responses[:ask_on_error] = :question
185:       q.character                = character
186:     end
187:   end

This method is the primary interface for user input. Just provide a question to ask the user, the answer_type you want returned, and optionally a code block setting up details of how you want the question handled. See HighLine.say() for details on the format of question, and HighLine::Question for more information about answer_type and what‘s valid in the code block.

If @question is set before ask() is called, parameters are ignored and that object (must be a HighLine::Question) is used to drive the process instead.

Raises EOFError if input is exhausted.

[Source]

     # File lib/highline.rb, line 203
203:   def ask( question, answer_type = String, &details ) # :yields: question
204:     @question ||= Question.new(question, answer_type, &details)
205:     
206:     return gather if @question.gather
207:   
208:     # readline() needs to handle it's own output, but readline only supports 
209:     # full line reading.  Therefore if @question.echo is anything but true, 
210:     # the prompt will not be issued. And we have to account for that now.
211:     say(@question) unless (@question.readline and @question.echo == true)
212:     begin
213:       @answer = @question.answer_or_default(get_response)
214:       unless @question.valid_answer?(@answer)
215:         explain_error(:not_valid)
216:         raise QuestionError
217:       end
218:       
219:       @answer = @question.convert(@answer)
220:       
221:       if @question.in_range?(@answer)
222:         if @question.confirm
223:           # need to add a layer of scope to ask a question inside a
224:           # question, without destroying instance data
225:           context_change = self.class.new(@input, @output, @wrap_at, @page_at)
226:           if @question.confirm == true
227:             confirm_question = "Are you sure?  "
228:           else
229:             # evaluate ERb under initial scope, so it will have
230:             # access to @question and @answer
231:             template  = ERB.new(@question.confirm, nil, "%")
232:             confirm_question = template.result(binding)
233:           end
234:           unless context_change.agree(confirm_question)
235:             explain_error(nil)
236:             raise QuestionError
237:           end
238:         end
239:         
240:         @answer
241:       else
242:         explain_error(:not_in_range)
243:         raise QuestionError
244:       end
245:     rescue QuestionError
246:       retry
247:     rescue ArgumentError
248:       explain_error(:invalid_type)
249:       retry
250:     rescue Question::NoAutoCompleteMatch
251:       explain_error(:no_completion)
252:       retry
253:     rescue NameError
254:       raise if $!.is_a?(NoMethodError)
255:       explain_error(:ambiguous_completion)
256:       retry
257:     ensure
258:       @question = nil    # Reset Question object.
259:     end
260:   end

This method is HighLine‘s menu handler. For simple usage, you can just pass all the menu items you wish to display. At that point, choose() will build and display a menu, walk the user through selection, and return their choice amoung the provided items. You might use this in a case statement for quick and dirty menus.

However, choose() is capable of much more. If provided, a block will be passed a HighLine::Menu object to configure. Using this method, you can customize all the details of menu handling from index display, to building a complete shell-like menuing system. See HighLine::Menu for all the methods it responds to.

Raises EOFError if input is exhausted.

[Source]

     # File lib/highline.rb, line 277
277:   def choose( *items, &details )
278:     @menu = @question = Menu.new(&details)
279:     @menu.choices(*items) unless items.empty?
280:     
281:     # Set _answer_type_ so we can double as the Question for ask().
282:     @menu.answer_type = if @menu.shell
283:       lambda do |command|    # shell-style selection
284:         first_word = command.to_s.split.first || ""
285: 
286:         options = @menu.options
287:         options.extend(OptionParser::Completion)
288:         answer = options.complete(first_word)
289: 
290:         if answer.nil?
291:           raise Question::NoAutoCompleteMatch
292:         end
293: 
294:         [answer.last, command.sub(/^\s*#{first_word}\s*/, "")]
295:       end
296:     else
297:       @menu.options          # normal menu selection, by index or name
298:     end
299:     
300:     # Provide hooks for ERb layouts.
301:     @header   = @menu.header
302:     @prompt   = @menu.prompt
303:     
304:     if @menu.shell
305:       selected = ask("Ignored", @menu.answer_type)
306:       @menu.select(self, *selected)
307:     else
308:       selected = ask("Ignored", @menu.answer_type)
309:       @menu.select(self, selected)
310:     end
311:   end

This method provides easy access to ANSI color sequences, without the user needing to remember to CLEAR at the end of each sequence. Just pass the string to color, followed by a list of colors you would like it to be affected by. The colors can be HighLine class constants, or symbols (:blue for BLUE, for example). A CLEAR will automatically be embedded to the end of the returned String.

This method returns the original string unchanged if HighLine::use_color? is false.

[Source]

     # File lib/highline.rb, line 324
324:   def color( string, *colors )
325:     return string unless self.class.use_color?
326:     
327:     colors.map! do |c|
328:       if self.class.using_color_scheme? and self.class.color_scheme.include? c
329:         self.class.color_scheme[c]
330:       elsif c.is_a? Symbol
331:         self.class.const_get(c.to_s.upcase)
332:       else
333:         c
334:       end
335:     end
336:     "#{colors.flatten.join}#{string}#{CLEAR}"
337:   end

This method is a utility for quickly and easily laying out lists. It can be accessed within ERb replacements of any text that will be sent to the user.

The only required parameter is items, which should be the Array of items to list. A specified mode controls how that list is formed and option has different effects, depending on the mode. Recognized modes are:

:columns_across:items will be placed in columns, flowing from left to right. If given, option is the number of columns to be used. When absent, columns will be determined based on wrap_at or a default of 80 characters.
:columns_down:Identical to :columns_across, save flow goes down.
:inline:All items are placed on a single line. The last two items are separated by option or a default of " or ". All other items are separated by ", ".
:rows:The default mode. Each of the items is placed on it‘s own line. The option parameter is ignored in this mode.

Each member of the items Array is passed through ERb and thus can contain their own expansions. Color escape expansions do not contribute to the final field width.

[Source]

     # File lib/highline.rb, line 367
367:   def list( items, mode = :rows, option = nil )
368:     items = items.to_ary.map do |item|
369:       ERB.new(item, nil, "%").result(binding)
370:     end
371:     
372:     case mode
373:     when :inline
374:       option = " or " if option.nil?
375:       
376:       case items.size
377:       when 0
378:         ""
379:       when 1
380:         items.first
381:       when 2
382:         "#{items.first}#{option}#{items.last}"
383:       else
384:         items[0..-2].join(", ") + "#{option}#{items.last}"
385:       end
386:     when :columns_across, :columns_down
387:       max_length = actual_length(
388:         items.max { |a, b| actual_length(a) <=> actual_length(b) }
389:       )
390: 
391:       if option.nil?
392:         limit  = @wrap_at || 80
393:         option = (limit + 2) / (max_length + 2)
394:       end
395: 
396:       items     = items.map do |item|
397:         pad = max_length + (item.length - actual_length(item))
398:         "%-#{pad}s" % item
399:       end
400:       row_count = (items.size / option.to_f).ceil
401:       
402:       if mode == :columns_across
403:         rows = Array.new(row_count) { Array.new }
404:         items.each_with_index do |item, index|
405:           rows[index / option] << item
406:         end
407: 
408:         rows.map { |row| row.join("  ") + "\n" }.join
409:       else
410:         columns = Array.new(option) { Array.new }
411:         items.each_with_index do |item, index|
412:           columns[index / row_count] << item
413:         end
414:       
415:         list = ""
416:         columns.first.size.times do |index|
417:           list << columns.map { |column| column[index] }.
418:                           compact.join("  ") + "\n"
419:         end
420:         list
421:       end
422:     else
423:       items.map { |i| "#{i}\n" }.join
424:     end
425:   end

Returns the number of columns for the console, or a default it they cannot be determined.

[Source]

     # File lib/highline.rb, line 479
479:   def output_cols
480:     return 80 unless @output.tty?
481:     terminal_size.first
482:   rescue
483:     return 80
484:   end

Returns the number of rows for the console, or a default if they cannot be determined.

[Source]

     # File lib/highline.rb, line 490
490:   def output_rows
491:     return 24 unless @output.tty?
492:     terminal_size.last
493:   rescue
494:     return 24
495:   end

Set to an integer value to cause HighLine to page output lines over the indicated line limit. When nil, the default, no paging occurs. If set to :auto, HighLine will attempt to determing the rows available for the @output or use a sensible default.

[Source]

     # File lib/highline.rb, line 471
471:   def page_at=( setting )
472:     @page_at = setting == :auto ? output_rows : setting
473:   end

The basic output method for HighLine objects. If the provided statement ends with a space or tab character, a newline will not be appended (output will be flush()ed). All other cases are passed straight to Kernel.puts().

The statement parameter is processed as an ERb template, supporting embedded Ruby code. The template is evaluated with a binding inside the HighLine instance, providing easy access to the ANSI color constants and the HighLine.color() method.

[Source]

     # File lib/highline.rb, line 437
437:   def say( statement )
438:     statement = statement.to_str
439:     return unless statement.length > 0
440:     
441:     template  = ERB.new(statement, nil, "%")
442:     statement = template.result(binding)
443:     
444:     statement = wrap(statement) unless @wrap_at.nil?
445:     statement = page_print(statement) unless @page_at.nil?
446:     
447:     if statement[-1, 1] == " " or statement[-1, 1] == "\t"
448:       @output.print(statement)
449:       @output.flush  
450:     else
451:       @output.puts(statement)
452:     end
453:   end

Set to an integer value to cause HighLine to wrap output lines at the indicated character limit. When nil, the default, no wrapping occurs. If set to :auto, HighLine will attempt to determing the columns available for the @output or use a sensible default.

[Source]

     # File lib/highline.rb, line 461
461:   def wrap_at=( setting )
462:     @wrap_at = setting == :auto ? output_cols : setting
463:   end

Private Instance methods

Returns the length of the passed string_with_escapes, minus and color sequence escapes.

[Source]

     # File lib/highline.rb, line 741
741:   def actual_length( string_with_escapes )
742:     string_with_escapes.gsub(/\e\[\d{1,2}m/, "").length
743:   end

Ask user if they wish to continue paging output. Allows them to type "q" to cancel the paging process.

[Source]

     # File lib/highline.rb, line 706
706:   def continue_paging?
707:     command = HighLine.new(@input, @output).ask(
708:       "-- press enter/return to continue or q to stop -- "
709:     ) { |q| q.character = true }
710:     command !~ /\A[qQ]\Z/  # Only continue paging if Q was not hit.
711:   end

A helper method for sending the output stream and error and repeat of the question.

[Source]

     # File lib/highline.rb, line 503
503:   def explain_error( error )
504:     say(@question.responses[error]) unless error.nil?
505:     if @question.responses[:ask_on_error] == :question
506:       say(@question)
507:     elsif @question.responses[:ask_on_error]
508:       say(@question.responses[:ask_on_error])
509:     end
510:   end

Collects an Array/Hash full of answers as described in HighLine::Question.gather().

Raises EOFError if input is exhausted.

[Source]

     # File lib/highline.rb, line 518
518:   def gather(  )
519:     @gather           = @question.gather
520:     @answers          = [ ]
521:     original_question = @question
522:     
523:     @question.gather = false
524:     
525:     case @gather
526:     when Integer
527:       @answers << ask(@question)
528:       @gather  -= 1
529: 
530:       original_question.question = ""
531:       until @gather.zero?
532:         @question =  original_question
533:         @answers  << ask(@question)
534:         @gather   -= 1
535:       end
536:     when String, Regexp
537:       @answers << ask(@question)
538: 
539:       original_question.question = ""
540:       until (@gather.is_a?(String) and @answers.last.to_s == @gather) or
541:             (@gather.is_a?(Regexp) and @answers.last.to_s =~ @gather)
542:         @question =  original_question
543:         @answers  << ask(@question)
544:       end
545:       
546:       @answers.pop
547:     when Hash
548:       @answers = { }
549:       @gather.keys.sort.each do |key|
550:         @question     = original_question
551:         @key          = key
552:         @answers[key] = ask(@question)
553:       end
554:     end
555:     
556:     @answers
557:   end

Read a line of input from the input stream and process whitespace as requested by the Question object.

If Question‘s readline property is set, that library will be used to fetch input. WARNING: This ignores the currently set input stream.

Raises EOFError if input is exhausted.

[Source]

     # File lib/highline.rb, line 568
568:   def get_line(  )
569:     if @question.readline
570:       require "readline"    # load only if needed
571: 
572:       # capture say()'s work in a String to feed to readline()
573:       old_output = @output
574:       @output    = StringIO.new
575:       say(@question)
576:       question = @output.string
577:       @output  = old_output
578:       
579:       # prep auto-completion
580:       completions              = @question.selection.abbrev
581:       Readline.completion_proc = lambda { |string| completions[string] }
582:       
583:       # work-around ugly readline() warnings
584:       old_verbose = $VERBOSE
585:       $VERBOSE    = nil
586:       answer      = @question.change_case(
587:                         @question.remove_whitespace(
588:                             Readline.readline(question, true) ) )
589:       $VERBOSE    = old_verbose
590: 
591:       answer
592:     else
593:       raise EOFError, "The input stream is exhausted." if @@track_eof and
594:                                                           @input.eof?
595: 
596:       @question.change_case(@question.remove_whitespace(@input.gets))
597:     end
598:   end

Return a line or character of input, as requested for this question. Character input will be returned as a single character String, not an Integer.

This question‘s first_answer will be returned instead of input, if set.

Raises EOFError if input is exhausted.

[Source]

     # File lib/highline.rb, line 609
609:   def get_response(  )
610:     return @question.first_answer if @question.first_answer?
611:     
612:     if @question.character.nil?
613:       if @question.echo == true and @question.limit.nil?
614:         get_line
615:       else
616:         raw_no_echo_mode if stty = CHARACTER_MODE == "stty"
617:         
618:         line = ""
619:         backspace_limit = 0
620:         begin
621: 
622:           while character = (stty ? @input.getc : get_character(@input))
623:             # honor backspace and delete
624:             if character == 127 or character == 8
625:               line.slice!(-1, 1)
626:               backspace_limit -= 1
627:             else
628:               line << character.chr
629:               backspace_limit = line.size
630:             end
631:             # looking for carriage return (decimal 13) or
632:             # newline (decimal 10) in raw input
633:             break if character == 13 or character == 10 or
634:                      (@question.limit and line.size == @question.limit)
635:             if @question.echo != false
636:               if character == 127 or character == 8 
637:                   # only backspace if we have characters on the line to
638:                   # eliminate, otherwise we'll tromp over the prompt
639:                   if backspace_limit >= 0 then
640:                     @output.print("\b#{ERASE_CHAR}")
641:                   else 
642:                       # do nothing
643:                   end
644:               else
645:                 @output.print(@question.echo)
646:               end
647:               @output.flush
648:             end
649:           end
650:         ensure
651:           restore_mode if stty
652:         end
653:         if @question.overwrite
654:           @output.print("\r#{ERASE_LINE}")
655:           @output.flush
656:         else
657:           say("\n")
658:         end
659:         
660:         @question.change_case(@question.remove_whitespace(line))
661:       end
662:     elsif @question.character == :getc
663:       @question.change_case(@input.getc.chr)
664:     else
665:       response = get_character(@input).chr
666:       if @question.overwrite
667:         @output.print("\r#{ERASE_LINE}")
668:         @output.flush
669:       else
670:         echo = if @question.echo == true
671:           response
672:         elsif @question.echo != false
673:           @question.echo
674:         else
675:           ""
676:         end
677:         say("#{echo}\n")
678:       end
679:       @question.change_case(response)
680:     end
681:   end

Page print a series of at most page_at lines for output. After each page is printed, HighLine will pause until the user presses enter/return then display the next page of data.

Note that the final page of output is not printed, but returned instead. This is to support any special handling for the final sequence.

[Source]

     # File lib/highline.rb, line 691
691:   def page_print( output )
692:     lines = output.scan(/[^\n]*\n?/)
693:     while lines.size > @page_at
694:       @output.puts lines.slice!(0...@page_at).join
695:       @output.puts
696:       # Return last line if user wants to abort paging
697:       return (["...\n"] + lines.slice(-2,1)).join unless continue_paging?
698:     end
699:     return lines.join
700:   end

Wrap a sequence of lines at wrap_at characters per line. Existing newlines will not be affected by this process, but additional newlines may be added.

[Source]

     # File lib/highline.rb, line 718
718:   def wrap( lines )
719:     wrapped = [ ]
720:     lines.each do |line|
721:       while line =~ /([^\n]{#{@wrap_at + 1},})/
722:         search  = $1.dup
723:         replace = $1.dup
724:         if index = replace.rindex(" ", @wrap_at)
725:           replace[index, 1] = "\n"
726:           replace.sub!(/\n[ \t]+/, "\n")
727:           line.sub!(search, replace)
728:         else
729:           line[@wrap_at, 0] = "\n"
730:         end
731:       end
732:       wrapped << line
733:     end
734:     return wrapped.join
735:   end

[Validate]