A HighLine object is a “high-level line oriented” shell over an input and an output stream. HighLine simplifies common console interaction, effectively replacing puts() and gets(). User code can simply specify the question to ask and any details about user interaction, then leave the rest of the work to HighLine. When HighLine.ask() returns, you’ll have the answer you requested, even if HighLine had to ask many times, validate results, perform range checking, convert types, etc.
color_scheme.rb
Created by Jeremy Hinegardner on 2007-01-24 Copyright 2007. All rights reserved
This is Free Software. See LICENSE and COPYING for details
The version of the installed library.
Embed in a String to clear all previous ANSI sequences. This MUST be done before the program exits!
An alias for CLEAR.
Erase the character under the cursor.
The start of an ANSI bold sequence.
The start of an ANSI dark sequence. (Terminal support uncommon.)
The start of an ANSI underline sequence.
An alias for UNDERLINE.
The start of an ANSI reverse sequence.
The start of an ANSI concealed sequence. (Terminal support uncommon.)
Set the terminal’s foreground ANSI color to black.
Set the terminal’s foreground ANSI color to red.
Set the terminal’s foreground ANSI color to green.
Set the terminal’s foreground ANSI color to yellow.
Set the terminal’s foreground ANSI color to blue.
Set the terminal’s foreground ANSI color to magenta.
Set the terminal’s foreground ANSI color to cyan.
Set the terminal’s foreground ANSI color to white.
Set the terminal’s background ANSI color to black.
Set the terminal’s background ANSI color to red.
Set the terminal’s background ANSI color to green.
Set the terminal’s background ANSI color to yellow.
Set the terminal’s background ANSI color to blue.
Set the terminal’s background ANSI color to magenta.
Set the terminal’s background ANSI color to cyan.
Set the terminal’s background ANSI color to white.
Returns the current color scheme.
# 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.
# 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.
# 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.
# 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.
# 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.
# 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.
# 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.
# File lib/highline.rb, line 79 79: def self.using_color_scheme? 80: not @@color_scheme.nil? 81: end
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. A block can be provided to further configure the question as in HighLine.ask()
Raises EOFError if input is exhausted.
# File lib/highline.rb, line 181 181: def agree( yes_or_no_question, character = nil ) 182: ask(yes_or_no_question, lambda { |yn| yn.downcase[0] == yy}) do |q| 183: q.validate = /\Ay(?:es)?|no?\Z/ 184: q.responses[:not_valid] = 'Please enter "yes" or "no".' 185: q.responses[:ask_on_error] = :question 186: q.character = character 187: 188: yield q if block_given? 189: end 190: 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.
# File lib/highline.rb, line 206 206: def ask( question, answer_type = String, &details ) # :yields: question 207: @question ||= Question.new(question, answer_type, &details) 208: 209: return gather if @question.gather 210: 211: # readline() needs to handle it's own output, but readline only supports 212: # full line reading. Therefore if @question.echo is anything but true, 213: # the prompt will not be issued. And we have to account for that now. 214: say(@question) unless (@question.readline and @question.echo == true) 215: begin 216: @answer = @question.answer_or_default(get_response) 217: unless @question.valid_answer?(@answer) 218: explain_error(:not_valid) 219: raise QuestionError 220: end 221: 222: @answer = @question.convert(@answer) 223: 224: if @question.in_range?(@answer) 225: if @question.confirm 226: # need to add a layer of scope to ask a question inside a 227: # question, without destroying instance data 228: context_change = self.class.new(@input, @output, @wrap_at, @page_at) 229: if @question.confirm == true 230: confirm_question = "Are you sure? " 231: else 232: # evaluate ERb under initial scope, so it will have 233: # access to @question and @answer 234: template = ERB.new(@question.confirm, nil, "%") 235: confirm_question = template.result(binding) 236: end 237: unless context_change.agree(confirm_question) 238: explain_error(nil) 239: raise QuestionError 240: end 241: end 242: 243: @answer 244: else 245: explain_error(:not_in_range) 246: raise QuestionError 247: end 248: rescue QuestionError 249: retry 250: rescue ArgumentError, NameError => error 251: raise if error.is_a?(NoMethodError) 252: if error.message =~ /ambiguous/ 253: # the assumption here is that OptionParser::Completion#complete 254: # (used for ambiguity resolution) throws exceptions containing 255: # the word 'ambiguous' whenever resolution fails 256: explain_error(:ambiguous_completion) 257: else 258: explain_error(:invalid_type) 259: end 260: retry 261: rescue Question::NoAutoCompleteMatch 262: explain_error(:no_completion) 263: retry 264: ensure 265: @question = nil # Reset Question object. 266: end 267: 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.
# File lib/highline.rb, line 284 284: def choose( *items, &details ) 285: @menu = @question = Menu.new(&details) 286: @menu.choices(*items) unless items.empty? 287: 288: # Set _answer_type_ so we can double as the Question for ask(). 289: @menu.answer_type = if @menu.shell 290: lambda do |command| # shell-style selection 291: first_word = command.to_s.split.first || "" 292: 293: options = @menu.options 294: options.extend(OptionParser::Completion) 295: answer = options.complete(first_word) 296: 297: if answer.nil? 298: raise Question::NoAutoCompleteMatch 299: end 300: 301: [answer.last, command.sub(/^\s*#{first_word}\s*/, "")] 302: end 303: else 304: @menu.options # normal menu selection, by index or name 305: end 306: 307: # Provide hooks for ERb layouts. 308: @header = @menu.header 309: @prompt = @menu.prompt 310: 311: if @menu.shell 312: selected = ask("Ignored", @menu.answer_type) 313: @menu.select(self, *selected) 314: else 315: selected = ask("Ignored", @menu.answer_type) 316: @menu.select(self, selected) 317: end 318: 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.
# File lib/highline.rb, line 331 331: def color( string, *colors ) 332: return string unless self.class.use_color? 333: 334: colors.map! do |c| 335: if self.class.using_color_scheme? and self.class.color_scheme.include? c 336: self.class.color_scheme[c] 337: elsif c.is_a? Symbol 338: self.class.const_get(c.to_s.upcase) 339: else 340: c 341: end 342: end 343: "#{colors.flatten.join}#{string}#{CLEAR}" 344: 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.
# File lib/highline.rb, line 374 374: def list( items, mode = :rows, option = nil ) 375: items = items.to_ary.map do |item| 376: ERB.new(item, nil, "%").result(binding) 377: end 378: 379: case mode 380: when :inline 381: option = " or " if option.nil? 382: 383: case items.size 384: when 0 385: "" 386: when 1 387: items.first 388: when 2 389: "#{items.first}#{option}#{items.last}" 390: else 391: items[0..2].join(", ") + "#{option}#{items.last}" 392: end 393: when :columns_across, :columns_down 394: max_length = actual_length( 395: items.max { |a, b| actual_length(a) <=> actual_length(b) } 396: ) 397: 398: if option.nil? 399: limit = @wrap_at || 80 400: option = (limit + 2) / (max_length + 2) 401: end 402: 403: items = items.map do |item| 404: pad = max_length + (item.length - actual_length(item)) 405: "%-#{pad}s" % item 406: end 407: row_count = (items.size / option.to_f).ceil 408: 409: if mode == :columns_across 410: rows = Array.new(row_count) { Array.new } 411: items.each_with_index do |item, index| 412: rows[index / option] << item 413: end 414: 415: rows.map { |row| row.join(" ") + "\n" }.join 416: else 417: columns = Array.new(option) { Array.new } 418: items.each_with_index do |item, index| 419: columns[index / row_count] << item 420: end 421: 422: list = "" 423: columns.first.size.times do |index| 424: list << columns.map { |column| column[index] }. 425: compact.join(" ") + "\n" 426: end 427: list 428: end 429: else 430: items.map { |i| "#{i}\n" }.join 431: end 432: end
Returns the number of columns for the console, or a default it they cannot be determined.
# File lib/highline.rb, line 486 486: def output_cols 487: return 80 unless @output.tty? 488: terminal_size.first 489: rescue 490: return 80 491: end
Returns the number of rows for the console, or a default if they cannot be determined.
# File lib/highline.rb, line 497 497: def output_rows 498: return 24 unless @output.tty? 499: terminal_size.last 500: rescue 501: return 24 502: 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.
# File lib/highline.rb, line 478 478: def page_at=( setting ) 479: @page_at = setting == :auto ? output_rows - 2 : setting 480: 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.
# File lib/highline.rb, line 444 444: def say( statement ) 445: statement = statement.to_str 446: return unless statement.length > 0 447: 448: template = ERB.new(statement, nil, "%") 449: statement = template.result(binding) 450: 451: statement = wrap(statement) unless @wrap_at.nil? 452: statement = page_print(statement) unless @page_at.nil? 453: 454: if statement[1, 1] == " " or statement[1, 1] == "\t" 455: @output.print(statement) 456: @output.flush 457: else 458: @output.puts(statement) 459: end 460: 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.
# File lib/highline.rb, line 468 468: def wrap_at=( setting ) 469: @wrap_at = setting == :auto ? output_cols : setting 470: end
Returns the length of the passed string_with_escapes, minus and color sequence escapes.
# File lib/highline.rb, line 753 753: def actual_length( string_with_escapes ) 754: string_with_escapes.gsub(/\e\[\d{1,2}m/, "").length 755: end
Ask user if they wish to continue paging output. Allows them to type “q” to cancel the paging process.
# File lib/highline.rb, line 718 718: def continue_paging? 719: command = HighLine.new(@input, @output).ask( 720: "-- press enter/return to continue or q to stop -- " 721: ) { |q| q.character = true } 722: command !~ /\A[qQ]\Z/ # Only continue paging if Q was not hit. 723: end
A helper method for sending the output stream and error and repeat of the question.
# File lib/highline.rb, line 510 510: def explain_error( error ) 511: say(@question.responses[error]) unless error.nil? 512: if @question.responses[:ask_on_error] == :question 513: say(@question) 514: elsif @question.responses[:ask_on_error] 515: say(@question.responses[:ask_on_error]) 516: end 517: end
Collects an Array/Hash full of answers as described in HighLine::Question.gather().
Raises EOFError if input is exhausted.
# File lib/highline.rb, line 525 525: def gather( ) 526: @gather = @question.gather 527: @answers = [ ] 528: original_question = @question 529: 530: @question.gather = false 531: 532: case @gather 533: when Integer 534: @answers << ask(@question) 535: @gather -= 1 536: 537: original_question.question = "" 538: until @gather.zero? 539: @question = original_question 540: @answers << ask(@question) 541: @gather -= 1 542: end 543: when String, Regexp 544: @answers << ask(@question) 545: 546: original_question.question = "" 547: until (@gather.is_a?(String) and @answers.last.to_s == @gather) or 548: (@gather.is_a?(Regexp) and @answers.last.to_s =~ @gather) 549: @question = original_question 550: @answers << ask(@question) 551: end 552: 553: @answers.pop 554: when Hash 555: @answers = { } 556: @gather.keys.sort.each do |key| 557: @question = original_question 558: @key = key 559: @answers[key] = ask(@question) 560: end 561: end 562: 563: @answers 564: 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.
# File lib/highline.rb, line 575 575: def get_line( ) 576: if @question.readline 577: require "readline" # load only if needed 578: 579: # capture say()'s work in a String to feed to readline() 580: old_output = @output 581: @output = StringIO.new 582: say(@question) 583: question = @output.string 584: @output = old_output 585: 586: # prep auto-completion 587: Readline.completion_proc = lambda do |string| 588: @question.selection.grep(/\A#{Regexp.escape(string)}/) 589: end 590: 591: # work-around ugly readline() warnings 592: old_verbose = $VERBOSE 593: $VERBOSE = nil 594: answer = @question.change_case( 595: @question.remove_whitespace( 596: Readline.readline(question, true) ) ) 597: $VERBOSE = old_verbose 598: 599: answer 600: else 601: raise EOFError, "The input stream is exhausted." if @@track_eof and 602: @input.eof? 603: 604: @question.change_case(@question.remove_whitespace(@input.gets)) 605: end 606: 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.
# File lib/highline.rb, line 617 617: def get_response( ) 618: return @question.first_answer if @question.first_answer? 619: 620: if @question.character.nil? 621: if @question.echo == true and @question.limit.nil? 622: get_line 623: else 624: raw_no_echo_mode if stty = CHARACTER_MODE == "stty" 625: 626: line = "" 627: backspace_limit = 0 628: begin 629: 630: while character = (stty ? @input.getbyte : get_character(@input)) 631: # honor backspace and delete 632: if character == 127 or character == 8 633: line.slice!(1, 1) 634: backspace_limit -= 1 635: else 636: line << character.chr 637: backspace_limit = line.size 638: end 639: # looking for carriage return (decimal 13) or 640: # newline (decimal 10) in raw input 641: break if character == 13 or character == 10 or 642: (@question.limit and line.size == @question.limit) 643: if @question.echo != false 644: if character == 127 or character == 8 645: # only backspace if we have characters on the line to 646: # eliminate, otherwise we'll tromp over the prompt 647: if backspace_limit >= 0 then 648: @output.print("\b#{ERASE_CHAR}") 649: else 650: # do nothing 651: end 652: else 653: if @question.echo == true 654: @output.print(character.chr) 655: else 656: @output.print(@question.echo) 657: end 658: end 659: @output.flush 660: end 661: end 662: ensure 663: restore_mode if stty 664: end 665: if @question.overwrite 666: @output.print("\r#{ERASE_LINE}") 667: @output.flush 668: else 669: say("\n") 670: end 671: 672: @question.change_case(@question.remove_whitespace(line)) 673: end 674: elsif @question.character == :getc 675: @question.change_case(@input.getbyte.chr) 676: else 677: response = get_character(@input).chr 678: if @question.overwrite 679: @output.print("\r#{ERASE_LINE}") 680: @output.flush 681: else 682: echo = if @question.echo == true 683: response 684: elsif @question.echo != false 685: @question.echo 686: else 687: "" 688: end 689: say("#{echo}\n") 690: end 691: @question.change_case(response) 692: end 693: 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.
# File lib/highline.rb, line 703 703: def page_print( output ) 704: lines = output.scan(/[^\n]*\n?/) 705: while lines.size > @page_at 706: @output.puts lines.slice!(0...@page_at).join 707: @output.puts 708: # Return last line if user wants to abort paging 709: return (["...\n"] + lines.slice(2,1)).join unless continue_paging? 710: end 711: return lines.join 712: 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.
# File lib/highline.rb, line 730 730: def wrap( text ) 731: wrapped = [ ] 732: text.each_line do |line| 733: while line =~ /([^\n]{#{@wrap_at + 1},})/ 734: search = $1.dup 735: replace = $1.dup 736: if index = replace.rindex(" ", @wrap_at) 737: replace[index, 1] = "\n" 738: replace.sub!(/\n[ \t]+/, "\n") 739: line.sub!(search, replace) 740: else 741: line[$~.begin(1) + @wrap_at, 0] = "\n" 742: end 743: end 744: wrapped << line 745: end 746: return wrapped.join 747: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.