highline-1.6.20/0000755000004100000410000000000012261230137013414 5ustar www-datawww-datahighline-1.6.20/Rakefile0000644000004100000410000000261612261230137015066 0ustar www-datawww-datarequire "rdoc/task" require "rake/testtask" require "rubygems/package_task" require "rubygems" task :default => [:test] Rake::TestTask.new do |test| test.libs << "test" test.test_files = [ "test/ts_all.rb"] test.verbose = true test.ruby_opts << "-w" end Rake::RDocTask.new do |rdoc| rdoc.rdoc_files.include( "README.rdoc", "INSTALL", "TODO", "CHANGELOG", "AUTHORS", "COPYING", "LICENSE", "lib/" ) rdoc.main = "README.rdoc" rdoc.rdoc_dir = "doc/html" rdoc.title = "HighLine Documentation" end desc "Upload current documentation to Rubyforge" task :upload_docs => [:rdoc] do sh "scp -r doc/html/* " + "bbazzarrakk@rubyforge.org:/var/www/gforge-projects/highline/doc/" sh "scp -r site/* " + "bbazzarrakk@rubyforge.org:/var/www/gforge-projects/highline/" end load(File.join(File.dirname(__FILE__), "highline.gemspec")) Gem::PackageTask.new(SPEC) do |package| # do nothing: I just need a gem but this block is required end desc "Show library's code statistics" task :stats do require 'code_statistics' CodeStatistics.new( ["HighLine", "lib"], ["Functionals", "examples"], ["Units", "test"] ).to_s end desc "Add new files to Subversion" task :add_to_svn do sh %Q{svn status | ruby -nae 'system "svn add \#{$F[1]}" if $F[0] == "?"' } end highline-1.6.20/CHANGELOG0000644000004100000410000003022612261230137014631 0ustar www-datawww-data= Change Log Below is a complete listing of changes for each revision of HighLine. == 1.6.20 * Fixed a bug with FFI::NCurses integration (by agentdave). * Improved StringExtensions performance (by John Leach). * Various JRuby fixes (by presidentbeef). == 1.6.19 * Fixed `terminal_size()` with jline2 (by presidentbeef). == 1.6.18 * Fixed a long supported interface that was accidentally broken with a recent change (by Rubem Nakamura Carneiro). == 1.6.17 * Added encoding support to menus (by David Lyons). * Some minor fixes to SystemExtensions (by whiteleaf and presidentbeef). == 1.6.16 * Added the new indention feature (by davispuh). * Separated auto-completion from the answer type (by davispuh). * Improved JRuby support (by rsutphin). * General code clean up (by stomar). * Made HighLine#say() a little smarter with regard to color escapes (by Kenneth Murphy). == 1.6.15 * Added support for nil arguments in lists (by Eric Saxby). * Fixed HighLine's termios integration (by Jens Wille). == 1.6.14 * Added JRuby 1.7 support (by Mina Nagy). * Take into account color escape sequences when wrapping text (by Mark J. Titorenko). == 1.6.13 * Removed unneeded Shebang lines (by Scott Gonyea). * Protect the String passed to Question.new from modification (by michael). * Added a retype-to-verify setting (by michael). == 1.6.12 * Silenced warnings (by James McEwan). == 1.6.11 * Fixed a bad test. (Fix by Diego Elio Pettenò.) == 1.6.10 * Fixed a regression that prevented asking for String arguments (by Jeffery Sman.) * Fixed a testing incompatibility (by Hans de Graaff.) == 1.6.9 * The new list modes now properly ignore escapes when sizing. * Added a project gemspec file. * Fixed a bug that prevented the use of termios (by tomdz). * Switch to JLine to provide better echo support on JRuby (by tomdz). == 1.6.8 * Fix missing ERASE_CHAR reference (by Aaron Gifford). == 1.6.7 * Fixed bug introduced in 1.6.6 attempted fix (by Aaron Gifford). == 1.6.6 * Fixed old style references causing HighLine::String errors (by Aaron Gifford). == 1.6.5 * HighLine#list() now correctly handles empty lists (fix by Lachlan Dowding). * HighLine#list() now supports :uneven_columns_across and :uneven_columns_down modes. == 1.6.4 * Add introspection methods to color_scheme: definition, keys, to_hash. * Add tests for new methods. == 1.6.3 * Add color NONE. * Add RGB color capability. * Made 'color' available as a class or instance method of HighLine, for instance: HighLine.color("foo", :blue)) or highline_obj.color("foo", :blue) are now both possible and equivalent. * Add HighLine::String class with convenience methods: #color (alias #foreground), #on (alias #background), colors, and styles. See lib/string_extensions.rb. * Add (optional) ability to extend String with the same convenience methods from HighLine::String, using Highline.colorize_strings. == 1.6.2 * Correctly handle STDIN being closed before we receive any data (fix by mleinart). * Try if msvcrt, if we can't load crtdll on Windows (fix by pepijnve). * A fix for nil_on_handled not running the action (reported by Andrew Davey). == 1.6.1 * Fixed raw_no_echo_mode so that it uses stty -icanon rather than cbreak as cbreak does not appear to be the posixly correct argument. It fails on Solaris if cbreak is used. * Fixed an issue that kept Menu from showing the correct choices for disambiguation. * Removed a circular require that kept Ruby 1.9.2 from loading HighLine. * Fixed a bug that caused infinite looping when wrapping text without spaces. * Fixed it so that :auto paging accounts for the two lines it adds. * On JRuby, improved error message about ffi-ncurses. Before 1.5.3, HighLine was silently swallowing error messages when ffi-ncurses gem was installed without ncurses present on the system. * Reverted Aaron Simmons's patch to allow redirecting STDIN on Windows. This is the only way we could find to restore HighLine's character reading to working order. == 1.5.2 * Added support for using the ffi-ncurses gem which is supported in JRuby. * Added gem build instructions. == 1.5.1 * Fixed the long standing echo true bug. (reported by Lauri Tuominen) * Improved Windows API calls to support the redirection of STDIN. (patch by Aaron Simmons) * Updated gem specification to avoid a deprecated call. * Made a minor documentation clarification about character mode support. * Worked around some API changes in Ruby's standard library in Ruby 1.9. (patch by Jake Benilov) == 1.5.0 * Fixed a bug that would prevent Readline from showing all completions. (reported by Yaohan Chen) * Added the ability to pass a block to HighLine#agree(). (patch by Yaohan Chen) == 1.4.0 * Made the code grabbing terminal size a little more cross-platform by adding support for Solaris. (patch by Ronald Braswell and Coey Minear) == 1.2.9 * Additional work on the backspacing issue. (patch by Jeremy Hinegardner) * Fixed Readline prompt bug. (patch by Jeremy Hinegardner) == 1.2.8 * Fixed backspacing past the prompt and interrupting a prompt bugs. (patch by Jeremy Hinegardner) == 1.2.7 * Fixed the stty indent bug. * Fixed the echo backspace bug. * Added HighLine::track_eof=() setting to work are threaded eof?() calls. == 1.2.6 Patch by Jeremy Hinegardner: * Added ColorScheme support. * Added HighLine::Question.overwrite mode. * Various documentation fixes. == 1.2.5 * Really fixed the bug I tried to fix in 1.2.4. == 1.2.4 * Fixed a crash causing bug when using menus, reported by Patrick Hof. == 1.2.3 * Treat Cygwin like a Posix OS, instead of a native Windows environment. == 1.2.2 * Minor documentation corrections. * Applied Thomas Werschleiln's patch to fix termio buffering on Solaris. * Applied Justin Bailey's patch to allow canceling paged output. * Fixed a documentation bug in the description of character case settings. * Added a notice about termios in HighLine::Question#echo. * Finally working around the infamous "fast typing" bug == 1.2.1 * Applied Justin Bailey's fix for the page_print() infinite loop bug. * Made a SystemExtensions module to expose OS level functionality other libraries may want to access. * Publicly exposed the get_character() method, per user requests. * Added terminal_size(), output_cols(), and output_rows() methods. * Added :auto setting for warp_at=() and page_at=(). == 1.2.0 * Improved RubyForge and gem spec project descriptions. * Added basic examples to README. * Added a VERSION constant. * Added support for hidden menu commands. * Added Object.or_ask() when using highline/import. == 1.0.4 * Moved the HighLine project to Subversion. * HighLine's color escapes can now be disabled. * Fixed EOF bug introduced in the last release. * Updated HighLine web page. * Moved to a forked development/stable version numbering. == 1.0.2 * Removed old and broken help tests. * Fixed test case typo found by David A. Black. * Added ERb escapes processing to lists, for coloring list items. Color escapes do not add to list element size. * HighLine now throws EOFError when input is exhausted. == 1.0.1 * Minor bug fix: Moved help initialization to before response building, so help would show up in the default responses. == 1.0.0 * Fixed documentation typo pointed out by Gavin Kistner. * Added gather = ... option to question for fetching entire Arrays or Hashes filled with answers. You can set +gather+ to a count of answers to collect, a String or Regexp matching the end of input, or a Hash where each key can be used in a new question. * Added File support to HighLine.ask(). You can specify a _directory_ and a _glob_ pattern that combine into a list of file choices the user can select from. You can choose to receive the user's answer as an open filehandle or as a Pathname object. * Added Readline support for history and editing. * Added tab completion for menu and file selection selection (requires Readline). * Added an optional character limit for input. * Added a complete help system to HighLine's shell menu creation tools. == 0.6.1 * Removed termios dependancy in gem, to fix Windows' install. == 0.6.0 * Implemented HighLine.choose() for menu handling. * Provided shortcut choose(item1, item2, ...) for simple menus. * Allowed Ruby code to be attached to each menu item, to create a complete menu solution. * Provided for total customization of the menu layout. * Allowed for menu selection by index, name or both. * Added a _shell_ mode to allow menu selection with additional details following the name. * Added a list() utility method that can be invoked just like color(). It can layout Arrays for you in any output in the modes :columns_across, :columns_down, :inline and :rows * Added support for echo = "*" style settings. User code can now choose the echo character this way. * Modified HighLine to user the "termios" library for character input, if available. Will return to old behavior (using "stty"), if "termios" cannot be loaded. * Improved "stty" state restoring code. * Fixed "stty" code to handle interrupt signals. * Improved the default auto-complete error message and exposed this message through the +responses+ interface as :no_completion. == 0.5.0 * Implemented echo = false for HighLine::Question objects, primarily to make fetching passwords trivial. * Fixed an auto-complete bug that could cause a crash when the user gave an answer that didn't complete to any valid choice. * Implemented +case+ for HighLine::Question objects to provide character case conversions on given answers. Can be set to :up, :down, or :capitalize. * Exposed @answer to the response system, to allow response that are aware of incorrect input. * Implemented +confirm+ for HighLine::Question objects to allow for verification for sensitive user choices. If set to +true+, user will have to answer an "Are you sure? " question. Can also be set to the question to confirm with the user. == 0.4.0 * Added @wrap_at and @page_at settings and accessors to HighLine, to control text flow. * Implemented line wrapping with adjustable limit. * Implemented paged printing with adjustable limit. == 0.3.0 * Added support for installing with setup.rb. * All output is now treated as an ERb sequence, allowing Ruby code to be embedded in output strings. * Added support for ANSI color sequences in say(). (And everything else by extension.) * Added whitespace handling for answers. Can be set to :strip, :chomp, :collapse, :strip_and_collapse, :chomp_and_collapse, :remove, or :none. * Exposed question details to ERb completion through @question, to allow for intelligent responses. * Simplified HighLine internals using @question. * Added support for fetching single character input either with getc() or HighLine's own cross-platform terminal input routine. * Improved type conversion to handle user defined classes. == 0.2.0 * Added Unit Tests to cover an already fixed output bug in the future. * Added Rakefile and setup test action (default). * Renamed HighLine::Answer to HighLine::Question to better illustrate its role. * Renamed fetch_line() to get_response() to better define its goal. * Simplified explain_error in terms of the Question object. * Renamed accept?() to in_range?() to better define purpose. * Reworked valid?() into valid_answer?() to better fit Question object. * Reworked @member into @in, to make it easier to remember and switched implementation to include?(). * Added range checks for @above and @below. * Fixed the bug causing ask() to swallow NoMethodErrors. * Rolled ask_on_error() into responses. * Redirected imports to Kernel from Object. * Added support for validate = lambda { ... }. * Added default answer support. * Fixed bug that caused ask() to die with an empty question. * Added complete documentation. * Improve the implemetation of agree() to be the intended "yes" or "no" only question. * Added Rake tasks for documentation and packaging. * Moved project to RubyForge. == 0.1.0 * Initial release as the solution to {Ruby Quiz #29}[http://www.rubyquiz.com/quiz29.html]. highline-1.6.20/examples/0000755000004100000410000000000012261230137015232 5ustar www-datawww-datahighline-1.6.20/examples/page_and_wrap.rb0000644000004100000410000006656712261230137020372 0ustar www-datawww-data#!/usr/bin/env ruby # page_and_wrap.rb # # Created by James Edward Gray II on 2005-05-07. # Copyright 2005 Gray Productions. All rights reserved. require "rubygems" require "highline/import" $terminal.wrap_at = 80 $terminal.page_at = 22 say(<!") if i == 0 say( "This should be " + "<%= color('white on #{c}', :white, :on_#{c}) %>!") else say( "This should be " + "<%= color( '#{colors[i - 1]} on #{c}', :#{colors[i - 1]}, :on_#{c} ) %>!") end end # Using color with constants. say("This should be <%= color('bold', BOLD) %>!") say("This should be <%= color('underlined', UNDERLINE) %>!") # Using constants only. say("This might even <%= BLINK %>blink<%= CLEAR %>!") # It even works with list wrapping. erb_digits = %w{Zero One Two Three Four} + ["<%= color('Five', :blue) %%>"] + %w{Six Seven Eight Nine} say("<%= list(#{erb_digits.inspect}, :columns_down, 3) %>") highline-1.6.20/examples/basic_usage.rb0000644000004100000410000000416012261230137020025 0ustar www-datawww-data#!/usr/bin/env ruby # basic_usage.rb # # Created by James Edward Gray II on 2005-04-28. # Copyright 2005 Gray Productions. All rights reserved. require "rubygems" require "highline/import" require "yaml" contacts = [ ] class NameClass def self.parse( string ) if string =~ /^\s*(\w+),\s*(\w+)\s*$/ self.new($2, $1) else raise ArgumentError, "Invalid name format." end end def initialize(first, last) @first, @last = first, last end attr_reader :first, :last end begin entry = Hash.new # basic output say("Enter a contact:") # basic input entry[:name] = ask("Name? (last, first) ", NameClass) do |q| q.validate = /\A\w+, ?\w+\Z/ end entry[:company] = ask("Company? ") { |q| q.default = "none" } entry[:address] = ask("Address? ") entry[:city] = ask("City? ") entry[:state] = ask("State? ") do |q| q.case = :up q.validate = /\A[A-Z]{2}\Z/ end entry[:zip] = ask("Zip? ") do |q| q.validate = /\A\d{5}(?:-?\d{4})?\Z/ end entry[:phone] = ask( "Phone? ", lambda { |p| p.delete("^0-9"). sub(/\A(\d{3})/, '(\1) '). sub(/(\d{4})\Z/, '-\1') } ) do |q| q.validate = lambda { |p| p.delete("^0-9").length == 10 } q.responses[:not_valid] = "Enter a phone numer with area code." end entry[:age] = ask("Age? ", Integer) { |q| q.in = 0..105 } entry[:birthday] = ask("Birthday? ", Date) entry[:interests] = ask( "Interests? (comma separated list) ", lambda { |str| str.split(/,\s*/) } ) entry[:description] = ask("Enter a description for this contact.") do |q| q.whitespace = :strip_and_collapse end contacts << entry # shortcut for yes and no questions end while agree("Enter another contact? ", true) if agree("Save these contacts? ", true) file_name = ask("Enter a file name: ") do |q| q.validate = /\A\w+\Z/ q.confirm = true end File.open("#{file_name}.yaml", "w") { |file| YAML.dump(contacts, file) } end highline-1.6.20/examples/repeat_entry.rb0000644000004100000410000000074612261230137020267 0ustar www-datawww-data#!/usr/bin/env ruby require "rubygems" require "highline/import" tounge_twister = ask("... try saying that three times fast") do |q| q.gather = 3 q.verify_match = true q.responses[:mismatch] = "Nope, those don't match. Try again." end puts "Ok, you did it." pass = ask("<%= @key %>: ") do |q| q.echo = '*' q.verify_match = true q.gather = {"Enter a password" => '', "Please type it again for verification" => ''} end puts "Your password is now #{pass}!" highline-1.6.20/examples/trapping_eof.rb0000644000004100000410000000063612261230137020241 0ustar www-datawww-data#!/usr/bin/env ruby # trapping_eof.rb # # Created by James Edward Gray II on 2006-02-20. # Copyright 2006 Gray Productions. All rights reserved. require "rubygems" require "highline/import" loop do begin name = ask("What's your name?") break if name == "exit" puts "Hello, #{name}!" rescue EOFError # HighLine throws this if @input.eof? break end end puts "Goodbye, dear friend." exit highline-1.6.20/examples/password.rb0000644000004100000410000000024012261230137017415 0ustar www-datawww-data#!/usr/bin/env ruby require "rubygems" require "highline/import" pass = ask("Enter your password: ") { |q| q.echo = false } puts "Your password is #{pass}!" highline-1.6.20/examples/overwrite.rb0000644000004100000410000000103512261230137017604 0ustar www-datawww-data#!/usr/bin/env ruby # overwrite.rb # # Created by Jeremy Hinegardner on 2007-01-24 # Copyright 2007 Jeremy Hinegardner. All rights reserved require 'rubygems' require 'highline/import' prompt = "here is your password:" ask( "#{prompt} <%= color('mypassword', RED, BOLD) %> (Press Any Key to blank) " ) do |q| q.overwrite = true q.echo = false # overwrite works best when echo is false. q.character = true # if this is set to :getc then overwrite does not work end say("<%= color('Look! blanked out!', GREEN) %>") highline-1.6.20/examples/menus.rb0000644000004100000410000000324612261230137016713 0ustar www-datawww-data#!/usr/bin/env ruby require "rubygems" require "highline/import" # The old way, using ask() and say()... choices = %w{ruby python perl} say("This is the old way using ask() and say()...") say("Please choose your favorite programming language:") say(choices.map { |c| " #{c}\n" }.join) case ask("? ", choices) when "ruby" say("Good choice!") else say("Not from around here, are you?") end # The new and improved choose()... say("\nThis is the new mode (default)...") choose do |menu| menu.prompt = "Please choose your favorite programming language? " menu.choice :ruby do say("Good choice!") end menu.choices(:python, :perl) do say("Not from around here, are you?") end end say("\nThis is letter indexing...") choose do |menu| menu.index = :letter menu.index_suffix = ") " menu.prompt = "Please choose your favorite programming language? " menu.choice :ruby do say("Good choice!") end menu.choices(:python, :perl) do say("Not from around here, are you?") end end say("\nThis is with a different layout...") choose do |menu| menu.layout = :one_line menu.header = "Languages" menu.prompt = "Favorite? " menu.choice :ruby do say("Good choice!") end menu.choices(:python, :perl) do say("Not from around here, are you?") end end say("\nYou can even build shells...") loop do choose do |menu| menu.layout = :menu_only menu.shell = true menu.choice(:load, "Load a file.") do |command, details| say("Loading file with options: #{details}...") end menu.choice(:save, "Save a file.") do |command, details| say("Saving file with options: #{details}...") end menu.choice(:quit, "Exit program.") { exit } end end highline-1.6.20/examples/asking_for_arrays.rb0000644000004100000410000000061612261230137021265 0ustar www-datawww-data#!/usr/bin/env ruby # asking_for_arrays.rb # # Created by James Edward Gray II on 2005-07-05. # Copyright 2005 Gray Productions. All rights reserved. require "rubygems" require "highline/import" require "pp" grades = ask( "Enter test scores (or a blank line to quit):", lambda { |ans| ans =~ /^-?\d+$/ ? Integer(ans) : ans} ) do |q| q.gather = "" end say("Grades:") pp grades highline-1.6.20/examples/get_character.rb0000644000004100000410000000043212261230137020351 0ustar www-datawww-data#!/usr/bin/env ruby require "rubygems" require "highline/import" choices = "ynaq" answer = ask("Your choice [#{choices}]? ") do |q| q.echo = false q.character = true q.validate = /\A[#{choices}]\Z/ end say("Your choice: #{answer}") highline-1.6.20/examples/color_scheme.rb0000644000004100000410000000153512261230137020225 0ustar www-datawww-data#!/usr/bin/env ruby -w # color_scheme.rb # # Created by Jeremy Hinegardner on 2007-01-24 # Copyright 2007 Jeremy Hinegardner. All rights reserved require 'rubygems' require 'highline/import' # Create a color scheme, naming color patterns with symbol names. ft = HighLine::ColorScheme.new do |cs| cs[:headline] = [ :bold, :yellow, :on_black ] cs[:horizontal_line] = [ :bold, :white, :on_blue] cs[:even_row] = [ :green ] cs[:odd_row] = [ :magenta ] end # Assign that color scheme to HighLine... HighLine.color_scheme = ft # ...and use it. say("<%= color('Headline', :headline) %>") say("<%= color('-'*20, :horizontal_line) %>") # Setup a toggle for rows. i = true ("A".."D").each do |row| row_color = i ? :even_row : :odd_row say("<%= color('#{row}', '#{row_color}') %>") i = !i end highline-1.6.20/examples/limit.rb0000644000004100000410000000043312261230137016675 0ustar www-datawww-data#!/usr/bin/env ruby -w # limit.rb # # Created by James Edward Gray II on 2008-11-12. # Copyright 2008 Gray Productions. All rights reserved. require "rubygems" require "highline/import" text = ask("Enter text (max 10 chars): ") { |q| q.limit = 10 } puts "You entered: #{text}!" highline-1.6.20/examples/using_readline.rb0000644000004100000410000000055712261230137020556 0ustar www-datawww-data#!/usr/bin/env ruby # using_readline.rb # # Created by James Edward Gray II on 2005-07-06. # Copyright 2005 Gray Productions. All rights reserved. require "rubygems" require "highline/import" loop do cmd = ask("Enter command: ", %w{save sample load reset quit}) do |q| q.readline = true end say("Executing \"#{cmd}\"...") break if cmd == "quit" end highline-1.6.20/doc/0000755000004100000410000000000012261230137014161 5ustar www-datawww-datahighline-1.6.20/doc/.cvsignore0000644000004100000410000000000512261230137016154 0ustar www-datawww-datahtml highline-1.6.20/highline.gemspec0000644000004100000410000000250212261230137016547 0ustar www-datawww-dataDIR = File.dirname(__FILE__) LIB = File.join(DIR, *%w[lib highline.rb]) GEM_VERSION = open(LIB) { |lib| lib.each { |line| if v = line[/^\s*VERSION\s*=\s*(['"])(\d+\.\d+\.\d+)\1/, 2] break v end } } SPEC = Gem::Specification.new do |spec| spec.name = "highline" spec.version = GEM_VERSION spec.platform = Gem::Platform::RUBY spec.summary = "HighLine is a high-level command-line IO library." spec.files = `git ls-files`.split("\n") spec.test_files = `git ls-files -- test/*.rb`.split("\n") spec.has_rdoc = true spec.extra_rdoc_files = %w[README.rdoc INSTALL TODO CHANGELOG LICENSE] spec.rdoc_options << '--title' << 'HighLine Documentation' << '--main' << 'README' spec.require_path = 'lib' spec.author = "James Edward Gray II" spec.email = "james@graysoftinc.com" spec.rubyforge_project = "highline" spec.homepage = "http://highline.rubyforge.org" spec.license = "Ruby" spec.description = <!") Menus: choose do |menu| menu.prompt = "Please choose your favorite programming language? " menu.choice(:ruby) { say("Good choice!") } menu.choices(:python, :perl) { say("Not from around here, are you?") } end For more examples see the examples/ directory of this project. == Installing See the INSTALL file for instructions. == Questions and/or Comments Feel free to email {James Edward Gray II}[mailto:james@grayproductions.net] or {Gregory Brown}[mailto:gregory.t.brown@gmail.com] with any questions. highline-1.6.20/lib/0000755000004100000410000000000012261230137014162 5ustar www-datawww-datahighline-1.6.20/lib/highline.rb0000755000004100000410000010364712261230137016314 0ustar www-datawww-data# highline.rb # # Created by James Edward Gray II on 2005-04-26. # Copyright 2005 Gray Productions. All rights reserved. # # See HighLine for documentation. # # This is Free Software. See LICENSE and COPYING for details. require "erb" require "optparse" require "stringio" require "abbrev" require "highline/system_extensions" require "highline/question" require "highline/menu" require "highline/color_scheme" require "highline/style" # # 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. # class HighLine # The version of the installed library. VERSION = "1.6.20".freeze # An internal HighLine error. User code does not need to trap this. class QuestionError < StandardError # do nothing, just creating a unique error type end # The setting used to disable color output. @@use_color = true # Pass +false+ to _setting_ to turn off HighLine's color escapes. def self.use_color=( setting ) @@use_color = setting end # Returns true if HighLine is currently using color escapes. def self.use_color? @@use_color end # For checking if the current version of HighLine supports RGB colors # Usage: HighLine.supports_rgb_color? rescue false # rescue for compatibility with older versions # Note: color usage also depends on HighLine.use_color being set def self.supports_rgb_color? true end # The setting used to disable EOF tracking. @@track_eof = true # Pass +false+ to _setting_ to turn off HighLine's EOF tracking. def self.track_eof=( setting ) @@track_eof = setting end # Returns true if HighLine is currently tracking EOF for input. def self.track_eof? @@track_eof end # The setting used to control color schemes. @@color_scheme = nil # Pass ColorScheme to _setting_ to set a HighLine color scheme. def self.color_scheme=( setting ) @@color_scheme = setting end # Returns the current color scheme. def self.color_scheme @@color_scheme end # Returns +true+ if HighLine is currently using a color scheme. def self.using_color_scheme? not @@color_scheme.nil? end # # Embed in a String to clear all previous ANSI sequences. This *MUST* be # done before the program exits! # ERASE_LINE_STYLE = Style.new(:name=>:erase_line, :builtin=>true, :code=>"\e[K") # Erase the current line of terminal output ERASE_CHAR_STYLE = Style.new(:name=>:erase_char, :builtin=>true, :code=>"\e[P") # Erase the character under the cursor. CLEAR_STYLE = Style.new(:name=>:clear, :builtin=>true, :code=>"\e[0m") # Clear color settings RESET_STYLE = Style.new(:name=>:reset, :builtin=>true, :code=>"\e[0m") # Alias for CLEAR. BOLD_STYLE = Style.new(:name=>:bold, :builtin=>true, :code=>"\e[1m") # Bold; Note: bold + a color works as you'd expect, # for example bold black. Bold without a color displays # the system-defined bold color (e.g. red on Mac iTerm) DARK_STYLE = Style.new(:name=>:dark, :builtin=>true, :code=>"\e[2m") # Dark; support uncommon UNDERLINE_STYLE = Style.new(:name=>:underline, :builtin=>true, :code=>"\e[4m") # Underline UNDERSCORE_STYLE = Style.new(:name=>:underscore, :builtin=>true, :code=>"\e[4m") # Alias for UNDERLINE BLINK_STYLE = Style.new(:name=>:blink, :builtin=>true, :code=>"\e[5m") # Blink; support uncommon REVERSE_STYLE = Style.new(:name=>:reverse, :builtin=>true, :code=>"\e[7m") # Reverse foreground and background CONCEALED_STYLE = Style.new(:name=>:concealed, :builtin=>true, :code=>"\e[8m") # Concealed; support uncommon STYLES = %w{CLEAR RESET BOLD DARK UNDERLINE UNDERSCORE BLINK REVERSE CONCEALED} # These RGB colors are approximate; see http://en.wikipedia.org/wiki/ANSI_escape_code BLACK_STYLE = Style.new(:name=>:black, :builtin=>true, :code=>"\e[30m", :rgb=>[ 0, 0, 0]) RED_STYLE = Style.new(:name=>:red, :builtin=>true, :code=>"\e[31m", :rgb=>[128, 0, 0]) GREEN_STYLE = Style.new(:name=>:green, :builtin=>true, :code=>"\e[32m", :rgb=>[ 0,128, 0]) BLUE_STYLE = Style.new(:name=>:blue, :builtin=>true, :code=>"\e[34m", :rgb=>[ 0, 0,128]) YELLOW_STYLE = Style.new(:name=>:yellow, :builtin=>true, :code=>"\e[33m", :rgb=>[128,128, 0]) MAGENTA_STYLE = Style.new(:name=>:magenta, :builtin=>true, :code=>"\e[35m", :rgb=>[128, 0,128]) CYAN_STYLE = Style.new(:name=>:cyan, :builtin=>true, :code=>"\e[36m", :rgb=>[ 0,128,128]) # On Mac OSX Terminal, white is actually gray WHITE_STYLE = Style.new(:name=>:white, :builtin=>true, :code=>"\e[37m", :rgb=>[192,192,192]) # Alias for WHITE, since WHITE is actually a light gray on Macs GRAY_STYLE = Style.new(:name=>:gray, :builtin=>true, :code=>"\e[37m", :rgb=>[192,192,192]) # On Mac OSX Terminal, this is black foreground, or bright white background. # Also used as base for RGB colors, if available NONE_STYLE = Style.new(:name=>:none, :builtin=>true, :code=>"\e[38m", :rgb=>[ 0, 0, 0]) BASIC_COLORS = %w{BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE GRAY NONE} colors = BASIC_COLORS.dup BASIC_COLORS.each do |color| bright_color = "BRIGHT_#{color}" colors << bright_color const_set bright_color+'_STYLE', const_get(color + '_STYLE').bright end COLORS = colors colors.each do |color| const_set color, const_get("#{color}_STYLE").code const_set "ON_#{color}_STYLE", const_get("#{color}_STYLE").on const_set "ON_#{color}", const_get("ON_#{color}_STYLE").code end ON_NONE_STYLE.rgb = [255,255,255] # Override; white background STYLES.each do |style| const_set style, const_get("#{style}_STYLE").code end # For RGB colors: def self.const_missing(name) if name.to_s =~ /^(ON_)?(RGB_)([A-F0-9]{6})(_STYLE)?$/ # RGB color on = $1 suffix = $4 if suffix code_name = $1.to_s + $2 + $3 else code_name = name.to_s end style_name = code_name + '_STYLE' style = Style.rgb($3) style = style.on if on const_set(style_name, style) const_set(code_name, style.code) if suffix style else style.code end else raise NameError, "Bad color or uninitialized constant #{name}" end end # # Create an instance of HighLine, connected to the streams _input_ # and _output_. # def initialize( input = $stdin, output = $stdout, wrap_at = nil, page_at = nil, indent_size=3, indent_level=0 ) @input = input @output = output @multi_indent = true @indent_size = indent_size @indent_level = indent_level self.wrap_at = wrap_at self.page_at = page_at @question = nil @answer = nil @menu = nil @header = nil @prompt = nil @gather = nil @answers = nil @key = nil initialize_system_extensions if respond_to?(:initialize_system_extensions) end include HighLine::SystemExtensions # The current column setting for wrapping output. attr_reader :wrap_at # The current row setting for paging output. attr_reader :page_at # Indentation over multiple lines attr_accessor :multi_indent # The indentation size attr_accessor :indent_size # The indentation level attr_accessor :indent_level # # 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. # def agree( yes_or_no_question, character = nil ) ask(yes_or_no_question, lambda { |yn| yn.downcase[0] == ?y}) do |q| q.validate = /\Ay(?:es)?|no?\Z/i q.responses[:not_valid] = 'Please enter "yes" or "no".' q.responses[:ask_on_error] = :question q.character = character yield q if block_given? end 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. # def ask( question, answer_type = String, &details ) # :yields: question @question ||= Question.new(question, answer_type, &details) return gather if @question.gather # readline() needs to handle its own output, but readline only supports # full line reading. Therefore if @question.echo is anything but true, # the prompt will not be issued. And we have to account for that now. # Also, JRuby-1.7's ConsoleReader.readLine() needs to be passed the prompt # to handle line editing properly. say(@question) unless ((JRUBY or @question.readline) and @question.echo == true) begin @answer = @question.answer_or_default(get_response) unless @question.valid_answer?(@answer) explain_error(:not_valid) raise QuestionError end @answer = @question.convert(@answer) if @question.in_range?(@answer) if @question.confirm # need to add a layer of scope to ask a question inside a # question, without destroying instance data context_change = self.class.new(@input, @output, @wrap_at, @page_at, @indent_size, @indent_level) if @question.confirm == true confirm_question = "Are you sure? " else # evaluate ERb under initial scope, so it will have # access to @question and @answer template = ERB.new(@question.confirm, nil, "%") confirm_question = template.result(binding) end unless context_change.agree(confirm_question) explain_error(nil) raise QuestionError end end @answer else explain_error(:not_in_range) raise QuestionError end rescue QuestionError retry rescue ArgumentError, NameError => error raise if error.is_a?(NoMethodError) if error.message =~ /ambiguous/ # the assumption here is that OptionParser::Completion#complete # (used for ambiguity resolution) throws exceptions containing # the word 'ambiguous' whenever resolution fails explain_error(:ambiguous_completion) else explain_error(:invalid_type) end retry rescue Question::NoAutoCompleteMatch explain_error(:no_completion) retry ensure @question = nil # Reset Question object. end 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 among 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. # def choose( *items, &details ) @menu = @question = Menu.new(&details) @menu.choices(*items) unless items.empty? # Set auto-completion @menu.completion = @menu.options # Set _answer_type_ so we can double as the Question for ask(). @menu.answer_type = if @menu.shell lambda do |command| # shell-style selection first_word = command.to_s.split.first || "" options = @menu.options options.extend(OptionParser::Completion) answer = options.complete(first_word) if answer.nil? raise Question::NoAutoCompleteMatch end [answer.last, command.sub(/^\s*#{first_word}\s*/, "")] end else @menu.options # normal menu selection, by index or name end # Provide hooks for ERb layouts. @header = @menu.header @prompt = @menu.prompt if @menu.shell selected = ask("Ignored", @menu.answer_type) @menu.select(self, *selected) else selected = ask("Ignored", @menu.answer_type) @menu.select(self, selected) end 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+. # def self.color( string, *colors ) return string unless self.use_color? Style(*colors).color(string) end # In case you just want the color code, without the embedding and the CLEAR def self.color_code(*colors) Style(*colors).code end # Works as an instance method, same as the class method def color_code(*colors) self.class.color_code(*colors) end # Works as an instance method, same as the class method def color(*args) self.class.color(*args) end # Remove color codes from a string def self.uncolor(string) Style.uncolor(string) end # Works as an instance method, same as the class method def uncolor(string) self.class.uncolor(string) 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. # :uneven_columns_across:: Like :columns_across but each # column is sized independently. # :uneven_columns_down:: Like :columns_down but each # column is sized independently. # :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 its 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. # def list( items, mode = :rows, option = nil ) items = items.to_ary.map do |item| if item.nil? "" else ERB.new(item, nil, "%").result(binding) end end if items.empty? "" else case mode when :inline option = " or " if option.nil? if items.size == 1 items.first else items[0..-2].join(", ") + "#{option}#{items.last}" end when :columns_across, :columns_down max_length = actual_length( items.max { |a, b| actual_length(a) <=> actual_length(b) } ) if option.nil? limit = @wrap_at || 80 option = (limit + 2) / (max_length + 2) end items = items.map do |item| pad = max_length + (item.to_s.length - actual_length(item)) "%-#{pad}s" % item end row_count = (items.size / option.to_f).ceil if mode == :columns_across rows = Array.new(row_count) { Array.new } items.each_with_index do |item, index| rows[index / option] << item end rows.map { |row| row.join(" ") + "\n" }.join else columns = Array.new(option) { Array.new } items.each_with_index do |item, index| columns[index / row_count] << item end list = "" columns.first.size.times do |index| list << columns.map { |column| column[index] }. compact.join(" ") + "\n" end list end when :uneven_columns_across if option.nil? limit = @wrap_at || 80 items.size.downto(1) do |column_count| row_count = (items.size / column_count.to_f).ceil rows = Array.new(row_count) { Array.new } items.each_with_index do |item, index| rows[index / column_count] << item end widths = Array.new(column_count, 0) rows.each do |row| row.each_with_index do |field, column| size = actual_length(field) widths[column] = size if size > widths[column] end end if column_count == 1 or widths.inject(0) { |sum, n| sum + n + 2 } <= limit + 2 return rows.map { |row| row.zip(widths).map { |field, i| "%-#{i + (field.to_s.length - actual_length(field))}s" % field }.join(" ") + "\n" }.join end end else row_count = (items.size / option.to_f).ceil rows = Array.new(row_count) { Array.new } items.each_with_index do |item, index| rows[index / option] << item end widths = Array.new(option, 0) rows.each do |row| row.each_with_index do |field, column| size = actual_length(field) widths[column] = size if size > widths[column] end end return rows.map { |row| row.zip(widths).map { |field, i| "%-#{i + (field.to_s.length - actual_length(field))}s" % field }.join(" ") + "\n" }.join end when :uneven_columns_down if option.nil? limit = @wrap_at || 80 items.size.downto(1) do |column_count| row_count = (items.size / column_count.to_f).ceil columns = Array.new(column_count) { Array.new } items.each_with_index do |item, index| columns[index / row_count] << item end widths = Array.new(column_count, 0) columns.each_with_index do |column, i| column.each do |field| size = actual_length(field) widths[i] = size if size > widths[i] end end if column_count == 1 or widths.inject(0) { |sum, n| sum + n + 2 } <= limit + 2 list = "" columns.first.size.times do |index| list << columns.zip(widths).map { |column, width| field = column[index] "%-#{width + (field.to_s.length - actual_length(field))}s" % field }.compact.join(" ").strip + "\n" end return list end end else row_count = (items.size / option.to_f).ceil columns = Array.new(option) { Array.new } items.each_with_index do |item, index| columns[index / row_count] << item end widths = Array.new(option, 0) columns.each_with_index do |column, i| column.each do |field| size = actual_length(field) widths[i] = size if size > widths[i] end end list = "" columns.first.size.times do |index| list << columns.zip(widths).map { |column, width| field = column[index] "%-#{width + (field.to_s.length - actual_length(field))}s" % field }.compact.join(" ").strip + "\n" end return list end else items.map { |i| "#{i}\n" }.join end end 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. # def say( statement ) statement = format_statement(statement) return unless statement.length > 0 # Don't add a newline if statement ends with whitespace, OR # if statement ends with whitespace before a color escape code. if /[ \t](\e\[\d+(;\d+)*m)?\Z/ =~ statement @output.print(indentation+statement) @output.flush else @output.puts(indentation+statement) end 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 determine the columns # available for the @output or use a sensible default. # def wrap_at=( setting ) @wrap_at = setting == :auto ? output_cols : setting 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 determine the rows available # for the @output or use a sensible default. # def page_at=( setting ) @page_at = setting == :auto ? output_rows - 2 : setting end # # Outputs indentation with current settings # def indentation return ' '*@indent_size*@indent_level end # # Executes block or outputs statement with indentation # def indent(increase=1, statement=nil, multiline=nil) @indent_level += increase multi = @multi_indent @multi_indent = multiline unless multiline.nil? begin if block_given? yield self else say(statement) end rescue @multi_indent = multi @indent_level -= increase raise end @multi_indent = multi @indent_level -= increase end # # Outputs newline # def newline @output.puts end # # Returns the number of columns for the console, or a default it they cannot # be determined. # def output_cols return 80 unless @output.tty? terminal_size.first rescue return 80 end # # Returns the number of rows for the console, or a default if they cannot be # determined. # def output_rows return 24 unless @output.tty? terminal_size.last rescue return 24 end private def format_statement statement statement = statement.dup.to_str return statement unless statement.length > 0 # Allow non-ascii menu prompts in ruby > 1.9.2. ERB eval the menu statement # with the environment's default encoding(usually utf8) statement.force_encoding(Encoding.default_external) if defined?(Encoding) && Encoding.default_external template = ERB.new(statement, nil, "%") statement = template.result(binding) statement = wrap(statement) unless @wrap_at.nil? statement = page_print(statement) unless @page_at.nil? statement = statement.gsub(/\n(?!$)/,"\n#{indentation}") if @multi_indent statement end # # A helper method for sending the output stream and error and repeat # of the question. # def explain_error( error ) say(@question.responses[error]) unless error.nil? if @question.responses[:ask_on_error] == :question say(@question) elsif @question.responses[:ask_on_error] say(@question.responses[:ask_on_error]) end end # # Collects an Array/Hash full of answers as described in # HighLine::Question.gather(). # # Raises EOFError if input is exhausted. # def gather( ) original_question = @question original_question_string = @question.question original_gather = @question.gather verify_match = @question.verify_match @question.gather = false begin # when verify_match is set this loop will repeat until unique_answers == 1 @answers = [ ] @gather = original_gather original_question.question = original_question_string case @gather when Integer @answers << ask(@question) @gather -= 1 original_question.question = "" until @gather.zero? @question = original_question @answers << ask(@question) @gather -= 1 end when ::String, Regexp @answers << ask(@question) original_question.question = "" until (@gather.is_a?(::String) and @answers.last.to_s == @gather) or (@gather.is_a?(Regexp) and @answers.last.to_s =~ @gather) @question = original_question @answers << ask(@question) end @answers.pop when Hash @answers = { } @gather.keys.sort.each do |key| @question = original_question @key = key @answers[key] = ask(@question) end end if verify_match && (unique_answers(@answers).size > 1) @question = original_question explain_error(:mismatch) else verify_match = false end end while verify_match original_question.verify_match ? @answer : @answers end # # A helper method used by HighLine::Question.verify_match # for finding whether a list of answers match or differ # from each other. # def unique_answers(list = @answers) (list.respond_to?(:values) ? list.values : list).uniq 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. # def get_line( ) if @question.readline require "readline" # load only if needed # capture say()'s work in a String to feed to readline() old_output = @output @output = StringIO.new say(@question) question = @output.string @output = old_output # prep auto-completion Readline.completion_proc = lambda do |string| @question.selection.grep(/\A#{Regexp.escape(string)}/) end # work-around ugly readline() warnings old_verbose = $VERBOSE $VERBOSE = nil raw_answer = Readline.readline(question, true) if raw_answer.nil? if @@track_eof raise EOFError, "The input stream is exhausted." else raw_answer = String.new # Never return nil end end answer = @question.change_case( @question.remove_whitespace(raw_answer)) $VERBOSE = old_verbose answer else if JRUBY statement = format_statement(@question) raw_answer = @java_console.readLine(statement, nil) raise EOFError, "The input stream is exhausted." if raw_answer.nil? and @@track_eof else raise EOFError, "The input stream is exhausted." if @@track_eof and @input.eof? raw_answer = @input.gets end @question.change_case(@question.remove_whitespace(raw_answer)) end 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. # def get_response( ) return @question.first_answer if @question.first_answer? if @question.character.nil? if @question.echo == true and @question.limit.nil? get_line else raw_no_echo_mode line = "" backspace_limit = 0 begin while character = get_character(@input) # honor backspace and delete if character == 127 or character == 8 line.slice!(-1, 1) backspace_limit -= 1 else line << character.chr backspace_limit = line.size end # looking for carriage return (decimal 13) or # newline (decimal 10) in raw input break if character == 13 or character == 10 if @question.echo != false if character == 127 or character == 8 # only backspace if we have characters on the line to # eliminate, otherwise we'll tromp over the prompt if backspace_limit >= 0 then @output.print("\b#{HighLine.Style(:erase_char).code}") else # do nothing end else if @question.echo == true @output.print(character.chr) else @output.print(@question.echo) end end @output.flush end break if @question.limit and line.size == @question.limit end ensure restore_mode end if @question.overwrite @output.print("\r#{HighLine.Style(:erase_line).code}") @output.flush else say("\n") end @question.change_case(@question.remove_whitespace(line)) end else if JRUBY #prompt has not been shown say @question end raw_no_echo_mode begin if @question.character == :getc response = @input.getbyte.chr else response = get_character(@input).chr if @question.overwrite @output.print("\r#{HighLine.Style(:erase_line).code}") @output.flush else echo = if @question.echo == true response elsif @question.echo != false @question.echo else "" end say("#{echo}\n") end end ensure restore_mode end @question.change_case(response) end 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. # def page_print( output ) lines = output.scan(/[^\n]*\n?/) while lines.size > @page_at @output.puts lines.slice!(0...@page_at).join @output.puts # Return last line if user wants to abort paging return (["...\n"] + lines.slice(-2,1)).join unless continue_paging? end return lines.join end # # Ask user if they wish to continue paging output. Allows them to type "q" to # cancel the paging process. # def continue_paging? command = HighLine.new(@input, @output).ask( "-- press enter/return to continue or q to stop -- " ) { |q| q.character = true } command !~ /\A[qQ]\Z/ # Only continue paging if Q was not hit. 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. # def wrap( text ) wrapped = [ ] text.each_line do |line| # take into account color escape sequences when wrapping wrap_at = @wrap_at + (line.length - actual_length(line)) while line =~ /([^\n]{#{wrap_at + 1},})/ search = $1.dup replace = $1.dup if index = replace.rindex(" ", wrap_at) replace[index, 1] = "\n" replace.sub!(/\n[ \t]+/, "\n") line.sub!(search, replace) else line[$~.begin(1) + wrap_at, 0] = "\n" end end wrapped << line end return wrapped.join end # # Returns the length of the passed +string_with_escapes+, minus and color # sequence escapes. # def actual_length( string_with_escapes ) string_with_escapes.to_s.gsub(/\e\[\d{1,2}m/, "").length end end require "highline/string_extensions" highline-1.6.20/lib/highline/0000755000004100000410000000000012261230137015751 5ustar www-datawww-datahighline-1.6.20/lib/highline/string_extensions.rb0000644000004100000410000000714512261230137022072 0ustar www-datawww-data# Extensions for class String # # HighLine::String is a subclass of String with convenience methods added for colorization. # # Available convenience methods include: # * 'color' method e.g. highline_string.color(:bright_blue, :underline) # * colors e.g. highline_string.magenta # * RGB colors e.g. highline_string.rgb_ff6000 # or highline_string.rgb(255,96,0) # * background colors e.g. highline_string.on_magenta # * RGB background colors e.g. highline_string.on_rgb_ff6000 # or highline_string.on_rgb(255,96,0) # * styles e.g. highline_string.underline # # Additionally, convenience methods can be chained, for instance the following are equivalent: # highline_string.bright_blue.blink.underline # highline_string.color(:bright_blue, :blink, :underline) # HighLine.color(highline_string, :bright_blue, :blink, :underline) # # For those less squeamish about possible conflicts, the same convenience methods can be # added to the built-in String class, as follows: # # require 'highline' # Highline.colorize_strings class HighLine def self.String(s) HighLine::String.new(s) end module StringExtensions def self.included(base) HighLine::COLORS.each do |color| color = color.downcase base.class_eval <<-END undef :#{color} if method_defined? :#{color} def #{color} color(:#{color}) end END base.class_eval <<-END undef :on_#{color} if method_defined? :on_#{color} def on_#{color} on(:#{color}) end END HighLine::STYLES.each do |style| style = style.downcase base.class_eval <<-END undef :#{style} if method_defined? :#{style} def #{style} color(:#{style}) end END end end base.class_eval do undef :color if method_defined? :color undef :foreground if method_defined? :foreground def color(*args) self.class.new(HighLine.color(self, *args)) end alias_method :foreground, :color undef :on if method_defined? :on def on(arg) color(('on_' + arg.to_s).to_sym) end undef :uncolor if method_defined? :uncolor def uncolor self.class.new(HighLine.uncolor(self)) end undef :rgb if method_defined? :rgb def rgb(*colors) color_code = colors.map{|color| color.is_a?(Numeric) ? '%02x'%color : color.to_s}.join raise "Bad RGB color #{colors.inspect}" unless color_code =~ /^[a-fA-F0-9]{6}/ color("rgb_#{color_code}".to_sym) end undef :on_rgb if method_defined? :on_rgb def on_rgb(*colors) color_code = colors.map{|color| color.is_a?(Numeric) ? '%02x'%color : color.to_s}.join raise "Bad RGB color #{colors.inspect}" unless color_code =~ /^[a-fA-F0-9]{6}/ color("on_rgb_#{color_code}".to_sym) end # TODO Chain existing method_missing undef :method_missing if method_defined? :method_missing def method_missing(method, *args, &blk) if method.to_s =~ /^(on_)?rgb_([0-9a-fA-F]{6})$/ color(method) else raise NoMethodError, "undefined method `#{method}' for #<#{self.class}:#{'%#x'%self.object_id}>" end end end end end class HighLine::String < ::String include StringExtensions end def self.colorize_strings ::String.send(:include, StringExtensions) end end highline-1.6.20/lib/highline/style.rb0000755000004100000410000001075012261230137017444 0ustar www-datawww-data# color_scheme.rb # # Created by Richard LeBer on 2011-06-27. # Copyright 2011. All rights reserved # # This is Free Software. See LICENSE and COPYING for details class HighLine def self.Style(*args) args = args.compact.flatten if args.size==1 arg = args.first if arg.is_a?(Style) Style.list[arg.name] || Style.index(arg) elsif arg.is_a?(::String) && arg =~ /^\e\[/ # arg is a code if styles = Style.code_index[arg] styles.first else Style.new(:code=>arg) end elsif style = Style.list[arg] style elsif HighLine.color_scheme && HighLine.color_scheme[arg] HighLine.color_scheme[arg] elsif arg.is_a?(Hash) Style.new(arg) elsif arg.to_s.downcase =~ /^rgb_([a-f0-9]{6})$/ Style.rgb($1) elsif arg.to_s.downcase =~ /^on_rgb_([a-f0-9]{6})$/ Style.rgb($1).on else raise NameError, "#{arg.inspect} is not a defined Style" end else name = args Style.list[name] || Style.new(:list=>args) end end class Style def self.index(style) if style.name @@styles ||= {} @@styles[style.name] = style end if !style.list @@code_index ||= {} @@code_index[style.code] ||= [] @@code_index[style.code].reject!{|indexed_style| indexed_style.name == style.name} @@code_index[style.code] << style end style end def self.rgb_hex(*colors) colors.map do |color| color.is_a?(Numeric) ? '%02x'%color : color.to_s end.join end def self.rgb_parts(hex) hex.scan(/../).map{|part| part.to_i(16)} end def self.rgb(*colors) hex = rgb_hex(*colors) name = ('rgb_' + hex).to_sym if style = list[name] style else parts = rgb_parts(hex) new(:name=>name, :code=>"\e[38;5;#{rgb_number(parts)}m", :rgb=>parts) end end def self.rgb_number(*parts) parts = parts.flatten 16 + parts.inject(0) {|kode, part| kode*6 + (part/256.0*6.0).floor} end def self.ansi_rgb_to_hex(ansi_number) raise "Invalid ANSI rgb code #{ansi_number}" unless (16..231).include?(ansi_number) parts = (ansi_number-16).to_s(6).rjust(3,'0').scan(/./).map{|d| (d.to_i*255.0/6.0).ceil} rgb_hex(*parts) end def self.list @@styles ||= {} end def self.code_index @@code_index ||= {} end def self.uncolor(string) string.gsub(/\e\[\d+(;\d+)*m/, '') end attr_reader :name, :list attr_accessor :rgb, :builtin # Single color/styles have :name, :code, :rgb (possibly), :builtin # Compound styles have :name, :list, :builtin def initialize(defn = {}) @definition = defn @name = defn[:name] @code = defn[:code] @rgb = defn[:rgb] @list = defn[:list] @builtin = defn[:builtin] if @rgb hex = self.class.rgb_hex(@rgb) @name ||= 'rgb_' + hex elsif @list @name ||= @list end self.class.index self unless defn[:no_index] end def dup self.class.new(@definition) end def to_hash @definition end def color(string) code + string + HighLine::CLEAR end def code if @list @list.map{|element| HighLine.Style(element).code}.join else @code end end def red @rgb && @rgb[0] end def green @rgb && @rgb[1] end def blue @rgb && @rgb[2] end def variant(new_name, options={}) raise "Cannot create a variant of a style list (#{inspect})" if @list new_code = options[:code] || code if options[:increment] raise "Unexpected code in #{inspect}" unless new_code =~ /^(.*?)(\d+)(.*)/ new_code = $1 + ($2.to_i + options[:increment]).to_s + $3 end new_rgb = options[:rgb] || @rgb self.class.new(self.to_hash.merge(:name=>new_name, :code=>new_code, :rgb=>new_rgb)) end def on new_name = ('on_'+@name.to_s).to_sym self.class.list[new_name] ||= variant(new_name, :increment=>10) end def bright raise "Cannot create a bright variant of a style list (#{inspect})" if @list new_name = ('bright_'+@name.to_s).to_sym if style = self.class.list[new_name] style else new_rgb = @rgb == [0,0,0] ? [128, 128, 128] : @rgb.map {|color| color==0 ? 0 : [color+128,255].min } variant(new_name, :increment=>60, :rgb=>new_rgb) end end end end highline-1.6.20/lib/highline/simulate.rb0000644000004100000410000000234712261230137020127 0ustar www-datawww-data# simulate.rb # # Created by Andy Rossmeissl on 2012-04-29. # Copyright 2005 Gray Productions. All rights reserved. # # This is Free Software. See LICENSE and COPYING for details. # # adapted from https://gist.github.com/194554 class HighLine # Simulates Highline input for use in tests. class Simulate # Creates a simulator with an array of Strings as a script def initialize(strings) @strings = strings end # Simulate StringIO#gets by shifting a string off of the script def gets @strings.shift end # Simulate StringIO#getbyte by shifting a single character off of the next line of the script def getbyte line = gets if line.length > 0 char = line.slice! 0 @strings.unshift line char end end # The simulator handles its own EOF def eof? false end # A wrapper method that temporarily replaces the Highline instance in $terminal with an instance of this object for the duration of the block def self.with(*strings) @input = $terminal.instance_variable_get :@input $terminal.instance_variable_set :@input, new(strings) yield ensure $terminal.instance_variable_set :@input, @input end end end highline-1.6.20/lib/highline/import.rb0000644000004100000410000000231312261230137017607 0ustar www-datawww-data# import.rb # # Created by James Edward Gray II on 2005-04-26. # Copyright 2005 Gray Productions. All rights reserved. # # This is Free Software. See LICENSE and COPYING for details. require "highline" require "forwardable" $terminal = HighLine.new # # require "highline/import" adds shortcut methods to Kernel, making # agree(), ask(), choose() and say() globally available. This is handy for # quick and dirty input and output. These methods use the HighLine object in # the global variable $terminal, which is initialized to used # $stdin and $stdout (you are free to change this). # Otherwise, these methods are identical to their HighLine counterparts, see that # class for detailed explanations. # module Kernel extend Forwardable def_delegators :$terminal, :agree, :ask, :choose, :say end class Object # # Tries this object as a _first_answer_ for a HighLine::Question. See that # attribute for details. # # *Warning*: This Object will be passed to String() before set. # def or_ask( *args, &details ) ask(*args) do |question| question.first_answer = String(self) unless nil? details.call(question) unless details.nil? end end end highline-1.6.20/lib/highline/compatibility.rb0000644000004100000410000000045412261230137021152 0ustar www-datawww-dataunless STDIN.respond_to? :getbyte class IO alias_method :getbyte, :getc end class StringIO alias_method :getbyte, :getc end end unless "".respond_to? :each_line # Not a perfect translation, but sufficient for our needs. class String alias_method :each_line, :each end end highline-1.6.20/lib/highline/color_scheme.rb0000644000004100000410000000747012261230137020750 0ustar www-datawww-data# 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 class HighLine # # ColorScheme objects encapsulate a named set of colors to be used in the # HighLine.colors() method call. For example, by applying a ColorScheme that # has a :warning color then the following could be used: # # colors("This is a warning", :warning) # # A ColorScheme contains named sets of HighLine color constants. # # Example: Instantiating a color scheme, applying it to HighLine, # and using it: # # ft = HighLine::ColorScheme.new do |cs| # cs[:headline] = [ :bold, :yellow, :on_black ] # cs[:horizontal_line] = [ :bold, :white ] # cs[:even_row] = [ :green ] # cs[:odd_row] = [ :magenta ] # end # # HighLine.color_scheme = ft # say("<%= color('Headline', :headline) %>") # say("<%= color('-'*20, :horizontal_line) %>") # i = true # ("A".."D").each do |row| # if i then # say("<%= color('#{row}', :even_row ) %>") # else # say("<%= color('#{row}', :odd_row) %>") # end # i = !i # end # # class ColorScheme # # Create an instance of HighLine::ColorScheme. The customization can # happen as a passed in Hash or via the yielded block. Keys are # converted to :symbols and values are converted to HighLine # constants. # def initialize( h = nil ) @scheme = Hash.new load_from_hash(h) unless h.nil? yield self if block_given? end # Load multiple colors from key/value pairs. def load_from_hash( h ) h.each_pair do |color_tag, constants| self[color_tag] = constants end end # Does this color scheme include the given tag name? def include?( color_tag ) @scheme.keys.include?(to_symbol(color_tag)) end # Allow the scheme to be accessed like a Hash. def []( color_tag ) @scheme[to_symbol(color_tag)] end # Retrieve the original form of the scheme def definition( color_tag ) style = @scheme[to_symbol(color_tag)] style && style.list end # Retrieve the keys in the scheme def keys @scheme.keys end # Allow the scheme to be set like a Hash. def []=( color_tag, constants ) @scheme[to_symbol(color_tag)] = HighLine::Style.new(:name=>color_tag.to_s.downcase.to_sym, :list=>constants, :no_index=>true) end # Retrieve the color scheme hash (in original definition format) def to_hash @scheme.inject({}) { |hsh, pair| key, value = pair; hsh[key] = value.list; hsh } end private # Return a normalized representation of a color name. def to_symbol( t ) t.to_s.downcase end # Return a normalized representation of a color setting. def to_constant( v ) v = v.to_s if v.is_a?(Symbol) if v.is_a?(::String) then HighLine.const_get(v.upcase) else v end end end # A sample ColorScheme. class SampleColorScheme < ColorScheme # # Builds the sample scheme with settings for :critical, # :error, :warning, :notice, :info, # :debug, :row_even, and :row_odd colors. # def initialize( h = nil ) scheme = { :critical => [ :yellow, :on_red ], :error => [ :bold, :red ], :warning => [ :bold, :yellow ], :notice => [ :bold, :magenta ], :info => [ :bold, :cyan ], :debug => [ :bold, :green ], :row_even => [ :cyan ], :row_odd => [ :magenta ] } super(scheme) end end end highline-1.6.20/lib/highline/system_extensions.rb0000755000004100000410000001637212261230137022115 0ustar www-datawww-data# 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. require "highline/compatibility" class HighLine module SystemExtensions JRUBY = defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby' if JRUBY def initialize_system_extensions require 'java' require 'readline' if JRUBY_VERSION =~ /^1.7/ java_import 'jline.console.ConsoleReader' input = @input && @input.to_inputstream output = @output && @output.to_outputstream @java_console = ConsoleReader.new(input, output) @java_console.set_history_enabled(false) @java_console.set_bell_enabled(true) @java_console.set_pagination_enabled(false) @java_terminal = @java_console.getTerminal elsif JRUBY_VERSION =~ /^1.6/ java_import 'java.io.OutputStreamWriter' java_import 'java.nio.channels.Channels' java_import 'jline.ConsoleReader' java_import 'jline.Terminal' @java_input = Channels.newInputStream(@input.to_channel) @java_output = OutputStreamWriter.new(Channels.newOutputStream(@output.to_channel)) @java_terminal = Terminal.getTerminal @java_console = ConsoleReader.new(@java_input, @java_output) @java_console.setUseHistory(false) @java_console.setBellEnabled(true) @java_console.setUsePagination(false) end end end extend self # # This section builds character reading and terminal size functions # to suit the proper platform we're running on. Be warned: Here be # dragons! # if RUBY_PLATFORM =~ /mswin(?!ce)|mingw|bccwin/i begin require "fiddle" module WinAPI include Fiddle Handle = RUBY_VERSION >= "2.0.0" ? Fiddle::Handle : DL::Handle Kernel32 = Handle.new("kernel32") Crt = Handle.new("msvcrt") rescue Handle.new("crtdll") def self._getch @@_m_getch ||= Function.new(Crt["_getch"], [], TYPE_INT) @@_m_getch.call end def self.GetStdHandle(handle_type) @@get_std_handle ||= Function.new(Kernel32["GetStdHandle"], [-TYPE_INT], -TYPE_INT) @@get_std_handle.call(handle_type) end def self.GetConsoleScreenBufferInfo(cons_handle, lp_buffer) @@get_console_screen_buffer_info ||= Function.new(Kernel32["GetConsoleScreenBufferInfo"], [TYPE_LONG, TYPE_VOIDP], TYPE_INT) @@get_console_screen_buffer_info.call(cons_handle, lp_buffer) end end rescue LoadError require "dl/import" module WinAPI extend DL::Importer rescue DL::Importable begin dlload "msvcrt", "kernel32" rescue DL::DLError dlload "crtdll", "kernel32" end extern "unsigned long _getch()" extern "unsigned long GetConsoleScreenBufferInfo(unsigned long, void*)" extern "unsigned long GetStdHandle(unsigned long)" end end CHARACTER_MODE = "Win32API" # For Debugging purposes only. # # Windows savvy getc(). # # *WARNING*: This method ignores input and reads one # character from +STDIN+! # def get_character( input = STDIN ) WinAPI._getch end # We do not define a raw_no_echo_mode for Windows as _getch turns off echo def raw_no_echo_mode end def restore_mode end # A Windows savvy method to fetch the console columns, and rows. def terminal_size format = 'SSSSSssssSS' buf = ([0] * format.size).pack(format) stdout_handle = WinAPI.GetStdHandle(0xFFFFFFF5) WinAPI.GetConsoleScreenBufferInfo(stdout_handle, buf) _, _, _, _, _, left, top, right, bottom, _, _ = buf.unpack(format) return right - left + 1, bottom - top + 1 end else # If we're not on Windows try... begin require "termios" # Unix, first choice termios. CHARACTER_MODE = "termios" # For Debugging purposes only. def raw_no_echo_mode @state = Termios.getattr(@input) new_settings = @state.dup new_settings.c_lflag &= ~(Termios::ECHO | Termios::ICANON) new_settings.c_cc[Termios::VMIN] = 1 Termios.setattr(@input, Termios::TCSANOW, new_settings) end def restore_mode Termios.setattr(@input, Termios::TCSANOW, @state) end rescue LoadError # If our first choice fails, try using JLine if JRUBY # if we are on JRuby. JLine is bundled with JRuby. CHARACTER_MODE = "jline" # For Debugging purposes only. def terminal_size if JRUBY_VERSION =~ /^1.7/ [ @java_terminal.get_width, @java_terminal.get_height ] else [ @java_terminal.getTerminalWidth, @java_terminal.getTerminalHeight ] end end def raw_no_echo_mode @state = @java_console.getEchoCharacter @java_console.setEchoCharacter 0 end def restore_mode @java_console.setEchoCharacter @state end else # If we are not on JRuby, try ncurses begin require 'ffi-ncurses' CHARACTER_MODE = "ncurses" # For Debugging purposes only. def raw_no_echo_mode FFI::NCurses.initscr FFI::NCurses.cbreak end def restore_mode FFI::NCurses.endwin end # # A ncurses savvy method to fetch the console columns, and rows. # def terminal_size size = [80, 40] FFI::NCurses.initscr begin size = FFI::NCurses.getmaxyx(FFI::NCurses.stdscr).reverse ensure FFI::NCurses.endwin end size end rescue LoadError # Finally, if all else fails, use stty # *WARNING*: This requires the external "stty" program! CHARACTER_MODE = "stty" # For Debugging purposes only. def raw_no_echo_mode @state = `stty -g` system "stty raw -echo -icanon isig" end def restore_mode system "stty #{@state}" end end end end # For termios and stty if not method_defined?(:terminal_size) # A Unix savvy method using stty to fetch the console columns, and rows. # ... stty does not work in JRuby def terminal_size if /solaris/ =~ RUBY_PLATFORM and `stty` =~ /\brows = (\d+).*\bcolumns = (\d+)/ [$2, $1].map { |c| x.to_i } else `stty size`.split.map { |x| x.to_i }.reverse end end end end if not method_defined?(:get_character) def get_character( input = STDIN ) input.getbyte end end end end highline-1.6.20/lib/highline/question.rb0000755000004100000410000004421012261230137020151 0ustar www-datawww-data# question.rb # # Created by James Edward Gray II on 2005-04-26. # Copyright 2005 Gray Productions. All rights reserved. # # This is Free Software. See LICENSE and COPYING for details. require "optparse" require "date" require "pathname" class HighLine # # Question objects contain all the details of a single invocation of # HighLine.ask(). The object is initialized by the parameters passed to # HighLine.ask() and then queried to make sure each step of the input # process is handled according to the users wishes. # class Question # An internal HighLine error. User code does not need to trap this. class NoAutoCompleteMatch < StandardError # do nothing, just creating a unique error type end # # Create an instance of HighLine::Question. Expects a _question_ to ask # (can be "") and an _answer_type_ to convert the answer to. # The _answer_type_ parameter must be a type recognized by # Question.convert(). If given, a block is yielded the new Question # object to allow custom initialization. # def initialize( question, answer_type ) # initialize instance data @question = question.dup @answer_type = answer_type @completion = @answer_type @character = nil @limit = nil @echo = true @readline = false @whitespace = :strip @case = nil @default = nil @validate = nil @above = nil @below = nil @in = nil @confirm = nil @gather = false @verify_match = false @first_answer = nil @directory = Pathname.new(File.expand_path(File.dirname($0))) @glob = "*" @responses = Hash.new @overwrite = false # allow block to override settings yield self if block_given? # finalize responses based on settings build_responses end # The ERb template of the question to be asked. attr_accessor :question # The type that will be used to convert this answer. attr_accessor :answer_type # For Auto-completion attr_accessor :completion # # Can be set to +true+ to use HighLine's cross-platform character reader # instead of fetching an entire line of input. (Note: HighLine's character # reader *ONLY* supports STDIN on Windows and Unix.) Can also be set to # :getc to use that method on the input stream. # # *WARNING*: The _echo_ and _overwrite_ attributes for a question are # ignored when using the :getc method. # attr_accessor :character # # Allows you to set a character limit for input. # # *WARNING*: This option forces a character by character read. # attr_accessor :limit # # Can be set to +true+ or +false+ to control whether or not input will # be echoed back to the user. A setting of +true+ will cause echo to # match input, but any other true value will be treated as a String to # echo for each character typed. # # This requires HighLine's character reader. See the _character_ # attribute for details. # # *Note*: When using HighLine to manage echo on Unix based systems, we # recommend installing the termios gem. Without it, it's possible to type # fast enough to have letters still show up (when reading character by # character only). # attr_accessor :echo # # Use the Readline library to fetch input. This allows input editing as # well as keeping a history. In addition, tab will auto-complete # within an Array of choices or a file listing. # # *WARNING*: This option is incompatible with all of HighLine's # character reading modes and it causes HighLine to ignore the # specified _input_ stream. # attr_accessor :readline # # Used to control whitespace processing for the answer to this question. # See HighLine::Question.remove_whitespace() for acceptable settings. # attr_accessor :whitespace # # Used to control character case processing for the answer to this question. # See HighLine::Question.change_case() for acceptable settings. # attr_accessor :case # Used to provide a default answer to this question. attr_accessor :default # # If set to a Regexp, the answer must match (before type conversion). # Can also be set to a Proc which will be called with the provided # answer to validate with a +true+ or +false+ return. # attr_accessor :validate # Used to control range checks for answer. attr_accessor :above, :below # If set, answer must pass an include?() check on this object. attr_accessor :in # # Asks a yes or no confirmation question, to ensure a user knows what # they have just agreed to. If set to +true+ the question will be, # "Are you sure? " Any other true value for this attribute is assumed # to be the question to ask. When +false+ or +nil+ (the default), # answers are not confirmed. # attr_accessor :confirm # # When set, the user will be prompted for multiple answers which will # be collected into an Array or Hash and returned as the final answer. # # You can set _gather_ to an Integer to have an Array of exactly that # many answers collected, or a String/Regexp to match an end input which # will not be returned in the Array. # # Optionally _gather_ can be set to a Hash. In this case, the question # will be asked once for each key and the answers will be returned in a # Hash, mapped by key. The @key variable is set before each # question is evaluated, so you can use it in your question. # attr_accessor :gather # # When set to +true+ multiple entries will be collected according to # the setting for _gather_, except they will be required to match # each other. Multiple identical entries will return a single answer. # attr_accessor :verify_match # # When set to a non *nil* value, this will be tried as an answer to the # question. If this answer passes validations, it will become the result # without the user ever being prompted. Otherwise this value is discarded, # and this Question is resolved as a normal call to HighLine.ask(). # attr_writer :first_answer # # The directory from which a user will be allowed to select files, when # File or Pathname is specified as an _answer_type_. Initially set to # Pathname.new(File.expand_path(File.dirname($0))). # attr_accessor :directory # # The glob pattern used to limit file selection when File or Pathname is # specified as an _answer_type_. Initially set to "*". # attr_accessor :glob # # A Hash that stores the various responses used by HighLine to notify # the user. The currently used responses and their purpose are as # follows: # # :ambiguous_completion:: Used to notify the user of an # ambiguous answer the auto-completion # system cannot resolve. # :ask_on_error:: This is the question that will be # redisplayed to the user in the event # of an error. Can be set to # :question to repeat the # original question. # :invalid_type:: The error message shown when a type # conversion fails. # :no_completion:: Used to notify the user that their # selection does not have a valid # auto-completion match. # :not_in_range:: Used to notify the user that a # provided answer did not satisfy # the range requirement tests. # :not_valid:: The error message shown when # validation checks fail. # attr_reader :responses # # When set to +true+ the question is asked, but output does not progress to # the next line. The Cursor is moved back to the beginning of the question # line and it is cleared so that all the contents of the line disappear from # the screen. # attr_accessor :overwrite # # Returns the provided _answer_string_ or the default answer for this # Question if a default was set and the answer is empty. # def answer_or_default( answer_string ) if answer_string.length == 0 and not @default.nil? @default else answer_string end end # # Called late in the initialization process to build intelligent # responses based on the details of this Question object. # def build_responses( ) ### WARNING: This code is quasi-duplicated in ### ### Menu.update_responses(). Check there too when ### ### making changes! ### append_default unless default.nil? @responses = { :ambiguous_completion => "Ambiguous choice. " + "Please choose one of #{@answer_type.inspect}.", :ask_on_error => "? ", :invalid_type => "You must enter a valid #{@answer_type}.", :no_completion => "You must choose one of " + "#{@answer_type.inspect}.", :not_in_range => "Your answer isn't within the expected range " + "(#{expected_range}).", :mismatch => "Your entries didn't match.", :not_valid => "Your answer isn't valid (must match " + "#{@validate.inspect})." }.merge(@responses) ### WARNING: This code is quasi-duplicated in ### ### Menu.update_responses(). Check there too when ### ### making changes! ### end # # Returns the provided _answer_string_ after changing character case by # the rules of this Question. Valid settings for whitespace are: # # +nil+:: Do not alter character case. # (Default.) # :up:: Calls upcase(). # :upcase:: Calls upcase(). # :down:: Calls downcase(). # :downcase:: Calls downcase(). # :capitalize:: Calls capitalize(). # # An unrecognized choice (like :none) is treated as +nil+. # def change_case( answer_string ) if [:up, :upcase].include?(@case) answer_string.upcase elsif [:down, :downcase].include?(@case) answer_string.downcase elsif @case == :capitalize answer_string.capitalize else answer_string end end # # Transforms the given _answer_string_ into the expected type for this # Question. Currently supported conversions are: # # [...]:: Answer must be a member of the passed Array. # Auto-completion is used to expand partial # answers. # lambda {...}:: Answer is passed to lambda for conversion. # Date:: Date.parse() is called with answer. # DateTime:: DateTime.parse() is called with answer. # File:: The entered file name is auto-completed in # terms of _directory_ + _glob_, opened, and # returned. # Float:: Answer is converted with Kernel.Float(). # Integer:: Answer is converted with Kernel.Integer(). # +nil+:: Answer is left in String format. (Default.) # Pathname:: Same as File, save that a Pathname object is # returned. # String:: Answer is converted with Kernel.String(). # HighLine::String:: Answer is converted with HighLine::String() # Regexp:: Answer is fed to Regexp.new(). # Symbol:: The method to_sym() is called on answer and # the result returned. # any other Class:: The answer is passed on to # Class.parse(). # # This method throws ArgumentError, if the conversion cannot be # completed for any reason. # def convert( answer_string ) if @answer_type.nil? answer_string elsif [::String, HighLine::String].include?(@answer_type) HighLine::String(answer_string) elsif [Float, Integer, String].include?(@answer_type) Kernel.send(@answer_type.to_s.to_sym, answer_string) elsif @answer_type == Symbol answer_string.to_sym elsif @answer_type == Regexp Regexp.new(answer_string) elsif @answer_type.is_a?(Array) or [File, Pathname].include?(@answer_type) # cheating, using OptionParser's Completion module choices = selection choices.extend(OptionParser::Completion) answer = choices.complete(answer_string) if answer.nil? raise NoAutoCompleteMatch end if @answer_type.is_a?(Array) answer.last elsif @answer_type == File File.open(File.join(@directory.to_s, answer.last)) else Pathname.new(File.join(@directory.to_s, answer.last)) end elsif [Date, DateTime].include?(@answer_type) or @answer_type.is_a?(Class) @answer_type.parse(answer_string) elsif @answer_type.is_a?(Proc) @answer_type[answer_string] end end # Returns an English explanation of the current range settings. def expected_range( ) expected = [ ] expected << "above #{@above}" unless @above.nil? expected << "below #{@below}" unless @below.nil? expected << "included in #{@in.inspect}" unless @in.nil? case expected.size when 0 then "" when 1 then expected.first when 2 then expected.join(" and ") else expected[0..-2].join(", ") + ", and #{expected.last}" end end # Returns _first_answer_, which will be unset following this call. def first_answer( ) @first_answer ensure @first_answer = nil end # Returns true if _first_answer_ is set. def first_answer?( ) not @first_answer.nil? end # # Returns +true+ if the _answer_object_ is greater than the _above_ # attribute, less than the _below_ attribute and include?()ed in the # _in_ attribute. Otherwise, +false+ is returned. Any +nil+ attributes # are not checked. # def in_range?( answer_object ) (@above.nil? or answer_object > @above) and (@below.nil? or answer_object < @below) and (@in.nil? or @in.include?(answer_object)) end # # Returns the provided _answer_string_ after processing whitespace by # the rules of this Question. Valid settings for whitespace are: # # +nil+:: Do not alter whitespace. # :strip:: Calls strip(). (Default.) # :chomp:: Calls chomp(). # :collapse:: Collapses all whitespace runs to a # single space. # :strip_and_collapse:: Calls strip(), then collapses all # whitespace runs to a single space. # :chomp_and_collapse:: Calls chomp(), then collapses all # whitespace runs to a single space. # :remove:: Removes all whitespace. # # An unrecognized choice (like :none) is treated as +nil+. # # This process is skipped for single character input. # def remove_whitespace( answer_string ) if @whitespace.nil? answer_string elsif [:strip, :chomp].include?(@whitespace) answer_string.send(@whitespace) elsif @whitespace == :collapse answer_string.gsub(/\s+/, " ") elsif [:strip_and_collapse, :chomp_and_collapse].include?(@whitespace) result = answer_string.send(@whitespace.to_s[/^[a-z]+/]) result.gsub(/\s+/, " ") elsif @whitespace == :remove answer_string.gsub(/\s+/, "") else answer_string end end # # Returns an Array of valid answers to this question. These answers are # only known when _answer_type_ is set to an Array of choices, File, or # Pathname. Any other time, this method will return an empty Array. # def selection( ) if @completion.is_a?(Array) @completion elsif [File, Pathname].include?(@completion) Dir[File.join(@directory.to_s, @glob)].map do |file| File.basename(file) end else [ ] end end # Stringifies the question to be asked. def to_str( ) @question end # # Returns +true+ if the provided _answer_string_ is accepted by the # _validate_ attribute or +false+ if it's not. # # It's important to realize that an answer is validated after whitespace # and case handling. # def valid_answer?( answer_string ) @validate.nil? or (@validate.is_a?(Regexp) and answer_string =~ @validate) or (@validate.is_a?(Proc) and @validate[answer_string]) end private # # Adds the default choice to the end of question between |...|. # Trailing whitespace is preserved so the function of HighLine.say() is # not affected. # def append_default( ) if @question =~ /([\t ]+)\Z/ @question << "|#{@default}|#{$1}" elsif @question == "" @question << "|#{@default}| " elsif @question[-1, 1] == "\n" @question[-2, 0] = " |#{@default}|" else @question << " |#{@default}|" end end end end highline-1.6.20/lib/highline/menu.rb0000644000004100000410000003405012261230137017244 0ustar www-datawww-data# menu.rb # # Created by Gregory Thomas Brown on 2005-05-10. # Copyright 2005. All rights reserved. # # This is Free Software. See LICENSE and COPYING for details. require "highline/question" class HighLine # # Menu objects encapsulate all the details of a call to HighLine.choose(). # Using the accessors and Menu.choice() and Menu.choices(), the block passed # to HighLine.choose() can detail all aspects of menu display and control. # class Menu < Question # # Create an instance of HighLine::Menu. All customization is done # through the passed block, which should call accessors and choice() and # choices() as needed to define the Menu. Note that Menus are also # Questions, so all that functionality is available to the block as # well. # def initialize( ) # # Initialize Question objects with ignored values, we'll # adjust ours as needed. # super("Ignored", [ ], &nil) # avoiding passing the block along @items = [ ] @hidden_items = [ ] @help = Hash.new("There's no help for that topic.") @index = :number @index_suffix = ". " @select_by = :index_or_name @flow = :rows @list_option = nil @header = nil @prompt = "? " @layout = :list @shell = false @nil_on_handled = false # Override Questions responses, we'll set our own. @responses = { } # Context for action code. @highline = nil yield self if block_given? init_help if @shell and not @help.empty? end # # An _index_ to append to each menu item in display. See # Menu.index=() for details. # attr_reader :index # # The String placed between an _index_ and a menu item. Defaults to # ". ". Switches to " ", when _index_ is set to a String (like "-"). # attr_accessor :index_suffix # # The _select_by_ attribute controls how the user is allowed to pick a # menu item. The available choices are: # # :index:: The user is allowed to type the numerical # or alphabetical index for their selection. # :index_or_name:: Allows both methods from the # :index option and the # :name option. # :name:: Menu items are selected by typing a portion # of the item name that will be # auto-completed. # attr_accessor :select_by # # This attribute is passed directly on as the mode to HighLine.list() by # all the preset layouts. See that method for appropriate settings. # attr_accessor :flow # # This setting is passed on as the third parameter to HighLine.list() # by all the preset layouts. See that method for details of its # effects. Defaults to +nil+. # attr_accessor :list_option # # Used by all the preset layouts to display title and/or introductory # information, when set. Defaults to +nil+. # attr_accessor :header # # Used by all the preset layouts to ask the actual question to fetch a # menu selection from the user. Defaults to "? ". # attr_accessor :prompt # # An ERb _layout_ to use when displaying this Menu object. See # Menu.layout=() for details. # attr_reader :layout # # When set to +true+, responses are allowed to be an entire line of # input, including details beyond the command itself. Only the first # "word" of input will be matched against the menu choices, but both the # command selected and the rest of the line will be passed to provided # action blocks. Defaults to +false+. # attr_accessor :shell # # When +true+, any selected item handled by provided action code will # return +nil+, instead of the results to the action code. This may # prove handy when dealing with mixed menus where only the names of # items without any code (and +nil+, of course) will be returned. # Defaults to +false+. # attr_accessor :nil_on_handled # # Adds _name_ to the list of available menu items. Menu items will be # displayed in the order they are added. # # An optional _action_ can be associated with this name and if provided, # it will be called if the item is selected. The result of the method # will be returned, unless _nil_on_handled_ is set (when you would get # +nil+ instead). In _shell_ mode, a provided block will be passed the # command chosen and any details that followed the command. Otherwise, # just the command is passed. The @highline variable is set to # the current HighLine context before the action code is called and can # thus be used for adding output and the like. # def choice( name, help = nil, &action ) @items << [name, action] @help[name.to_s.downcase] = help unless help.nil? update_responses # rebuild responses based on our settings end # # A shortcut for multiple calls to the sister method choice(). Be # warned: An _action_ set here will apply to *all* provided # _names_. This is considered to be a feature, so you can easily # hand-off interface processing to a different chunk of code. # def choices( *names, &action ) names.each { |n| choice(n, &action) } end # Identical to choice(), but the item will not be listed for the user. def hidden( name, help = nil, &action ) @hidden_items << [name, action] @help[name.to_s.downcase] = help unless help.nil? end # # Sets the indexing style for this Menu object. Indexes are appended to # menu items, when displayed in list form. The available settings are: # # :number:: Menu items will be indexed numerically, starting # with 1. This is the default method of indexing. # :letter:: Items will be indexed alphabetically, starting # with a. # :none:: No index will be appended to menu items. # any String:: Will be used as the literal _index_. # # Setting the _index_ to :none or a literal String also adjusts # _index_suffix_ to a single space and _select_by_ to :name. # Because of this, you should make a habit of setting the _index_ first. # def index=( style ) @index = style # Default settings. if @index == :none or @index.is_a?(::String) @index_suffix = " " @select_by = :name end end # # Initializes the help system by adding a :help choice, some # action code, and the default help listing. # def init_help( ) return if @items.include?(:help) topics = @help.keys.sort help_help = @help.include?("help") ? @help["help"] : "This command will display helpful messages about " + "functionality, like this one. To see the help for " + "a specific topic enter:\n\thelp [TOPIC]\nTry asking " + "for help on any of the following:\n\n" + "<%= list(#{topics.inspect}, :columns_across) %>" choice(:help, help_help) do |command, topic| topic.strip! topic.downcase! if topic.empty? @highline.say(@help["help"]) else @highline.say("= #{topic}\n\n#{@help[topic]}") end end end # # Used to set help for arbitrary topics. Use the topic "help" # to override the default message. # def help( topic, help ) @help[topic] = help end # # Setting a _layout_ with this method also adjusts some other attributes # of the Menu object, to ideal defaults for the chosen _layout_. To # account for that, you probably want to set a _layout_ first in your # configuration block, if needed. # # Accepted settings for _layout_ are: # # :list:: The default _layout_. The _header_ if set # will appear at the top on its own line with # a trailing colon. Then the list of menu # items will follow. Finally, the _prompt_ # will be used as the ask()-like question. # :one_line:: A shorter _layout_ that fits on one line. # The _header_ comes first followed by a # colon and spaces, then the _prompt_ with menu # items between trailing parenthesis. # :menu_only:: Just the menu items, followed up by a likely # short _prompt_. # any ERb String:: Will be taken as the literal _layout_. This # String can access @header, # @menu and @prompt, but is # otherwise evaluated in the typical HighLine # context, to provide access to utilities like # HighLine.list() primarily. # # If set to either :one_line, or :menu_only, _index_ # will default to :none and _flow_ will default to # :inline. # def layout=( new_layout ) @layout = new_layout # Default settings. case @layout when :one_line, :menu_only self.index = :none @flow = :inline end end # # This method returns all possible options for auto-completion, based # on the settings of _index_ and _select_by_. # def options( ) # add in any hidden menu commands @items.concat(@hidden_items) by_index = if @index == :letter l_index = "`" @items.map { "#{l_index.succ!}" } else (1 .. @items.size).collect { |s| String(s) } end by_name = @items.collect { |c| c.first } case @select_by when :index then by_index when :name by_name else by_index + by_name end ensure # make sure the hidden items are removed, before we return @items.slice!(@items.size - @hidden_items.size, @hidden_items.size) end # # This method processes the auto-completed user selection, based on the # rules for this Menu object. If an action was provided for the # selection, it will be executed as described in Menu.choice(). # def select( highline_context, selection, details = nil ) # add in any hidden menu commands @items.concat(@hidden_items) # Find the selected action. name, action = if selection =~ /^\d+$/ @items[selection.to_i - 1] else l_index = "`" index = @items.map { "#{l_index.succ!}" }.index(selection) @items.find { |c| c.first == selection } or @items[index] end # Run or return it. if not action.nil? @highline = highline_context if @shell result = action.call(name, details) else result = action.call(name) end @nil_on_handled ? nil : result elsif action.nil? name else nil end ensure # make sure the hidden items are removed, before we return @items.slice!(@items.size - @hidden_items.size, @hidden_items.size) end # # Allows Menu objects to pass as Arrays, for use with HighLine.list(). # This method returns all menu items to be displayed, complete with # indexes. # def to_ary( ) case @index when :number @items.map { |c| "#{@items.index(c) + 1}#{@index_suffix}#{c.first}" } when :letter l_index = "`" @items.map { |c| "#{l_index.succ!}#{@index_suffix}#{c.first}" } when :none @items.map { |c| "#{c.first}" } else @items.map { |c| "#{index}#{@index_suffix}#{c.first}" } end end # # Allows Menu to behave as a String, just like Question. Returns the # _layout_ to be rendered, which is used by HighLine.say(). # def to_str( ) case @layout when :list '<%= if @header.nil? then '' else "#{@header}:\n" end %>' + "<%= list( @menu, #{@flow.inspect}, #{@list_option.inspect} ) %>" + "<%= @prompt %>" when :one_line '<%= if @header.nil? then '' else "#{@header}: " end %>' + "<%= @prompt %>" + "(<%= list( @menu, #{@flow.inspect}, #{@list_option.inspect} ) %>)" + "<%= @prompt[/\s*$/] %>" when :menu_only "<%= list( @menu, #{@flow.inspect}, #{@list_option.inspect} ) %><%= @prompt %>" else @layout end end # # This method will update the intelligent responses to account for # Menu specific differences. This overrides the work done by # Question.build_responses(). # def update_responses( ) append_default unless default.nil? @responses = @responses.merge( :ambiguous_completion => "Ambiguous choice. " + "Please choose one of #{options.inspect}.", :ask_on_error => "? ", :invalid_type => "You must enter a valid #{options}.", :no_completion => "You must choose one of " + "#{options.inspect}.", :not_in_range => "Your answer isn't within the expected range " + "(#{expected_range}).", :mismatch => "Your entries didn't match.", :not_valid => "Your answer isn't valid (must match " + "#{@validate.inspect})." ) end end end highline-1.6.20/metadata.yml0000644000004100000410000000476412261230137015732 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: highline version: !ruby/object:Gem::Version version: 1.6.20 platform: ruby authors: - James Edward Gray II autorequire: bindir: bin cert_chain: [] date: 2013-10-16 00:00:00.000000000 Z dependencies: [] description: ! 'A high-level IO library that provides validation, type conversion, and more for command-line interfaces. HighLine also includes a complete menu system that can crank out anything from simple list selection to complete shells with just minutes of work. ' email: james@graysoftinc.com executables: [] extensions: [] extra_rdoc_files: - README.rdoc - INSTALL - TODO - CHANGELOG - LICENSE files: - .gitignore - AUTHORS - CHANGELOG - COPYING - INSTALL - LICENSE - README.rdoc - Rakefile - TODO - doc/.cvsignore - examples/ansi_colors.rb - examples/asking_for_arrays.rb - examples/basic_usage.rb - examples/color_scheme.rb - examples/get_character.rb - examples/limit.rb - examples/menus.rb - examples/overwrite.rb - examples/page_and_wrap.rb - examples/password.rb - examples/repeat_entry.rb - examples/trapping_eof.rb - examples/using_readline.rb - highline.gemspec - lib/highline.rb - lib/highline/color_scheme.rb - lib/highline/compatibility.rb - lib/highline/import.rb - lib/highline/menu.rb - lib/highline/question.rb - lib/highline/simulate.rb - lib/highline/string_extensions.rb - lib/highline/style.rb - lib/highline/system_extensions.rb - setup.rb - site/.cvsignore - site/highline.css - site/images/logo.png - site/index.html - test/string_methods.rb - test/tc_color_scheme.rb - test/tc_highline.rb - test/tc_import.rb - test/tc_menu.rb - test/tc_string_extension.rb - test/tc_string_highline.rb - test/tc_style.rb - test/ts_all.rb homepage: http://highline.rubyforge.org licenses: - Ruby metadata: {} post_install_message: rdoc_options: - --title - HighLine Documentation - --main - README require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: highline rubygems_version: 2.0.3 signing_key: specification_version: 4 summary: HighLine is a high-level command-line IO library. test_files: - test/string_methods.rb - test/tc_color_scheme.rb - test/tc_highline.rb - test/tc_import.rb - test/tc_menu.rb - test/tc_string_extension.rb - test/tc_string_highline.rb - test/tc_style.rb - test/ts_all.rb highline-1.6.20/INSTALL0000644000004100000410000000333012261230137014444 0ustar www-datawww-data= Installing HighLine RubyGems is the preferred easy install method for HighLine. However, you can install HighLine manually as described below. == Installing the Gem HighLine is intended to be installed via the RubyGems[http://rubyforge.org/projects/rubygems/] system. To get the latest version, simply enter the following into your command prompt: $ sudo gem install highline You must have RubyGems[http://rubyforge.org/projects/rubygems/] installed for the above to work. If you want to build the gem locally, make sure you have Rake[http://rubyforge.org/projects/rake/] installed then run the following command: $ rake package == Installing Manually Download the latest version of HighLine from the {RubyForge project page}[http://rubyforge.org/frs/?group_id=683]. Navigate to the root project directory and enter: $ sudo ruby setup.rb == Installing HighLine on JRuby If you are using HighLine on JRuby, many features will not work properly without a working ncurses installation. First, ensure that you have ncurses installed and then install the ffi-ncurses gem. If ffi-ncurses fails to find your ncurses library, you may need to set the RUBY_FFI_NCURSES envirionment variable, i.e: RUBY_FFI_NCURSES_LIB=ncursesw ruby examples/hello.rb For details, see the ffi-ncurses documentation at: http://github.com/seanohalpin/ffi-ncurses == Using termios While not a requirement, HighLine will take advantage of the termios library if installed (on Unix). This slightly improves HighLine's character reading capabilities and thus is recommended for all Unix users. If using the HighLine gem, you should be able to add termios as easily as: $ sudo gem install termios For manual installs, consult the termios documentation. highline-1.6.20/test/0000755000004100000410000000000012261230137014373 5ustar www-datawww-datahighline-1.6.20/test/tc_string_extension.rb0000644000004100000410000000055712261230137021017 0ustar www-datawww-data# tc_string_extension.rb # # Created by Richard LeBer 2011-06-27 # # This is Free Software. See LICENSE and COPYING for details. require "test/unit" require "highline" require "stringio" require "string_methods" class TestStringExtension < Test::Unit::TestCase def setup HighLine.colorize_strings @string = "string" end include StringMethods end highline-1.6.20/test/ts_all.rb0000644000004100000410000000056412261230137016203 0ustar www-datawww-data# ts_all.rb # # Created by James Edward Gray II on 2005-04-26. # Copyright 2005 Gray Productions. All rights reserved. # # This is Free Software. See LICENSE and COPYING for details. require "test/unit" require "tc_highline" require "tc_import" require "tc_menu" require "tc_style" require "tc_color_scheme" require "tc_string_highline" require "tc_string_extension"highline-1.6.20/test/tc_menu.rb0000644000004100000410000002560312261230137016360 0ustar www-datawww-data# encoding: utf-8 # tc_menu.rb # # Created by Gregory Thomas Brown on 2005-05-10. # Copyright 2005. All rights reserved. # # This is Free Software. See LICENSE and COPYING for details. require "test/unit" require "highline" require "stringio" class TestMenu < Test::Unit::TestCase def setup @input = StringIO.new @output = StringIO.new @terminal = HighLine.new(@input, @output) end def test_choices @input << "2\n" @input.rewind output = @terminal.choose do |menu| menu.choices("Sample1", "Sample2", "Sample3") end assert_equal("Sample2", output) end def test_flow @input << "Sample1\n" @input.rewind @terminal.choose do |menu| # Default: menu.flow = :rows menu.choice "Sample1" menu.choice "Sample2" menu.choice "Sample3" end assert_equal("1. Sample1\n2. Sample2\n3. Sample3\n? ", @output.string) @output.truncate(@output.rewind) @input.rewind @terminal.choose do |menu| menu.flow = :columns_across menu.choice "Sample1" menu.choice "Sample2" menu.choice "Sample3" end assert_equal("1. Sample1 2. Sample2 3. Sample3\n? ", @output.string) @output.truncate(@output.rewind) @input.rewind @terminal.choose do |menu| menu.flow = :inline menu.index = :none menu.choice "Sample1" menu.choice "Sample2" menu.choice "Sample3" end assert_equal("Sample1, Sample2 or Sample3? ", @output.string) end def test_unicode_flow @input << "1\n" @input.rewind @terminal.choose do |menu| # Default: menu.flow = :rows menu.choice "Unicode right single quotation mark: ’" end assert_equal("1. Unicode right single quotation mark: ’\n? ", @output.string) end def test_help @input << "help\nhelp load\nhelp rules\nhelp missing\n" @input.rewind 4.times do @terminal.choose do |menu| menu.shell = true menu.choice(:load, "Load a file.") menu.choice(:save, "Save data in file.") menu.choice(:quit, "Exit program.") menu.help("rules", "The rules of this system are as follows...") end end assert_equal( "1. load\n2. save\n3. quit\n4. help\n? " + "This command will display helpful messages about " + "functionality, like this one. To see the help for a " + "specific topic enter:\n" + "\thelp [TOPIC]\n" + "Try asking for help on any of the following:\n" + "\nload quit rules save \n" + "1. load\n2. save\n3. quit\n4. help\n? " + "= load\n\n" + "Load a file.\n" + "1. load\n2. save\n3. quit\n4. help\n? " + "= rules\n\n" + "The rules of this system are as follows...\n" + "1. load\n2. save\n3. quit\n4. help\n? " + "= missing\n\n" + "There's no help for that topic.\n", @output.string ) end def test_index @input << "Sample1\n" @input.rewind @terminal.choose do |menu| # Default: menu.index = :number menu.choice "Sample1" menu.choice "Sample2" menu.choice "Sample3" end assert_equal("1. Sample1\n2. Sample2\n3. Sample3\n? ", @output.string) @output.truncate(@output.rewind) @input.rewind @terminal.choose do |menu| menu.index = :letter menu.index_suffix = ") " menu.choice "Sample1" menu.choice "Sample2" menu.choice "Sample3" end assert_equal("a) Sample1\nb) Sample2\nc) Sample3\n? ", @output.string) @output.truncate(@output.rewind) @input.rewind @terminal.choose do |menu| menu.index = :none menu.choice "Sample1" menu.choice "Sample2" menu.choice "Sample3" end assert_equal("Sample1\nSample2\nSample3\n? ", @output.string) @output.truncate(@output.rewind) @input.rewind @terminal.choose do |menu| menu.index = "*" menu.choice "Sample1" menu.choice "Sample2" menu.choice "Sample3" end assert_equal("* Sample1\n* Sample2\n* Sample3\n? ", @output.string) end def test_layouts @input << "save\n" @input.rewind @terminal.choose(:load, :save, :quit) # Default: layout = :list assert_equal("1. load\n2. save\n3. quit\n? ", @output.string) @input.rewind @output.truncate(@output.rewind) @terminal.choose(:load, :save, :quit) do |menu| menu.header = "File Menu" end assert_equal( "File Menu:\n" + "1. load\n2. save\n3. quit\n? ", @output.string ) @input.rewind @output.truncate(@output.rewind) @terminal.choose(:load, :save, :quit) do |menu| menu.layout = :one_line menu.header = "File Menu" menu.prompt = "Operation? " end assert_equal( "File Menu: Operation? " + "(load, save or quit) ", @output.string ) @input.rewind @output.truncate(@output.rewind) @terminal.choose(:load, :save, :quit) do |menu| menu.layout = :menu_only end assert_equal("load, save or quit? ", @output.string) @input.rewind @output.truncate(@output.rewind) @terminal.choose(:load, :save, :quit) do |menu| menu.layout = '<%= list(@menu) %>File Menu: ' end assert_equal("1. load\n2. save\n3. quit\nFile Menu: ", @output.string) end def test_list_option @input << "l\n" @input.rewind @terminal.choose(:load, :save, :quit) do |menu| menu.layout = :menu_only menu.list_option = ", or " end assert_equal("load, save, or quit? ", @output.string) end def test_nil_on_handled @input << "3\n3\n2\n" @input.rewind # Shows that by default proc results are returned. output = @terminal.choose do |menu| menu.choice "Sample1" do "output1" end menu.choice "Sample2" do "output2" end menu.choice "Sample3" do "output3" end end assert_equal("output3", output) # # Shows that they can be replaced with +nil+ by setting # _nil_on_handled to +true+. # output = @terminal.choose do |menu| menu.nil_on_handled = true menu.choice "Sample1" do "output1" end menu.choice "Sample2" do "output2" end menu.choice "Sample3" do "output3" end end assert_equal(nil, output) # Shows that a menu item without a proc will be returned no matter what. output = @terminal.choose do |menu| menu.choice "Sample1" menu.choice "Sample2" menu.choice "Sample3" end assert_equal("Sample2", output) end def test_passed_command @input << "q\n" @input.rewind selected = nil @terminal.choose do |menu| menu.choices(:load, :save, :quit) { |command| selected = command } end assert_equal(:quit, selected) end def test_question_options @input << "save\n" @input.rewind answer = @terminal.choose(:Load, :Save, :Quit) do |menu| menu.case = :capitalize end assert_equal(:Save, answer) @input.rewind answer = @terminal.choose(:Load, :Save, :Quit) do |menu| menu.case = :capitalize menu.character = :getc end assert_equal(:Save, answer) assert_equal(?a, @input.getc) end def test_select_by @input << "Sample1\n2\n" @input.rewind selected = @terminal.choose do |menu| menu.choice "Sample1" menu.choice "Sample2" menu.choice "Sample3" end assert_equal("Sample1", selected) @input.rewind selected = @terminal.choose do |menu| menu.select_by = :index menu.choice "Sample1" menu.choice "Sample2" menu.choice "Sample3" end assert_equal("Sample2", selected) @input.rewind selected = @terminal.choose do |menu| menu.select_by = :name menu.choice "Sample1" menu.choice "Sample2" menu.choice "Sample3" end assert_equal("Sample1", selected) end def test_hidden @input << "Hidden\n4\n" @input.rewind selected = @terminal.choose do |menu| menu.choice "Sample1" menu.choice "Sample2" menu.choice "Sample3" menu.hidden "Hidden!" end assert_equal("Hidden!", selected) assert_equal("1. Sample1\n2. Sample2\n3. Sample3\n? ", @output.string) @input.rewind selected = @terminal.choose do |menu| menu.select_by = :index menu.choice "Sample1" menu.choice "Sample2" menu.choice "Sample3" menu.hidden "Hidden!" end assert_equal("Hidden!", selected) @input.rewind selected = @terminal.choose do |menu| menu.select_by = :name menu.choice "Sample1" menu.choice "Sample2" menu.choice "Sample3" menu.hidden "Hidden!" end assert_equal("Hidden!", selected) @input.rewind end def test_select_by_letter @input << "b\n" @input.rewind selected = @terminal.choose do |menu| menu.index = :letter menu.choice :save menu.choice :load menu.choice :quit end assert_equal(:load, selected) end def test_shell @input << "save --some-option my_file.txt\n" @input.rewind selected = nil options = nil answer = @terminal.choose do |menu| menu.choices(:load, :quit) menu.choice(:save) do |command, details| selected = command options = details "Saved!" end menu.shell = true end assert_equal("Saved!", answer) assert_equal(:save, selected) assert_equal("--some-option my_file.txt", options) end def test_simple_menu_shortcut @input << "3\n" @input.rewind selected = @terminal.choose(:save, :load, :quit) assert_equal(:quit, selected) end def test_symbols @input << "3\n" @input.rewind selected = @terminal.choose do |menu| menu.choices(:save, :load, :quit) end assert_equal(:quit, selected) end def test_paged_print_infinite_loop_bug @terminal.page_at = 5 # Will page twice, so start with two new lines @input << "\n\n3\n" @input.rewind # Sadly this goes into an infinite loop without the fix to page_print selected = @terminal.choose(* 1..10) assert_equal(selected, 3) end def test_cancel_paging # Tests that paging can be cancelled halfway through @terminal.page_at = 5 # Will page twice, so stop after first page and make choice 3 @input << "q3\n" @input.rewind selected = @terminal.choose(* 1..10) assert_equal(selected, 3) # Make sure paging message appeared assert( @output.string.index('press enter/return to continue or q to stop'), "Paging message did not appear." ) # Make sure it only appeared once assert( @output.string !~ /q to stop.*q to stop/m, "Paging message appeared more than once." ) end end highline-1.6.20/test/tc_string_highline.rb0000644000004100000410000000156312261230137020570 0ustar www-datawww-data# tc_highline_string.rb # # Created by Richard LeBer 2011-06-27 # # This is Free Software. See LICENSE and COPYING for details. require "test/unit" require "highline" require "stringio" require "string_methods" class TestHighLineString < Test::Unit::TestCase def setup @string = HighLine::String.new("string") end def test_string_class # Basic constructor assert_equal HighLine::String, @string.class assert_equal "string", @string # Alternative constructor method new_string = HighLine::String("string") assert_equal HighLine::String, new_string.class assert_equal @string, new_string # String methods work assert_equal 6, @string.size assert_equal "STRING", @string.upcase end include StringMethods def test_string_class_is_unchanged assert_raise(::NoMethodError) { "string".color(:blue) } end end highline-1.6.20/test/tc_import.rb0000644000004100000410000000230212261230137016715 0ustar www-datawww-data# tc_import.rb # # Created by James Edward Gray II on 2005-04-26. # Copyright 2005 Gray Productions. All rights reserved. # # This is Free Software. See LICENSE and COPYING for details. require "test/unit" require "highline/import" require "stringio" class TestImport < Test::Unit::TestCase def test_import assert_respond_to(self, :agree) assert_respond_to(self, :ask) assert_respond_to(self, :choose) assert_respond_to(self, :say) end def test_or_ask old_terminal = $terminal input = StringIO.new output = StringIO.new $terminal = HighLine.new(input, output) input << "10\n" input.rewind assert_equal(10, nil.or_ask("How much? ", Integer)) input.rewind assert_equal(20, "20".or_ask("How much? ", Integer)) assert_equal(20, 20.or_ask("How much? ", Integer)) assert_equal(10, 20.or_ask("How much? ", Integer) { |q| q.in = 1..10 }) ensure $terminal = old_terminal end def test_redirection old_terminal = $terminal $terminal = HighLine.new(nil, (output = StringIO.new)) say("Testing...") assert_equal("Testing...\n", output.string) ensure $terminal = old_terminal end end highline-1.6.20/test/tc_color_scheme.rb0000644000004100000410000000670412261230137020057 0ustar www-datawww-data# tc_color_scheme.rb # # Created by Jeremy Hinegardner on 2007-01-24. # Copyright 2007 Jeremy Hinegardner. All rights reserved. # # This is Free Software. See LICENSE and COPYING for details. require "test/unit" require "highline" require "stringio" class TestColorScheme < Test::Unit::TestCase def setup @input = StringIO.new @output = StringIO.new @terminal = HighLine.new(@input, @output) @old_color_scheme = HighLine.color_scheme end def teardown HighLine.color_scheme = @old_color_scheme end def test_using_color_scheme assert_equal(false,HighLine.using_color_scheme?) HighLine.color_scheme = HighLine::ColorScheme.new assert_equal(true,HighLine.using_color_scheme?) end def test_scheme HighLine.color_scheme = HighLine::SampleColorScheme.new @terminal.say("This should be <%= color('warning yellow', :warning) %>.") assert_equal("This should be \e[1m\e[33mwarning yellow\e[0m.\n",@output.string) @output.rewind @terminal.say("This should be <%= color('warning yellow', 'warning') %>.") assert_equal("This should be \e[1m\e[33mwarning yellow\e[0m.\n",@output.string) @output.rewind @terminal.say("This should be <%= color('warning yellow', 'WarNing') %>.") assert_equal("This should be \e[1m\e[33mwarning yellow\e[0m.\n",@output.string) @output.rewind # Check that keys are available, and as expected assert_equal ["critical", "error", "warning", "notice", "info", "debug", "row_even", "row_odd"].sort, HighLine.color_scheme.keys.sort # Color scheme doesn't care if we use symbols or strings, and is case-insensitive warning1 = HighLine.color_scheme[:warning] warning2 = HighLine.color_scheme["warning"] warning3 = HighLine.color_scheme[:wArning] warning4 = HighLine.color_scheme["warniNg"] assert_instance_of HighLine::Style, warning1 assert_instance_of HighLine::Style, warning2 assert_instance_of HighLine::Style, warning3 assert_instance_of HighLine::Style, warning4 assert_equal warning1, warning2 assert_equal warning1, warning3 assert_equal warning1, warning4 # Nonexistent keys return nil assert_nil HighLine.color_scheme[:nonexistent] # Same as above, for definitions defn1 = HighLine.color_scheme.definition(:warning) defn2 = HighLine.color_scheme.definition("warning") defn3 = HighLine.color_scheme.definition(:wArning) defn4 = HighLine.color_scheme.definition("warniNg") assert_instance_of Array, defn1 assert_instance_of Array, defn2 assert_instance_of Array, defn3 assert_instance_of Array, defn4 assert_equal [:bold, :yellow], defn1 assert_equal [:bold, :yellow], defn2 assert_equal [:bold, :yellow], defn3 assert_equal [:bold, :yellow], defn4 assert_nil HighLine.color_scheme.definition(:nonexistent) color_scheme_hash = HighLine.color_scheme.to_hash assert_instance_of Hash, color_scheme_hash assert_equal ["critical", "error", "warning", "notice", "info", "debug", "row_even", "row_odd"].sort, color_scheme_hash.keys.sort assert_instance_of Array, HighLine.color_scheme.definition(:warning) assert_equal [:bold, :yellow], HighLine.color_scheme.definition(:warning) # turn it back off, should raise an exception HighLine.color_scheme = @old_color_scheme assert_raises(NameError) { @terminal.say("This should be <%= color('nothing at all', :error) %>.") } end end highline-1.6.20/test/tc_highline.rb0000755000004100000410000010506012261230137017202 0ustar www-datawww-data# tc_highline.rb # # Created by James Edward Gray II on 2005-04-26. # Copyright 2005 Gray Productions. All rights reserved. # # This is Free Software. See LICENSE and COPYING for details. require "test/unit" require "highline" require "stringio" if HighLine::CHARACTER_MODE == "Win32API" class HighLine # Override Windows' character reading so it's not tied to STDIN. def get_character( input = STDIN ) input.getc end end end class TestHighLine < Test::Unit::TestCase def setup @input = StringIO.new @output = StringIO.new @terminal = HighLine.new(@input, @output) end def test_agree @input << "y\nyes\nYES\nHell no!\nNo\n" @input.rewind assert_equal(true, @terminal.agree("Yes or no? ")) assert_equal(true, @terminal.agree("Yes or no? ")) assert_equal(true, @terminal.agree("Yes or no? ")) assert_equal(false, @terminal.agree("Yes or no? ")) @input.truncate(@input.rewind) @input << "yellow" @input.rewind assert_equal(true, @terminal.agree("Yes or no? ", :getc)) end def test_agree_with_block @input << "\n\n" @input.rewind assert_equal(true, @terminal.agree("Yes or no? ") { |q| q.default = "y" }) assert_equal(false, @terminal.agree("Yes or no? ") { |q| q.default = "n" }) end def test_ask name = "James Edward Gray II" @input << name << "\n" @input.rewind assert_equal(name, @terminal.ask("What is your name? ")) assert_raise(EOFError) { @terminal.ask("Any input left? ") } end def test_ask_string name = "James Edward Gray II" @input << name << "\n" @input.rewind assert_equal(name, @terminal.ask("What is your name? ", String)) assert_raise(EOFError) { @terminal.ask("Any input left? ", String) } end def test_indent text = "Testing...\n" @terminal.indent_level=1 @terminal.say(text) assert_equal(' '*3+text, @output.string) @output.truncate(@output.rewind) @terminal.indent_level=3 @terminal.say(text) assert_equal(' '*9+text, @output.string) @output.truncate(@output.rewind) @terminal.indent_level=0 @terminal.indent_size=5 @terminal.indent(2, text) assert_equal(' '*10+text, @output.string) @output.truncate(@output.rewind) @terminal.indent_size=4 @terminal.indent { @terminal.say(text) } assert_equal(' '*4+text, @output.string) @output.truncate(@output.rewind) @terminal.indent_size=2 @terminal.indent(3) { |t| t.say(text) } assert_equal(' '*6+text, @output.string) @output.truncate(@output.rewind) @terminal.indent { |t| t.indent { t.indent { t.indent { |tt| tt.say(text) } } } } assert_equal(' '*8+text, @output.string) text = "Multi\nLine\nIndentation\n" indent = ' '*4 @terminal.indent_level=2 @output.truncate(@output.rewind) @terminal.say(text) assert_equal("#{indent}Multi\n#{indent}Line\n#{indent}Indentation\n", @output.string) @output.truncate(@output.rewind) @terminal.multi_indent = false @terminal.say(text) assert_equal("#{indent}Multi\nLine\nIndentation\n", @output.string) @output.truncate(@output.rewind) @terminal.indent(0, text, true) assert_equal("#{indent}Multi\n#{indent}Line\n#{indent}Indentation\n", @output.string) end def test_newline @terminal.newline @terminal.newline assert_equal("\n\n", @output.string) end def test_bug_fixes # auto-complete bug @input << "ruby\nRuby\n" @input.rewind languages = [:Perl, :Python, :Ruby] answer = @terminal.ask( "What is your favorite programming language? ", languages ) assert_equal(languages.last, answer) @input.truncate(@input.rewind) @input << "ruby\n" @input.rewind answer = @terminal.ask( "What is your favorite programming language? ", languages ) do |q| q.case = :capitalize end assert_equal(languages.last, answer) # poor auto-complete error message @input.truncate(@input.rewind) @input << "lisp\nruby\n" @input.rewind @output.truncate(@output.rewind) answer = @terminal.ask( "What is your favorite programming language? ", languages ) do |q| q.case = :capitalize end assert_equal(languages.last, answer) assert_equal( "What is your favorite programming language? " + "You must choose one of [:Perl, :Python, :Ruby].\n" + "? ", @output.string ) end def test_case_changes @input << "jeg2\n" @input.rewind answer = @terminal.ask("Enter your initials ") do |q| q.case = :up end assert_equal("JEG2", answer) @input.truncate(@input.rewind) @input << "cRaZY\n" @input.rewind answer = @terminal.ask("Enter a search string: ") do |q| q.case = :down end assert_equal("crazy", answer) end def test_character_echo @input << "password\r" @input.rewind answer = @terminal.ask("Please enter your password: ") do |q| q.echo = "*" end assert_equal("password", answer) assert_equal("Please enter your password: ********\n", @output.string) @input.truncate(@input.rewind) @input << "2" @input.rewind @output.truncate(@output.rewind) answer = @terminal.ask( "Select an option (1, 2 or 3): ", Integer ) do |q| q.echo = "*" q.character = true end assert_equal(2, answer) assert_equal("Select an option (1, 2 or 3): *\n", @output.string) end def test_backspace_does_not_enter_prompt @input << "\b\b" @input.rewind answer = @terminal.ask("Please enter your password: ") do |q| q.echo = "*" end assert_equal("", answer) assert_equal("Please enter your password: \n", @output.string) end def test_readline_on_non_echo_question_has_prompt @input << "you can't see me" @input.rewind answer = @terminal.ask("Please enter some hidden text: ") do |q| q.readline = true q.echo = "*" end assert_equal("you can't see me", answer) assert_equal("Please enter some hidden text: ****************\n", @output.string) end def test_character_reading # WARNING: This method does NOT cover Unix and Windows savvy testing! @input << "12345" @input.rewind answer = @terminal.ask("Enter a single digit: ", Integer) do |q| q.character = :getc end assert_equal(1, answer) end def test_frozen_statement @terminal.say('This is a frozen statement'.freeze) assert_equal("This is a frozen statement\n", @output.string) end def test_color @terminal.say("This should be <%= BLUE %>blue<%= CLEAR %>!") assert_equal("This should be \e[34mblue\e[0m!\n", @output.string) @output.truncate(@output.rewind) @terminal.say( "This should be " + "<%= BOLD + ON_WHITE %>bold on white<%= CLEAR %>!" ) assert_equal( "This should be \e[1m\e[47mbold on white\e[0m!\n", @output.string ) @output.truncate(@output.rewind) @terminal.say("This should be <%= color('cyan', CYAN) %>!") assert_equal("This should be \e[36mcyan\e[0m!\n", @output.string) @output.truncate(@output.rewind) @terminal.say( "This should be " + "<%= color('blinking on red', :blink, :on_red) %>!" ) assert_equal( "This should be \e[5m\e[41mblinking on red\e[0m!\n", @output.string ) @output.truncate(@output.rewind) @terminal.say("This should be <%= NONE %>none<%= CLEAR %>!") assert_equal("This should be \e[38mnone\e[0m!\n", @output.string) @output.truncate(@output.rewind) @terminal.say("This should be <%= RGB_906030 %>rgb_906030<%= CLEAR %>!") assert_equal("This should be \e[38;5;137mrgb_906030\e[0m!\n", @output.string) @output.truncate(@output.rewind) @terminal.say("This should be <%= ON_RGB_C06030 %>on_rgb_c06030<%= CLEAR %>!") assert_equal("This should be \e[48;5;173mon_rgb_c06030\e[0m!\n", @output.string) @output.truncate(@output.rewind) # Does class method work, too? @terminal.say("This should be <%= HighLine.color('reverse underlined magenta', :reverse, :underline, :magenta) %>!") assert_equal( "This should be \e[7m\e[4m\e[35mreverse underlined magenta\e[0m!\n", @output.string ) @output.truncate(@output.rewind) # turn off color old_setting = HighLine.use_color? assert_nothing_raised(Exception) { HighLine.use_color = false } @terminal.say("This should be <%= color('cyan', CYAN) %>!") assert_equal("This should be cyan!\n", @output.string) HighLine.use_color = old_setting end def test_uncolor # instance method assert_equal( "This should be reverse underlined magenta!\n", @terminal.uncolor("This should be \e[7m\e[4m\e[35mreverse underlined magenta\e[0m!\n") ) @output.truncate(@output.rewind) # class method assert_equal( "This should be reverse underlined magenta!\n", HighLine.uncolor("This should be \e[7m\e[4m\e[35mreverse underlined magenta\e[0m!\n") ) @output.truncate(@output.rewind) # RGB color assert_equal( "This should be rgb_906030!\n", @terminal.uncolor("This should be \e[38;5;137mrgb_906030\e[0m!\n") ) end def test_confirm @input << "junk.txt\nno\nsave.txt\ny\n" @input.rewind answer = @terminal.ask("Enter a filename: ") do |q| q.confirm = "Are you sure you want to overwrite <%= @answer %>? " q.responses[:ask_on_error] = :question end assert_equal("save.txt", answer) assert_equal( "Enter a filename: " + "Are you sure you want to overwrite junk.txt? " + "Enter a filename: " + "Are you sure you want to overwrite save.txt? ", @output.string ) @input.truncate(@input.rewind) @input << "junk.txt\nyes\nsave.txt\nn\n" @input.rewind @output.truncate(@output.rewind) answer = @terminal.ask("Enter a filename: ") do |q| q.confirm = "Are you sure you want to overwrite <%= @answer %>? " end assert_equal("junk.txt", answer) assert_equal( "Enter a filename: " + "Are you sure you want to overwrite junk.txt? ", @output.string ) end def test_defaults @input << "\nNo Comment\n" @input.rewind answer = @terminal.ask("Are you sexually active? ") do |q| q.validate = /\Ay(?:es)?|no?|no comment\Z/i end assert_equal("No Comment", answer) @input.truncate(@input.rewind) @input << "\nYes\n" @input.rewind @output.truncate(@output.rewind) answer = @terminal.ask("Are you sexually active? ") do |q| q.default = "No Comment" q.validate = /\Ay(?:es)?|no?|no comment\Z/i end assert_equal("No Comment", answer) assert_equal( "Are you sexually active? |No Comment| ", @output.string ) end def test_string_preservation @input << "Maybe\nYes\n" @input.rewind my_string = "Is that your final answer? " @terminal.ask(my_string) { |q| q.default = "Possibly" } @terminal.ask(my_string) { |q| q.default = "Maybe" } assert_equal("Is that your final answer? ", my_string) end def test_empty @input << "\n" @input.rewind answer = @terminal.ask("") do |q| q.default = "yes" q.validate = /\Ay(?:es)?|no?\Z/i end assert_equal("yes", answer) end def test_erb @terminal.say( "The integers from 1 to 10 are:\n" + "% (1...10).each do |n|\n" + "\t<%= n %>,\n" + "% end\n" + "\tand 10" ) assert_equal( "The integers from 1 to 10 are:\n" + "\t1,\n\t2,\n\t3,\n\t4,\n\t5,\n" + "\t6,\n\t7,\n\t8,\n\t9,\n\tand 10\n", @output.string ) end def test_files @input << "#{File.basename(__FILE__)[0, 5]}\n" @input.rewind assert_equal "tc_hi\n",@input.read @input.rewind file = @terminal.ask("Select a file: ", File) do |q| q.directory = File.expand_path(File.dirname(__FILE__)) q.glob = "*.rb" end assert_instance_of(File, file) assert_equal("# tc_highline.rb\n", file.gets) file.close @input.rewind pathname = @terminal.ask("Select a file: ", Pathname) do |q| q.directory = File.expand_path(File.dirname(__FILE__)) q.glob = "*.rb" end assert_instance_of(Pathname, pathname) assert_equal(File.size(__FILE__), pathname.size) end def test_gather @input << "James\nDana\nStorm\nGypsy\n\n" @input.rewind answers = @terminal.ask("Enter four names:") do |q| q.gather = 4 end assert_equal(%w{James Dana Storm Gypsy}, answers) assert_equal("\n", @input.gets) assert_equal("Enter four names:\n", @output.string) @input.rewind answers = @terminal.ask("Enter four names:") do |q| q.gather = "" end assert_equal(%w{James Dana Storm Gypsy}, answers) @input.rewind answers = @terminal.ask("Enter four names:") do |q| q.gather = /^\s*$/ end assert_equal(%w{James Dana Storm Gypsy}, answers) @input.truncate(@input.rewind) @input << "29\n49\n30\n" @input.rewind @output.truncate(@output.rewind) answers = @terminal.ask("<%= @key %>: ", Integer) do |q| q.gather = { "Age" => 0, "Wife's Age" => 0, "Father's Age" => 0} end assert_equal( { "Age" => 29, "Wife's Age" => 30, "Father's Age" => 49}, answers ) assert_equal("Age: Father's Age: Wife's Age: ", @output.string) end def test_typing_verification @input << "all work and no play makes jack a dull boy\n" * 3 @input.rewind answer = @terminal.ask("How's work? ") do |q| q.gather = 3 q.verify_match = true end assert_equal("all work and no play makes jack a dull boy", answer) @input.truncate(@input.rewind) @input << "all play and no work makes jack a mere toy\n" @input << "all work and no play makes jack a dull boy\n" * 5 @input.rewind @output.truncate(@output.rewind) answer = @terminal.ask("How are things going? ") do |q| q.gather = 3 q.verify_match = true q.responses[:mismatch] = 'Typing mismatch!' q.responses[:ask_on_error] = '' end assert_equal("all work and no play makes jack a dull boy", answer) # now try using a hash for gather @input.truncate(@input.rewind) @input << "Password\nPassword\n" @input.rewind @output.truncate(@output.rewind) answer = @terminal.ask("<%= @key %>: ") do |q| q.verify_match = true q.gather = {"Enter a password" => '', "Please type it again" => ''} end assert_equal("Password", answer) @input.truncate(@input.rewind) @input << "Password\nMistake\nPassword\nPassword\n" @input.rewind @output.truncate(@output.rewind) answer = @terminal.ask("<%= @key %>: ") do |q| q.verify_match = true q.responses[:mismatch] = 'Typing mismatch!' q.responses[:ask_on_error] = '' q.gather = {"Enter a password" => '', "Please type it again" => ''} end assert_equal("Password", answer) assert_equal( "Enter a password: " + "Please type it again: " + "Typing mismatch!\n" + "Enter a password: " + "Please type it again: ", @output.string ) end def test_lists digits = %w{Zero One Two Three Four Five Six Seven Eight Nine} erb_digits = digits.dup erb_digits[erb_digits.index("Five")] = "<%= color('Five', :blue) %%>" @terminal.say("<%= list(#{digits.inspect}) %>") assert_equal(digits.map { |d| "#{d}\n" }.join, @output.string) @output.truncate(@output.rewind) @terminal.say("<%= list(#{digits.inspect}, :inline) %>") assert_equal( digits[0..-2].join(", ") + " or #{digits.last}\n", @output.string ) @output.truncate(@output.rewind) @terminal.say("<%= list(#{digits.inspect}, :inline, ' and ') %>") assert_equal( digits[0..-2].join(", ") + " and #{digits.last}\n", @output.string ) @output.truncate(@output.rewind) @terminal.say("<%= list(#{digits.inspect}, :columns_down, 3) %>") assert_equal( "Zero Four Eight\n" + "One Five Nine \n" + "Two Six \n" + "Three Seven\n", @output.string ) @output.truncate(@output.rewind) @terminal.say("<%= list(#{erb_digits.inspect}, :columns_down, 3) %>") assert_equal( "Zero Four Eight\n" + "One \e[34mFive\e[0m Nine \n" + "Two Six \n" + "Three Seven\n", @output.string ) colums_of_twenty = ["12345678901234567890"] * 5 @output.truncate(@output.rewind) @terminal.say("<%= list(#{colums_of_twenty.inspect}, :columns_down) %>") assert_equal( "12345678901234567890 12345678901234567890 " + "12345678901234567890\n" + "12345678901234567890 12345678901234567890\n", @output.string ) @output.truncate(@output.rewind) @terminal.say("<%= list(#{digits.inspect}, :columns_across, 3) %>") assert_equal( "Zero One Two \n" + "Three Four Five \n" + "Six Seven Eight\n" + "Nine \n", @output.string ) colums_of_twenty.pop @output.truncate(@output.rewind) @terminal.say("<%= list( #{colums_of_twenty.inspect}, :columns_across ) %>") assert_equal( "12345678901234567890 12345678901234567890 " + "12345678901234567890\n" + "12345678901234567890\n", @output.string ) @output.truncate(@output.rewind) wide = %w[0123456789 a b c d e f g h i j k l m n o p q r s t u v w x y z] @terminal.say("<%= list( #{wide.inspect}, :uneven_columns_across ) %>") assert_equal( "0123456789 a b c d e f g h i j k l m n o " + "p q r s t u v w\n" + "x y z\n", @output.string ) @output.truncate(@output.rewind) @terminal.say("<%= list( #{wide.inspect}, :uneven_columns_across, 10 ) %>") assert_equal( "0123456789 a b c d e f g h i\n" + "j k l m n o p q r s\n" + "t u v w x y z\n", @output.string ) @output.truncate(@output.rewind) @terminal.say("<%= list( #{wide.inspect}, :uneven_columns_down ) %>") assert_equal( "0123456789 b d f h j l n p r t v x z\n" + "a c e g i k m o q s u w y\n", @output.string ) @output.truncate(@output.rewind) @terminal.say("<%= list( #{wide.inspect}, :uneven_columns_down, 10 ) %>") assert_equal( "0123456789 c f i l o r u x\n" + "a d g j m p s v y\n" + "b e h k n q t w z\n", @output.string ) end def test_lists_with_zero_items modes = [nil, :rows, :inline, :columns_across, :columns_down] modes.each do |mode| result = @terminal.list([], mode) assert_equal("", result) end end def test_lists_with_nil_items modes = [nil] modes.each do |mode| result = @terminal.list([nil], mode) assert_equal("\n", result) end end def test_lists_with_one_item items = ['Zero'] modes = { nil => "Zero\n", :rows => "Zero\n", :inline => "Zero", :columns_across => "Zero\n", :columns_down => "Zero\n" } modes.each do |mode, expected| result = @terminal.list(items, mode) assert_equal(expected, result) end end def test_lists_with_two_items items = ['Zero', 'One'] modes = { nil => "Zero\nOne\n", :rows => "Zero\nOne\n", :inline => "Zero or One", :columns_across => "Zero One \n", :columns_down => "Zero One \n" } modes.each do |mode, expected| result = @terminal.list(items, mode) assert_equal(expected, result) end end def test_lists_with_three_items items = ['Zero', 'One', 'Two'] modes = { nil => "Zero\nOne\nTwo\n", :rows => "Zero\nOne\nTwo\n", :inline => "Zero, One or Two", :columns_across => "Zero One Two \n", :columns_down => "Zero One Two \n" } modes.each do |mode, expected| result = @terminal.list(items, mode) assert_equal(expected, result) end end def test_mode assert(%w[Win32API termios ncurses stty jline].include?(HighLine::CHARACTER_MODE), "#{HighLine::CHARACTER_MODE} not in list") end class NameClass def self.parse( string ) if string =~ /^\s*(\w+),\s*(\w+)\s+(\w+)\s*$/ self.new($2, $3, $1) else raise ArgumentError, "Invalid name format." end end def initialize(first, middle, last) @first, @middle, @last = first, middle, last end attr_reader :first, :middle, :last end def test_my_class_conversion @input << "Gray, James Edward\n" @input.rewind answer = @terminal.ask("Your name? ", NameClass) do |q| q.validate = lambda do |name| names = name.split(/,\s*/) return false unless names.size == 2 return false if names.first =~ /\s/ names.last.split.size == 2 end end assert_instance_of(NameClass, answer) assert_equal("Gray", answer.last) assert_equal("James", answer.first) assert_equal("Edward", answer.middle) end def test_no_echo @input << "password\r" @input.rewind answer = @terminal.ask("Please enter your password: ") do |q| q.echo = false end assert_equal("password", answer) assert_equal("Please enter your password: \n", @output.string) @input.rewind @output.truncate(@output.rewind) answer = @terminal.ask("Pick a letter or number: ") do |q| q.character = true q.echo = false end assert_equal("p", answer) assert_equal("a", @input.getc.chr) assert_equal("Pick a letter or number: \n", @output.string) end def test_paging @terminal.page_at = 22 @input << "\n\n" @input.rewind @terminal.say((1..50).map { |n| "This is line #{n}.\n"}.join) assert_equal( (1..22).map { |n| "This is line #{n}.\n"}.join + "\n-- press enter/return to continue or q to stop -- \n\n" + (23..44).map { |n| "This is line #{n}.\n"}.join + "\n-- press enter/return to continue or q to stop -- \n\n" + (45..50).map { |n| "This is line #{n}.\n"}.join, @output.string ) end def test_range_requirements @input << "112\n-541\n28\n" @input.rewind answer = @terminal.ask("Tell me your age.", Integer) do |q| q.in = 0..105 end assert_equal(28, answer) assert_equal( "Tell me your age.\n" + "Your answer isn't within the expected range " + "(included in 0..105).\n" + "? " + "Your answer isn't within the expected range " + "(included in 0..105).\n" + "? ", @output.string ) @input.truncate(@input.rewind) @input << "1\n-541\n28\n" @input.rewind @output.truncate(@output.rewind) answer = @terminal.ask("Tell me your age.", Integer) do |q| q.above = 3 end assert_equal(28, answer) assert_equal( "Tell me your age.\n" + "Your answer isn't within the expected range " + "(above 3).\n" + "? " + "Your answer isn't within the expected range " + "(above 3).\n" + "? ", @output.string ) @input.truncate(@input.rewind) @input << "1\n28\n-541\n" @input.rewind @output.truncate(@output.rewind) answer = @terminal.ask("Lowest numer you can think of?", Integer) do |q| q.below = 0 end assert_equal(-541, answer) assert_equal( "Lowest numer you can think of?\n" + "Your answer isn't within the expected range " + "(below 0).\n" + "? " + "Your answer isn't within the expected range " + "(below 0).\n" + "? ", @output.string ) @input.truncate(@input.rewind) @input << "1\n-541\n6\n" @input.rewind @output.truncate(@output.rewind) answer = @terminal.ask("Enter a low even number: ", Integer) do |q| q.above = 0 q.below = 10 q.in = [2, 4, 6, 8] end assert_equal(6, answer) assert_equal( "Enter a low even number: " + "Your answer isn't within the expected range " + "(above 0, below 10, and included in [2, 4, 6, 8]).\n" + "? " + "Your answer isn't within the expected range " + "(above 0, below 10, and included in [2, 4, 6, 8]).\n" + "? ", @output.string ) end def test_reask number = 61676 @input << "Junk!\n" << number << "\n" @input.rewind answer = @terminal.ask("Favorite number? ", Integer) assert_kind_of(Integer, number) assert_instance_of(Fixnum, number) assert_equal(number, answer) assert_equal( "Favorite number? " + "You must enter a valid Integer.\n" + "? ", @output.string ) @input.rewind @output.truncate(@output.rewind) answer = @terminal.ask("Favorite number? ", Integer) do |q| q.responses[:ask_on_error] = :question q.responses[:invalid_type] = "Not a valid number!" end assert_kind_of(Integer, number) assert_instance_of(Fixnum, number) assert_equal(number, answer) assert_equal( "Favorite number? " + "Not a valid number!\n" + "Favorite number? ", @output.string ) @input.truncate(@input.rewind) @input << "gen\ngene\n" @input.rewind @output.truncate(@output.rewind) answer = @terminal.ask("Select a mode: ", [:generate, :gentle]) assert_instance_of(Symbol, answer) assert_equal(:generate, answer) assert_equal( "Select a mode: " + "Ambiguous choice. " + "Please choose one of [:generate, :gentle].\n" + "? ", @output.string ) end def test_response_embedding @input << "112\n-541\n28\n" @input.rewind answer = @terminal.ask("Tell me your age.", Integer) do |q| q.in = 0..105 q.responses[:not_in_range] = "Need a <%= @question.answer_type %>" + " <%= @question.expected_range %>." end assert_equal(28, answer) assert_equal( "Tell me your age.\n" + "Need a Integer included in 0..105.\n" + "? " + "Need a Integer included in 0..105.\n" + "? ", @output.string ) end def test_say @terminal.say("This will have a newline.") assert_equal("This will have a newline.\n", @output.string) @output.truncate(@output.rewind) @terminal.say("This will also have one newline.\n") assert_equal("This will also have one newline.\n", @output.string) @output.truncate(@output.rewind) @terminal.say("This will not have a newline. ") assert_equal("This will not have a newline. ", @output.string) @output.truncate(@output.rewind) @terminal.say("This will not\n end with a newline. ") assert_equal("This will not\n end with a newline. ", @output.string) @output.truncate(@output.rewind) @terminal.say("This will \nend with a newline.") assert_equal("This will \nend with a newline.\n", @output.string) @output.truncate(@output.rewind) colorized = @terminal.color("This will not have a newline. ", :green) @terminal.say(colorized) assert_equal("\e[32mThis will not have a newline. \e[0m", @output.string) @output.truncate(@output.rewind) colorized = @terminal.color("This will have a newline.", :green) @terminal.say(colorized) assert_equal("\e[32mThis will have a newline.\e[0m\n", @output.string) end def test_terminal_size assert_instance_of(Fixnum, @terminal.terminal_size[0]) assert_instance_of(Fixnum, @terminal.terminal_size[1]) end def test_type_conversion number = 61676 @input << number << "\n" @input.rewind answer = @terminal.ask("Favorite number? ", Integer) assert_kind_of(Integer, answer) assert_instance_of(Fixnum, answer) assert_equal(number, answer) @input.truncate(@input.rewind) number = 1_000_000_000_000_000_000_000_000_000_000 @input << number << "\n" @input.rewind answer = @terminal.ask("Favorite number? ", Integer) assert_kind_of(Integer, answer) assert_instance_of(Bignum, answer) assert_equal(number, answer) @input.truncate(@input.rewind) number = 10.5002 @input << number << "\n" @input.rewind answer = @terminal.ask( "Favorite number? ", lambda { |n| n.to_f.abs.round } ) assert_kind_of(Integer, answer) assert_instance_of(Fixnum, answer) assert_equal(11, answer) @input.truncate(@input.rewind) animal = :dog @input << animal << "\n" @input.rewind answer = @terminal.ask("Favorite animal? ", Symbol) assert_instance_of(Symbol, answer) assert_equal(animal, answer) @input.truncate(@input.rewind) @input << "16th June 1976\n" @input.rewind answer = @terminal.ask("Enter your birthday.", Date) assert_instance_of(Date, answer) assert_equal(16, answer.day) assert_equal(6, answer.month) assert_equal(1976, answer.year) @input.truncate(@input.rewind) pattern = "^yes|no$" @input << pattern << "\n" @input.rewind answer = @terminal.ask("Give me a pattern to match with: ", Regexp) assert_instance_of(Regexp, answer) assert_equal(/#{pattern}/, answer) @input.truncate(@input.rewind) @input << "gen\n" @input.rewind answer = @terminal.ask("Select a mode: ", [:generate, :run]) assert_instance_of(Symbol, answer) assert_equal(:generate, answer) end def test_validation @input << "system 'rm -rf /'\n105\n0b101_001\n" @input.rewind answer = @terminal.ask("Enter a binary number: ") do |q| q.validate = /\A(?:0b)?[01_]+\Z/ end assert_equal("0b101_001", answer) assert_equal( "Enter a binary number: " + "Your answer isn't valid " + "(must match /\\A(?:0b)?[01_]+\\Z/).\n" + "? " + "Your answer isn't valid " + "(must match /\\A(?:0b)?[01_]+\\Z/).\n" + "? ", @output.string ) @input.truncate(@input.rewind) @input << "Gray II, James Edward\n" + "Gray, Dana Ann Leslie\n" + "Gray, James Edward\n" @input.rewind answer = @terminal.ask("Your name? ") do |q| q.validate = lambda do |name| names = name.split(/,\s*/) return false unless names.size == 2 return false if names.first =~ /\s/ names.last.split.size == 2 end end assert_equal("Gray, James Edward", answer) end def test_whitespace @input << " A lot\tof \t space\t \there! \n" @input.rewind answer = @terminal.ask("Enter a whitespace filled string: ") do |q| q.whitespace = :chomp end assert_equal(" A lot\tof \t space\t \there! ", answer) @input.rewind answer = @terminal.ask("Enter a whitespace filled string: ") assert_equal("A lot\tof \t space\t \there!", answer) @input.rewind answer = @terminal.ask("Enter a whitespace filled string: ") do |q| q.whitespace = :strip_and_collapse end assert_equal("A lot of space here!", answer) @input.rewind answer = @terminal.ask("Enter a whitespace filled string: ") do |q| q.whitespace = :remove end assert_equal("Alotofspacehere!", answer) @input.rewind answer = @terminal.ask("Enter a whitespace filled string: ") do |q| q.whitespace = :none end assert_equal(" A lot\tof \t space\t \there! \n", answer) end def test_wrap @terminal.wrap_at = 80 @terminal.say("This is a very short line.") assert_equal("This is a very short line.\n", @output.string) @output.truncate(@output.rewind) @terminal.say( "This is a long flowing paragraph meant to span " + "several lines. This text should definitely be " + "wrapped at the set limit, in the result. Your code " + "does well with things like this.\n\n" + " * This is a simple embedded list.\n" + " * You're code should not mess with this...\n" + " * Because it's already formatted correctly and " + "does not\n" + " exceed the limit!" ) assert_equal( "This is a long flowing paragraph meant to span " + "several lines. This text should\n" + "definitely be wrapped at the set limit, in the " + "result. Your code does well with\n" + "things like this.\n\n" + " * This is a simple embedded list.\n" + " * You're code should not mess with this...\n" + " * Because it's already formatted correctly and does " + "not\n" + " exceed the limit!\n", @output.string ) @output.truncate(@output.rewind) @terminal.say("-=" * 50) assert_equal(("-=" * 40 + "\n") + ("-=" * 10 + "\n"), @output.string) end def test_track_eof assert_raise(EOFError) { @terminal.ask("Any input left? ") } # turn EOF tracking old_setting = HighLine.track_eof? assert_nothing_raised(Exception) { HighLine.track_eof = false } begin @terminal.ask("And now? ") # this will still blow up, nothing available rescue assert_not_equal(EOFError, $!.class) # but HighLine's safe guards are off end HighLine.track_eof = old_setting end def test_version assert_not_nil(HighLine::VERSION) assert_instance_of(String, HighLine::VERSION) assert(HighLine::VERSION.frozen?) assert_match(/\A\d+\.\d+\.\d+\Z/, HighLine::VERSION) end end highline-1.6.20/test/tc_style.rb0000755000004100000410000005625212261230137016563 0ustar www-datawww-data# tc_style.rb # # Created by Richard LeBer on 2011-06-11. # # This is Free Software. See LICENSE and COPYING for details. require "test/unit" require "highline" require "stringio" class TestStyle < Test::Unit::TestCase def setup @input = StringIO.new @output = StringIO.new @terminal = HighLine.new(@input, @output) @style1 = HighLine::Style.new(:name=>:foo, :code=>"\e[99m", :rgb=>[1,2,3]) @style2 = HighLine::Style.new(:name=>:lando, :code=>"\e[98m") @style3 = HighLine::Style.new(:name=>[:foo, :lando], :list=>[:foo, :lando]) @style4 = HighLine::Style(:rgb_654321) end def teardown # HighLine::Style.clear_index end def test_style_method # Retrieve a style from an existing Style (no new Style created) new_style = @style1.dup # This will replace @style1 in the indexes s = HighLine.Style(@style1) assert_instance_of HighLine::Style, s assert_same new_style, s # i.e. s===the latest style created, but not the one searched for # Retrieve a style from a new Style (no new Style created) s2 = HighLine::Style.new(:name=>:bar, :code=>"\e[97m") s = HighLine.Style(s2) assert_instance_of HighLine::Style, s assert_same s2, s # Create a builtin style from an existing ANSI escape string s = HighLine.Style("\e[1m") assert_instance_of HighLine::Style, s assert_nil s.list assert_equal "\e[1m", s.code assert_equal :bold, s.name # Create a builtin style from a new ANSI escape string s = HighLine.Style("\e[96m") assert_instance_of HighLine::Style, s assert_nil s.list assert_equal "\e[96m", s.code # Create a builtin style from a symbol s = HighLine.Style(:red) assert_instance_of HighLine::Style, s assert_nil s.list assert_equal :red, s.name # Retrieve an existing style by name (no new Style created) s = HighLine.Style(@style2.name) assert_instance_of HighLine::Style, s assert_same @style2, s # See below for color scheme tests # Create style from a Hash s = HighLine.Style(:name=>:han, :code=>"blah", :rgb=>'phooey') assert_instance_of HighLine::Style, s assert_equal :han, s.name assert_equal "blah", s.code assert_equal "phooey", s.rgb # Create style from an RGB foreground color code s = HighLine.Style(:rgb_1f2e3d) assert_instance_of HighLine::Style, s assert_equal :rgb_1f2e3d, s.name assert_equal "\e[38;5;23m", s.code # Trust me; more testing below assert_equal [31,46,61], s.rgb # 0x1f==31, 0x2e==46, 0x3d=61 # Create style from an RGB background color code s = HighLine.Style(:on_rgb_1f2e3d) assert_instance_of HighLine::Style, s assert_equal :on_rgb_1f2e3d, s.name assert_equal "\e[48;5;23m", s.code # Trust me; more testing below assert_equal [31,46,61], s.rgb # 0x1f==31, 0x2e==46, 0x3d=61 # Create a style list s1 = HighLine.Style(:bold, :red) assert_instance_of HighLine::Style, s1 assert_equal [:bold, :red], s1.list # Find an existing style list s2 = HighLine.Style(:bold, :red) assert_instance_of HighLine::Style, s2 assert_same s1, s2 # Create a style list with nils s1 = HighLine.Style(:underline, nil, :blue) assert_instance_of HighLine::Style, s1 assert_equal [:underline, :blue], s1.list # Raise an error for an undefined style assert_raise(::NameError) { HighLine.Style(:fubar) } end def test_no_color_scheme HighLine.color_scheme = nil assert_raise(::NameError) { HighLine.Style(:critical) } end def test_with_color_scheme HighLine.color_scheme = HighLine::SampleColorScheme.new s = HighLine.Style(:critical) assert_instance_of HighLine::Style, s assert_equal :critical, s.name assert_equal [:yellow, :on_red], s.list end def test_builtin_foreground_colors_defined HighLine::COLORS.each do |color| style = HighLine.const_get(color+'_STYLE') assert_instance_of HighLine::Style, style assert_equal color.downcase.to_sym, style.name assert style.builtin code = HighLine.const_get(color) assert_instance_of String, code, "Bad code for #{color}" end end def test_builtin_background_colors_defined HighLine::COLORS.each do |color| style = HighLine.const_get('ON_' + color+'_STYLE') assert_instance_of HighLine::Style, style assert_equal "ON_#{color}".downcase.to_sym, style.name assert style.builtin code = HighLine.const_get('ON_' + color) assert_instance_of String, code, "Bad code for ON_#{color}" end end def test_builtin_styles_defined HighLine::STYLES.each do |style_constant| style = HighLine.const_get(style_constant+'_STYLE') assert_instance_of HighLine::Style, style assert_equal style_constant.downcase.to_sym, style.name assert style.builtin code = HighLine.const_get(style_constant) assert_instance_of String, code, "Bad code for #{style_constant}" end end def test_index # Add a Style with a new name and code assert_nil HighLine::Style.list[:s1] assert_nil HighLine::Style.code_index['foo'] s1 = HighLine::Style.new(:name=>:s1, :code=>'foo') assert_not_nil HighLine::Style.list[:s1] assert_same s1, HighLine::Style.list[:s1] assert_equal :s1, HighLine::Style.list[:s1].name assert_equal 'foo', HighLine::Style.list[:s1].code styles = HighLine::Style.list.size codes = HighLine::Style.code_index.size assert_instance_of Array, HighLine::Style.code_index['foo'] assert_equal 1, HighLine::Style.code_index['foo'].size assert_same s1, HighLine::Style.code_index['foo'].last assert_equal :s1, HighLine::Style.code_index['foo'].last.name assert_equal 'foo', HighLine::Style.code_index['foo'].last.code # Add another Style with a new name and code assert_nil HighLine::Style.list[:s2] assert_nil HighLine::Style.code_index['bar'] s2 = HighLine::Style.new(:name=>:s2, :code=>'bar') assert_equal styles+1, HighLine::Style.list.size assert_equal codes+1, HighLine::Style.code_index.size assert_not_nil HighLine::Style.list[:s2] assert_same s2, HighLine::Style.list[:s2] assert_equal :s2, HighLine::Style.list[:s2].name assert_equal 'bar', HighLine::Style.list[:s2].code assert_instance_of Array, HighLine::Style.code_index['bar'] assert_equal 1, HighLine::Style.code_index['bar'].size assert_same s2, HighLine::Style.code_index['bar'].last assert_equal :s2, HighLine::Style.code_index['bar'].last.name assert_equal 'bar', HighLine::Style.code_index['bar'].last.code # Add a Style with an existing name s3_before = HighLine::Style.list[:s2] assert_not_nil HighLine::Style.list[:s2] assert_nil HighLine::Style.code_index['baz'] s3 = HighLine::Style.new(:name=>:s2, :code=>'baz') assert_not_same s2, s3 assert_not_same s3_before, s3 assert_equal styles+1, HighLine::Style.list.size assert_equal codes+2, HighLine::Style.code_index.size assert_not_nil HighLine::Style.list[:s2] assert_same s3, HighLine::Style.list[:s2] assert_not_same s2, HighLine::Style.list[:s2] assert_equal :s2, HighLine::Style.list[:s2].name assert_equal 'baz', HighLine::Style.list[:s2].code assert_instance_of Array, HighLine::Style.code_index['baz'] assert_equal 1, HighLine::Style.code_index['baz'].size assert_same s3, HighLine::Style.code_index['baz'].last assert_equal :s2, HighLine::Style.code_index['baz'].last.name assert_equal 'baz', HighLine::Style.code_index['baz'].last.code # Add a Style with an existing code assert_equal 1, HighLine::Style.code_index['baz'].size s4 = HighLine::Style.new(:name=>:s4, :code=>'baz') assert_equal styles+2, HighLine::Style.list.size assert_equal codes+2, HighLine::Style.code_index.size assert_not_nil HighLine::Style.list[:s4] assert_same s4, HighLine::Style.list[:s4] assert_equal :s4, HighLine::Style.list[:s4].name assert_equal 'baz', HighLine::Style.list[:s4].code assert_equal 2, HighLine::Style.code_index['baz'].size assert_same s3, HighLine::Style.code_index['baz'].first # Unchanged from last time assert_equal :s2, HighLine::Style.code_index['baz'].first.name # Unchanged from last time assert_equal 'baz', HighLine::Style.code_index['baz'].first.code # Unchanged from last time assert_same s4, HighLine::Style.code_index['baz'].last assert_equal :s4, HighLine::Style.code_index['baz'].last.name assert_equal 'baz', HighLine::Style.code_index['baz'].last.code end def test_rgb_hex assert_equal "abcdef", HighLine::Style.rgb_hex("abcdef") assert_equal "ABCDEF", HighLine::Style.rgb_hex("AB","CD","EF") assert_equal "010203", HighLine::Style.rgb_hex(1,2,3) assert_equal "123456", HighLine::Style.rgb_hex(18,52,86) end def test_rgb_parts assert_equal [1,2,3], HighLine::Style.rgb_parts("010203") assert_equal [18,52,86], HighLine::Style.rgb_parts("123456") end def test_rgb s = HighLine::Style.rgb(1, 2, 3) assert_instance_of HighLine::Style, s assert_equal :rgb_010203, s.name assert_equal [1,2,3], s.rgb assert_equal "\e[38;5;16m", s.code s = HighLine::Style.rgb("12", "34","56") assert_instance_of HighLine::Style, s assert_equal :rgb_123456, s.name assert_equal [0x12, 0x34, 0x56], s.rgb assert_equal "\e[38;5;24m", s.code s = HighLine::Style.rgb("abcdef") assert_instance_of HighLine::Style, s assert_equal :rgb_abcdef, s.name assert_equal [0xab, 0xcd, 0xef], s.rgb assert_equal "\e[38;5;189m", s.code end def test_rgb_number # ANSI RGB coding splits 0..255 into equal sixths, and then the # red green and blue are encoded in base 6, plus 16, i.e. # 16 + 36*(red_level) + 6*(green_level) + blue_level, # where each of red_level, green_level, and blue_level are in # the range 0..5 # This test logic works because 42 is just below 1/6 of 255, # and 43 is just above assert_equal 16 + 0*36 + 0*6 + 0, HighLine::Style.rgb_number( 0, 0, 0) assert_equal 16 + 0*36 + 0*6 + 0, HighLine::Style.rgb_number( 0, 0, 42) assert_equal 16 + 0*36 + 0*6 + 1, HighLine::Style.rgb_number( 0, 0, 43) assert_equal 16 + 0*36 + 0*6 + 0, HighLine::Style.rgb_number( 0, 42, 0) assert_equal 16 + 0*36 + 0*6 + 0, HighLine::Style.rgb_number( 0, 42, 42) assert_equal 16 + 0*36 + 0*6 + 1, HighLine::Style.rgb_number( 0, 42, 43) assert_equal 16 + 0*36 + 1*6 + 0, HighLine::Style.rgb_number( 0, 43, 0) assert_equal 16 + 0*36 + 1*6 + 0, HighLine::Style.rgb_number( 0, 43, 42) assert_equal 16 + 0*36 + 1*6 + 1, HighLine::Style.rgb_number( 0, 43, 43) assert_equal 16 + 0*36 + 0*6 + 0, HighLine::Style.rgb_number( 42, 0, 0) assert_equal 16 + 0*36 + 0*6 + 0, HighLine::Style.rgb_number( 42, 0, 42) assert_equal 16 + 0*36 + 0*6 + 1, HighLine::Style.rgb_number( 42, 0, 43) assert_equal 16 + 0*36 + 0*6 + 0, HighLine::Style.rgb_number( 42, 42, 0) assert_equal 16 + 0*36 + 0*6 + 0, HighLine::Style.rgb_number( 42, 42, 42) assert_equal 16 + 0*36 + 0*6 + 1, HighLine::Style.rgb_number( 42, 42, 43) assert_equal 16 + 0*36 + 1*6 + 0, HighLine::Style.rgb_number( 42, 43, 0) assert_equal 16 + 0*36 + 1*6 + 0, HighLine::Style.rgb_number( 42, 43, 42) assert_equal 16 + 0*36 + 1*6 + 1, HighLine::Style.rgb_number( 42, 43, 43) assert_equal 16 + 1*36 + 0*6 + 0, HighLine::Style.rgb_number( 43, 0, 0) assert_equal 16 + 1*36 + 0*6 + 0, HighLine::Style.rgb_number( 43, 0, 42) assert_equal 16 + 1*36 + 0*6 + 1, HighLine::Style.rgb_number( 43, 0, 43) assert_equal 16 + 1*36 + 0*6 + 0, HighLine::Style.rgb_number( 43, 42, 0) assert_equal 16 + 1*36 + 0*6 + 0, HighLine::Style.rgb_number( 43, 42, 42) assert_equal 16 + 1*36 + 0*6 + 1, HighLine::Style.rgb_number( 43, 42, 43) assert_equal 16 + 1*36 + 1*6 + 0, HighLine::Style.rgb_number( 43, 43, 0) assert_equal 16 + 1*36 + 1*6 + 0, HighLine::Style.rgb_number( 43, 43, 42) assert_equal 16 + 1*36 + 1*6 + 1, HighLine::Style.rgb_number( 43, 43, 43) assert_equal 16 + 5*36 + 5*6 + 5, HighLine::Style.rgb_number(255,255,255) end def test_ansi_rgb_to_hex assert_equal "000000", HighLine::Style.ansi_rgb_to_hex(16 + 0*36 + 0*6 + 0) assert_equal "000000", HighLine::Style.ansi_rgb_to_hex(16 + 0*36 + 0*6 + 0) assert_equal "00002b", HighLine::Style.ansi_rgb_to_hex(16 + 0*36 + 0*6 + 1) assert_equal "000000", HighLine::Style.ansi_rgb_to_hex(16 + 0*36 + 0*6 + 0) assert_equal "000000", HighLine::Style.ansi_rgb_to_hex(16 + 0*36 + 0*6 + 0) assert_equal "00002b", HighLine::Style.ansi_rgb_to_hex(16 + 0*36 + 0*6 + 1) assert_equal "002b00", HighLine::Style.ansi_rgb_to_hex(16 + 0*36 + 1*6 + 0) assert_equal "002b00", HighLine::Style.ansi_rgb_to_hex(16 + 0*36 + 1*6 + 0) assert_equal "002b2b", HighLine::Style.ansi_rgb_to_hex(16 + 0*36 + 1*6 + 1) assert_equal "000000", HighLine::Style.ansi_rgb_to_hex(16 + 0*36 + 0*6 + 0) assert_equal "000000", HighLine::Style.ansi_rgb_to_hex(16 + 0*36 + 0*6 + 0) assert_equal "00002b", HighLine::Style.ansi_rgb_to_hex(16 + 0*36 + 0*6 + 1) assert_equal "000000", HighLine::Style.ansi_rgb_to_hex(16 + 0*36 + 0*6 + 0) assert_equal "000000", HighLine::Style.ansi_rgb_to_hex(16 + 0*36 + 0*6 + 0) assert_equal "00002b", HighLine::Style.ansi_rgb_to_hex(16 + 0*36 + 0*6 + 1) assert_equal "002b00", HighLine::Style.ansi_rgb_to_hex(16 + 0*36 + 1*6 + 0) assert_equal "002b00", HighLine::Style.ansi_rgb_to_hex(16 + 0*36 + 1*6 + 0) assert_equal "002b2b", HighLine::Style.ansi_rgb_to_hex(16 + 0*36 + 1*6 + 1) assert_equal "2b0000", HighLine::Style.ansi_rgb_to_hex(16 + 1*36 + 0*6 + 0) assert_equal "2b0000", HighLine::Style.ansi_rgb_to_hex(16 + 1*36 + 0*6 + 0) assert_equal "2b002b", HighLine::Style.ansi_rgb_to_hex(16 + 1*36 + 0*6 + 1) assert_equal "2b0000", HighLine::Style.ansi_rgb_to_hex(16 + 1*36 + 0*6 + 0) assert_equal "2b0000", HighLine::Style.ansi_rgb_to_hex(16 + 1*36 + 0*6 + 0) assert_equal "2b002b", HighLine::Style.ansi_rgb_to_hex(16 + 1*36 + 0*6 + 1) assert_equal "2b2b00", HighLine::Style.ansi_rgb_to_hex(16 + 1*36 + 1*6 + 0) assert_equal "2b2b00", HighLine::Style.ansi_rgb_to_hex(16 + 1*36 + 1*6 + 0) assert_equal "2b2b2b", HighLine::Style.ansi_rgb_to_hex(16 + 1*36 + 1*6 + 1) # 0xd5 is the smallest number where n/255.0*6.0 > 5 assert_equal "d5d5d5", HighLine::Style.ansi_rgb_to_hex(16 + 5*36 + 5*6 + 5) end def test_list list_size = HighLine::Style.list.size # Add a Style with a new name and code assert_nil HighLine::Style.list[:s5] s5 = HighLine::Style.new(:name=>:s5, :code=>'foo') assert_not_nil HighLine::Style.list[:s5] assert_equal list_size+1, HighLine::Style.list.size assert_not_nil HighLine::Style.list[:s5] assert_same s5, HighLine::Style.list[:s5] assert_equal :s5, HighLine::Style.list[:s5].name assert_equal 'foo', HighLine::Style.list[:s5].code # Add another Style with a new name and code assert_nil HighLine::Style.list[:s6] s6 = HighLine::Style.new(:name=>:s6, :code=>'bar') assert_equal list_size+2, HighLine::Style.list.size assert_not_nil HighLine::Style.list[:s6] assert_same s6, HighLine::Style.list[:s6] assert_equal :s6, HighLine::Style.list[:s6].name assert_equal 'bar', HighLine::Style.list[:s6].code # Add a Style with an existing name s7 = HighLine::Style.new(:name=>:s6, :code=>'baz') assert_equal list_size+2, HighLine::Style.list.size # No net addition to list assert_not_nil HighLine::Style.list[:s6] assert_same s7, HighLine::Style.list[:s6] # New one replaces old one assert_not_same s6, HighLine::Style.list[:s6] assert_equal :s6, HighLine::Style.list[:s6].name assert_equal 'baz', HighLine::Style.list[:s6].code end def test_code_index list_size = HighLine::Style.code_index.size # Add a Style with a new name and code assert_nil HighLine::Style.code_index['chewie'] HighLine::Style.new(:name=>:s8, :code=>'chewie') assert_equal list_size+1, HighLine::Style.code_index.size assert_instance_of Array, HighLine::Style.code_index['chewie'] assert_equal 1, HighLine::Style.code_index['chewie'].size assert_equal :s8, HighLine::Style.code_index['chewie'].last.name assert_equal 'chewie', HighLine::Style.code_index['chewie'].last.code # Add another Style with a new name and code assert_nil HighLine::Style.code_index['c3po'] HighLine::Style.new(:name=>:s9, :code=>'c3po') assert_equal list_size+2, HighLine::Style.code_index.size assert_instance_of Array, HighLine::Style.code_index['c3po'] assert_equal 1, HighLine::Style.code_index['c3po'].size assert_equal :s9, HighLine::Style.code_index['c3po'].last.name assert_equal 'c3po', HighLine::Style.code_index['c3po'].last.code # Add a Style with an existing code assert_equal 1, HighLine::Style.code_index['c3po'].size HighLine::Style.new(:name=>:s10, :code=>'c3po') assert_equal list_size+2, HighLine::Style.code_index.size assert_equal 2, HighLine::Style.code_index['c3po'].size assert_equal :s10, HighLine::Style.code_index['c3po'].last.name assert_equal 'c3po', HighLine::Style.code_index['c3po'].last.code end def test_uncolor # Normal color assert_equal "This should be reverse underlined magenta!\n", HighLine::Style.uncolor("This should be \e[7m\e[4m\e[35mreverse underlined magenta\e[0m!\n" ) # RGB color assert_equal "This should be rgb_906030!\n", HighLine::Style.uncolor("This should be \e[38;5;137mrgb_906030\e[0m!\n" ) end def test_color assert_equal "\e[99mstring\e[0m", @style1.color("string") # simple style assert_equal "\e[99m\e[98mstring\e[0m", @style3.color("string") # Style list end def test_code assert_equal "\e[99m", @style1.code # simple style assert_equal "\e[99m\e[98m", @style3.code # Style list end def test_red assert_equal 0x65, @style4.red assert_equal 0, HighLine::Style(:none).red # Probably reliable assert_equal 0, HighLine::Style(:black).red # Probably reliable assert_equal 255, HighLine::Style(:bright_magenta).red # Seems to be reliable assert_equal 255, HighLine::Style(:on_none).red # Probably reliable end def test_green assert_equal 0x43, @style4.green assert_equal 0, HighLine::Style(:none).green # Probably reliable assert_equal 0, HighLine::Style(:black).green # Probably reliable assert 240 <= HighLine::Style(:bright_cyan).green # Probably reliable assert_equal 255, HighLine::Style(:on_none).green # Probably reliable end def test_blue assert_equal 0x21, @style4.blue assert_equal 0, HighLine::Style(:none).blue # Probably reliable assert_equal 0, HighLine::Style(:black).blue # Probably reliable assert_equal 255, HighLine::Style(:bright_blue).blue # Probably reliable assert_equal 255, HighLine::Style(:on_none).blue # Probably reliable end def test_builtin assert HighLine::Style(:red).builtin assert !@style1.builtin end def test_variant style1_name = @style1.name style1_code = @style1.code style1_rgb = @style1.rgb s1 = @style1.variant(:new_foo1, :code=>'abracadabra') assert_instance_of HighLine::Style, s1 assert_not_same @style1, s1 # This is a copy assert_equal :new_foo1, s1.name # Changed assert_equal 'abracadabra', s1.code # Changed assert_equal [1,2,3], s1.rgb # Unchanged s2 = @style1.variant(:new_foo2, :increment=>-15) assert_instance_of HighLine::Style, s2 assert_not_same @style1, s2 # This is a copy assert_equal :new_foo2, s2.name # Changed assert_equal "\e[84m", s2.code # 99 (original code) - 15 assert_equal [1,2,3], s2.rgb # Unchanged s3 = @style1.variant(:new_foo3, :code=>"\e[55m", :increment=>15) assert_instance_of HighLine::Style, s3 assert_not_same @style1, s3 # This is a copy assert_equal :new_foo3, s3.name # Changed assert_equal "\e[70m", s3.code # 99 (new code) + 15 assert_equal [1,2,3], s3.rgb # Unchanged s4 = @style1.variant(:new_foo4, :code=>"\e[55m", :increment=>15, :rgb=>"blah") assert_instance_of HighLine::Style, s4 assert_not_same @style1, s4 # This is a copy assert_equal :new_foo4, s4.name # Changed assert_equal "\e[70m", s4.code # 99 (new code) + 15 assert_equal 'blah', s4.rgb # Changed s5 = @style1.variant(:new_foo5) assert_instance_of HighLine::Style, s5 assert_not_same @style1, s5 # This is a copy assert_equal :new_foo5, s5.name # Changed assert_equal "\e[99m", s5.code # Unchanged assert_equal [1,2,3], s5.rgb # Unchanged # No @style1's have been harmed in the running of this test assert_equal style1_name, @style1.name assert_equal style1_code, @style1.code assert_equal style1_rgb, @style1.rgb assert_raise(::RuntimeError) { @style3.variant(:new_foo6) } # Can't create a variant of a list style end def test_on style1_name = @style1.name style1_code = @style1.code style1_rgb = @style1.rgb s1 = @style1.on assert_instance_of HighLine::Style, s1 assert_not_same @style1, s1 # This is a copy assert_equal :on_foo, s1.name # Changed assert_equal "\e[109m", s1.code # Changed assert_equal [1,2,3], s1.rgb # Unchanged # No @style1's have been harmed in the running of this test assert_equal style1_name, @style1.name assert_equal style1_code, @style1.code assert_equal style1_rgb, @style1.rgb assert_raise(::RuntimeError) { @style3.on } # Can't create a variant of a list style end def test_bright style1_name = @style1.name style1_code = @style1.code style1_rgb = @style1.rgb s1 = @style1.bright assert_instance_of HighLine::Style, s1 assert_not_same @style1, s1 # This is a copy assert_equal :bright_foo, s1.name # Changed assert_equal "\e[159m", s1.code # Changed assert_equal [129,130,131], s1.rgb # Changed # No @style1's have been harmed in the running of this test assert_equal style1_name, @style1.name assert_equal style1_code, @style1.code assert_equal style1_rgb, @style1.rgb s2_base = HighLine::Style.new(:name=>:leia, :code=>"\e[92m", :rgb=>[0,0,14]) s2 = s2_base.bright assert_instance_of HighLine::Style, s2 assert_not_same s2_base, s2 # This is a copy assert_equal :bright_leia, s2.name # Changed assert_equal "\e[152m", s2.code # Changed assert_equal [0,0,142], s2.rgb # Changed s3_base = HighLine::Style.new(:name=>:luke, :code=>"\e[93m", :rgb=>[20,21,0]) s3 = s3_base.bright assert_instance_of HighLine::Style, s3 assert_not_same s3_base, s3 # This is a copy assert_equal :bright_luke, s3.name # Changed assert_equal "\e[153m", s3.code # Changed assert_equal [148,149,0], s3.rgb # Changed s4_base = HighLine::Style.new(:name=>:r2d2, :code=>"\e[94m", :rgb=>[0,0,0]) s4 = s4_base.bright assert_instance_of HighLine::Style, s4 assert_not_same s4_base, s4 # This is a copy assert_equal :bright_r2d2, s4.name # Changed assert_equal "\e[154m", s4.code # Changed assert_equal [128,128,128], s4.rgb # Changed; special case assert_raise(::RuntimeError) { @style3.bright } # Can't create a variant of a list style end end highline-1.6.20/test/string_methods.rb0000644000004100000410000000254312261230137017755 0ustar www-datawww-data# string_methods.rb # # Created by Richard LeBer 2011-06-27 # # This is Free Software. See LICENSE and COPYING for details. # # String class convenience methods module StringMethods def test_color assert_equal("\e[34mstring\e[0m", @string.color(:blue)) assert_equal("\e[1m\e[47mstring\e[0m", @string.color(:bold,:on_white)) assert_equal("\e[45mstring\e[0m", @string.on(:magenta)) assert_equal("\e[36mstring\e[0m", @string.cyan) assert_equal("\e[41m\e[5mstring\e[0m\e[0m", @string.blink.on_red) assert_equal("\e[38;5;137mstring\e[0m", @string.color(:rgb_906030)) assert_equal("\e[38;5;101mstring\e[0m", @string.rgb('606030')) assert_equal("\e[38;5;107mstring\e[0m", @string.rgb('60','90','30')) assert_equal("\e[38;5;107mstring\e[0m", @string.rgb(96,144,48)) assert_equal("\e[38;5;173mstring\e[0m", @string.rgb_c06030) assert_equal("\e[48;5;137mstring\e[0m", @string.color(:on_rgb_906030)) assert_equal("\e[48;5;101mstring\e[0m", @string.on_rgb('606030')) assert_equal("\e[48;5;107mstring\e[0m", @string.on_rgb('60','90','30')) assert_equal("\e[48;5;107mstring\e[0m", @string.on_rgb(96,144,48)) assert_equal("\e[48;5;173mstring\e[0m", @string.on_rgb_c06030) end def test_uncolor colored_string = HighLine::String("\e[38;5;137mstring\e[0m") assert_equal "string", colored_string.uncolor end end highline-1.6.20/.gitignore0000644000004100000410000000001012261230137015373 0ustar www-datawww-datadoc pkg highline-1.6.20/AUTHORS0000644000004100000410000000037512261230137014471 0ustar www-datawww-dataJames Edward Gray II:: {james@grayproductions.net}[mailto:james@grayproductions.net] Gregory Brown:: {gregory.t.brown@gmail.com}[mailto:gregory.t.brown@gmail.com] Richard LeBer:: {richard.leber@gmail.com}[mailto:richard.leber@gmail.com]highline-1.6.20/LICENSE0000644000004100000410000000052712261230137014425 0ustar www-datawww-data= License Terms Distributed under the user's choice of the {GPL Version 2}[http://www.gnu.org/licenses/old-licenses/gpl-2.0.html] (see COPYING for details) or the {Ruby software license}[http://www.ruby-lang.org/en/LICENSE.txt] by James Edward Gray II and Greg Brown. Please email James[mailto:james@grayproductions.net] with any questions. highline-1.6.20/setup.rb0000644000004100000410000007106512261230137015112 0ustar www-datawww-data# # setup.rb # # Copyright (c) 2000-2004 Minero Aoki # # This program is free software. # You can distribute/modify this program under the terms of # the GNU LGPL, Lesser General Public License version 2.1. # unless Enumerable.method_defined?(:map) # Ruby 1.4.6 module Enumerable alias map collect end end unless File.respond_to?(:read) # Ruby 1.6 def File.read(fname) open(fname) {|f| return f.read } end end def File.binread(fname) open(fname, 'rb') {|f| return f.read } end # for corrupted windows stat(2) def File.dir?(path) File.directory?((path[-1,1] == '/') ? path : path + '/') end class SetupError < StandardError; end def setup_rb_error(msg) raise SetupError, msg end # # Config # if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } ARGV.delete(arg) require arg.split(/=/, 2)[1] $".push 'rbconfig.rb' else require 'rbconfig' end def multipackage_install? FileTest.directory?(File.dirname($0) + '/packages') end class ConfigItem def initialize(name, template, default, desc) @name = name.freeze @template = template @value = default @default = default.dup.freeze @description = desc end attr_reader :name attr_reader :description attr_accessor :default alias help_default default def help_opt "--#{@name}=#{@template}" end def value @value end def eval(table) @value.gsub(%r<\$([^/]+)>) { table[$1] } end def set(val) @value = check(val) end private def check(val) setup_rb_error "config: --#{name} requires argument" unless val val end end class BoolItem < ConfigItem def config_type 'bool' end def help_opt "--#{@name}" end private def check(val) return 'yes' unless val unless /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i =~ val setup_rb_error "config: --#{@name} accepts only yes/no for argument" end (/\Ay(es)?|\At(rue)/i =~ value) ? 'yes' : 'no' end end class PathItem < ConfigItem def config_type 'path' end private def check(path) setup_rb_error "config: --#{@name} requires argument" unless path path[0,1] == '$' ? path : File.expand_path(path) end end class ProgramItem < ConfigItem def config_type 'program' end end class SelectItem < ConfigItem def initialize(name, template, default, desc) super @ok = template.split('/') end def config_type 'select' end private def check(val) unless @ok.include?(val.strip) setup_rb_error "config: use --#{@name}=#{@template} (#{val})" end val.strip end end class PackageSelectionItem < ConfigItem def initialize(name, template, default, help_default, desc) super name, template, default, desc @help_default = help_default end attr_reader :help_default def config_type 'package' end private def check(val) unless File.dir?("packages/#{val}") setup_rb_error "config: no such package: #{val}" end val end end class ConfigTable_class def initialize(items) @items = items @table = {} items.each do |i| @table[i.name] = i end ALIASES.each do |ali, name| @table[ali] = @table[name] end end include Enumerable def each(&block) @items.each(&block) end def key?(name) @table.key?(name) end def lookup(name) @table[name] or raise ArgumentError, "no such config item: #{name}" end def add(item) @items.push item @table[item.name] = item end def remove(name) item = lookup(name) @items.delete_if {|i| i.name == name } @table.delete_if {|name, i| i.name == name } item end def new dup() end def savefile '.config' end def load begin t = dup() File.foreach(savefile()) do |line| k, v = *line.split(/=/, 2) t[k] = v.strip end t rescue Errno::ENOENT setup_rb_error $!.message + "#{File.basename($0)} config first" end end def save @items.each {|i| i.value } File.open(savefile(), 'w') {|f| @items.each do |i| f.printf "%s=%s\n", i.name, i.value if i.value end } end def [](key) lookup(key).eval(self) end def []=(key, val) lookup(key).set val end end c = ::Config::CONFIG rubypath = c['bindir'] + '/' + c['ruby_install_name'] major = c['MAJOR'].to_i minor = c['MINOR'].to_i teeny = c['TEENY'].to_i version = "#{major}.#{minor}" # ruby ver. >= 1.4.4? newpath_p = ((major >= 2) or ((major == 1) and ((minor >= 5) or ((minor == 4) and (teeny >= 4))))) if c['rubylibdir'] # V < 1.6.3 _stdruby = c['rubylibdir'] _siteruby = c['sitedir'] _siterubyver = c['sitelibdir'] _siterubyverarch = c['sitearchdir'] elsif newpath_p # 1.4.4 <= V <= 1.6.3 _stdruby = "$prefix/lib/ruby/#{version}" _siteruby = c['sitedir'] _siterubyver = "$siteruby/#{version}" _siterubyverarch = "$siterubyver/#{c['arch']}" else # V < 1.4.4 _stdruby = "$prefix/lib/ruby/#{version}" _siteruby = "$prefix/lib/ruby/#{version}/site_ruby" _siterubyver = _siteruby _siterubyverarch = "$siterubyver/#{c['arch']}" end libdir = '-* dummy libdir *-' stdruby = '-* dummy rubylibdir *-' siteruby = '-* dummy site_ruby *-' siterubyver = '-* dummy site_ruby version *-' parameterize = lambda {|path| path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')\ .sub(/\A#{Regexp.quote(libdir)}/, '$libdir')\ .sub(/\A#{Regexp.quote(stdruby)}/, '$stdruby')\ .sub(/\A#{Regexp.quote(siteruby)}/, '$siteruby')\ .sub(/\A#{Regexp.quote(siterubyver)}/, '$siterubyver') } libdir = parameterize.call(c['libdir']) stdruby = parameterize.call(_stdruby) siteruby = parameterize.call(_siteruby) siterubyver = parameterize.call(_siterubyver) siterubyverarch = parameterize.call(_siterubyverarch) if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } makeprog = arg.sub(/'/, '').split(/=/, 2)[1] else makeprog = 'make' end common_conf = [ PathItem.new('prefix', 'path', c['prefix'], 'path prefix of target environment'), PathItem.new('bindir', 'path', parameterize.call(c['bindir']), 'the directory for commands'), PathItem.new('libdir', 'path', libdir, 'the directory for libraries'), PathItem.new('datadir', 'path', parameterize.call(c['datadir']), 'the directory for shared data'), PathItem.new('mandir', 'path', parameterize.call(c['mandir']), 'the directory for man pages'), PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), 'the directory for man pages'), PathItem.new('stdruby', 'path', stdruby, 'the directory for standard ruby libraries'), PathItem.new('siteruby', 'path', siteruby, 'the directory for version-independent aux ruby libraries'), PathItem.new('siterubyver', 'path', siterubyver, 'the directory for aux ruby libraries'), PathItem.new('siterubyverarch', 'path', siterubyverarch, 'the directory for aux ruby binaries'), PathItem.new('rbdir', 'path', '$siterubyver', 'the directory for ruby scripts'), PathItem.new('sodir', 'path', '$siterubyverarch', 'the directory for ruby extentions'), PathItem.new('rubypath', 'path', rubypath, 'the path to set to #! line'), ProgramItem.new('rubyprog', 'name', rubypath, 'the ruby program using for installation'), ProgramItem.new('makeprog', 'name', makeprog, 'the make program to compile ruby extentions'), SelectItem.new('shebang', 'all/ruby/never', 'ruby', 'shebang line (#!) editing mode'), BoolItem.new('without-ext', 'yes/no', 'no', 'does not compile/install ruby extentions') ] class ConfigTable_class # open again ALIASES = { 'std-ruby' => 'stdruby', 'site-ruby-common' => 'siteruby', # For backward compatibility 'site-ruby' => 'siterubyver', # For backward compatibility 'bin-dir' => 'bindir', 'bin-dir' => 'bindir', 'rb-dir' => 'rbdir', 'so-dir' => 'sodir', 'data-dir' => 'datadir', 'ruby-path' => 'rubypath', 'ruby-prog' => 'rubyprog', 'ruby' => 'rubyprog', 'make-prog' => 'makeprog', 'make' => 'makeprog' } end multipackage_conf = [ PackageSelectionItem.new('with', 'name,name...', '', 'ALL', 'package names that you want to install'), PackageSelectionItem.new('without', 'name,name...', '', 'NONE', 'package names that you do not want to install') ] if multipackage_install? ConfigTable = ConfigTable_class.new(common_conf + multipackage_conf) else ConfigTable = ConfigTable_class.new(common_conf) end module MetaConfigAPI def eval_file_ifexist(fname) instance_eval File.read(fname), fname, 1 if File.file?(fname) end def config_names ConfigTable.map {|i| i.name } end def config?(name) ConfigTable.key?(name) end def bool_config?(name) ConfigTable.lookup(name).config_type == 'bool' end def path_config?(name) ConfigTable.lookup(name).config_type == 'path' end def value_config?(name) case ConfigTable.lookup(name).config_type when 'bool', 'path' true else false end end def add_config(item) ConfigTable.add item end def add_bool_config(name, default, desc) ConfigTable.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) end def add_path_config(name, default, desc) ConfigTable.add PathItem.new(name, 'path', default, desc) end def set_config_default(name, default) ConfigTable.lookup(name).default = default end def remove_config(name) ConfigTable.remove(name) end end # # File Operations # module FileOperations def mkdir_p(dirname, prefix = nil) dirname = prefix + File.expand_path(dirname) if prefix $stderr.puts "mkdir -p #{dirname}" if verbose? return if no_harm? # does not check '/'... it's too abnormal case dirs = File.expand_path(dirname).split(%r<(?=/)>) if /\A[a-z]:\z/i =~ dirs[0] disk = dirs.shift dirs[0] = disk + dirs[0] end dirs.each_index do |idx| path = dirs[0..idx].join('') Dir.mkdir path unless File.dir?(path) end end def rm_f(fname) $stderr.puts "rm -f #{fname}" if verbose? return if no_harm? if File.exist?(fname) or File.symlink?(fname) File.chmod 0777, fname File.unlink fname end end def rm_rf(dn) $stderr.puts "rm -rf #{dn}" if verbose? return if no_harm? Dir.chdir dn Dir.foreach('.') do |fn| next if fn == '.' next if fn == '..' if File.dir?(fn) verbose_off { rm_rf fn } else verbose_off { rm_f fn } end end Dir.chdir '..' Dir.rmdir dn end def move_file(src, dest) File.unlink dest if File.exist?(dest) begin File.rename src, dest rescue File.open(dest, 'wb') {|f| f.write File.binread(src) } File.chmod File.stat(src).mode, dest File.unlink src end end def install(from, dest, mode, prefix = nil) $stderr.puts "install #{from} #{dest}" if verbose? return if no_harm? realdest = prefix ? prefix + File.expand_path(dest) : dest realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) str = File.binread(from) if diff?(str, realdest) verbose_off { rm_f realdest if File.exist?(realdest) } File.open(realdest, 'wb') {|f| f.write str } File.chmod mode, realdest File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| if prefix f.puts realdest.sub(prefix, '') else f.puts realdest end } end end def diff?(new_content, path) return true unless File.exist?(path) new_content != File.binread(path) end def command(str) $stderr.puts str if verbose? system str or raise RuntimeError, "'system #{str}' failed" end def ruby(str) command config('rubyprog') + ' ' + str end def make(task = '') command config('makeprog') + ' ' + task end def extdir?(dir) File.exist?(dir + '/MANIFEST') end def all_files_in(dirname) Dir.open(dirname) {|d| return d.select {|ent| File.file?("#{dirname}/#{ent}") } } end REJECT_DIRS = %w( CVS SCCS RCS CVS.adm .svn ) def all_dirs_in(dirname) Dir.open(dirname) {|d| return d.select {|n| File.dir?("#{dirname}/#{n}") } - %w(. ..) - REJECT_DIRS } end end # # Main Installer # module HookUtils def run_hook(name) try_run_hook "#{curr_srcdir()}/#{name}" or try_run_hook "#{curr_srcdir()}/#{name}.rb" end def try_run_hook(fname) return false unless File.file?(fname) begin instance_eval File.read(fname), fname, 1 rescue setup_rb_error "hook #{fname} failed:\n" + $!.message end true end end module HookScriptAPI def get_config(key) @config[key] end alias config get_config def set_config(key, val) @config[key] = val end # # srcdir/objdir (works only in the package directory) # #abstract srcdir_root #abstract objdir_root #abstract relpath def curr_srcdir "#{srcdir_root()}/#{relpath()}" end def curr_objdir "#{objdir_root()}/#{relpath()}" end def srcfile(path) "#{curr_srcdir()}/#{path}" end def srcexist?(path) File.exist?(srcfile(path)) end def srcdirectory?(path) File.dir?(srcfile(path)) end def srcfile?(path) File.file? srcfile(path) end def srcentries(path = '.') Dir.open("#{curr_srcdir()}/#{path}") {|d| return d.to_a - %w(. ..) } end def srcfiles(path = '.') srcentries(path).select {|fname| File.file?(File.join(curr_srcdir(), path, fname)) } end def srcdirectories(path = '.') srcentries(path).select {|fname| File.dir?(File.join(curr_srcdir(), path, fname)) } end end class ToplevelInstaller Version = '3.3.1' Copyright = 'Copyright (c) 2000-2004 Minero Aoki' TASKS = [ [ 'all', 'do config, setup, then install' ], [ 'config', 'saves your configurations' ], [ 'show', 'shows current configuration' ], [ 'setup', 'compiles ruby extentions and others' ], [ 'install', 'installs files' ], [ 'clean', "does `make clean' for each extention" ], [ 'distclean',"does `make distclean' for each extention" ] ] def ToplevelInstaller.invoke instance().invoke end @singleton = nil def ToplevelInstaller.instance @singleton ||= new(File.dirname($0)) @singleton end include MetaConfigAPI def initialize(ardir_root) @config = nil @options = { 'verbose' => true } @ardir = File.expand_path(ardir_root) end def inspect "#<#{self.class} #{__id__()}>" end def invoke run_metaconfigs case task = parsearg_global() when nil, 'all' @config = load_config('config') parsearg_config init_installers exec_config exec_setup exec_install else @config = load_config(task) __send__ "parsearg_#{task}" init_installers __send__ "exec_#{task}" end end def run_metaconfigs eval_file_ifexist "#{@ardir}/metaconfig" end def load_config(task) case task when 'config' ConfigTable.new when 'clean', 'distclean' if File.exist?(ConfigTable.savefile) then ConfigTable.load else ConfigTable.new end else ConfigTable.load end end def init_installers @installer = Installer.new(@config, @options, @ardir, File.expand_path('.')) end # # Hook Script API bases # def srcdir_root @ardir end def objdir_root '.' end def relpath '.' end # # Option Parsing # def parsearg_global valid_task = /\A(?:#{TASKS.map {|task,desc| task }.join '|'})\z/ while arg = ARGV.shift case arg when /\A\w+\z/ setup_rb_error "invalid task: #{arg}" unless valid_task =~ arg return arg when '-q', '--quiet' @options['verbose'] = false when '--verbose' @options['verbose'] = true when '-h', '--help' print_usage $stdout exit 0 when '-v', '--version' puts "#{File.basename($0)} version #{Version}" exit 0 when '--copyright' puts Copyright exit 0 else setup_rb_error "unknown global option '#{arg}'" end end nil end def parsearg_no_options unless ARGV.empty? setup_rb_error "#{task}: unknown options: #{ARGV.join ' '}" end end alias parsearg_show parsearg_no_options alias parsearg_setup parsearg_no_options alias parsearg_clean parsearg_no_options alias parsearg_distclean parsearg_no_options def parsearg_config re = /\A--(#{ConfigTable.map {|i| i.name }.join('|')})(?:=(.*))?\z/ @options['config-opt'] = [] while i = ARGV.shift if /\A--?\z/ =~ i @options['config-opt'] = ARGV.dup break end m = re.match(i) or setup_rb_error "config: unknown option #{i}" name, value = *m.to_a[1,2] @config[name] = value end end def parsearg_install @options['no-harm'] = false @options['install-prefix'] = '' while a = ARGV.shift case a when /\A--no-harm\z/ @options['no-harm'] = true when /\A--prefix=(.*)\z/ path = $1 path = File.expand_path(path) unless path[0,1] == '/' @options['install-prefix'] = path else setup_rb_error "install: unknown option #{a}" end end end def print_usage(out) out.puts 'Typical Installation Procedure:' out.puts " $ ruby #{File.basename $0} config" out.puts " $ ruby #{File.basename $0} setup" out.puts " # ruby #{File.basename $0} install (may require root privilege)" out.puts out.puts 'Detailed Usage:' out.puts " ruby #{File.basename $0} " out.puts " ruby #{File.basename $0} [] []" fmt = " %-24s %s\n" out.puts out.puts 'Global options:' out.printf fmt, '-q,--quiet', 'suppress message outputs' out.printf fmt, ' --verbose', 'output messages verbosely' out.printf fmt, '-h,--help', 'print this message' out.printf fmt, '-v,--version', 'print version and quit' out.printf fmt, ' --copyright', 'print copyright and quit' out.puts out.puts 'Tasks:' TASKS.each do |name, desc| out.printf fmt, name, desc end fmt = " %-24s %s [%s]\n" out.puts out.puts 'Options for CONFIG or ALL:' ConfigTable.each do |item| out.printf fmt, item.help_opt, item.description, item.help_default end out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" out.puts out.puts 'Options for INSTALL:' out.printf fmt, '--no-harm', 'only display what to do if given', 'off' out.printf fmt, '--prefix=path', 'install path prefix', '$prefix' out.puts end # # Task Handlers # def exec_config @installer.exec_config @config.save # must be final end def exec_setup @installer.exec_setup end def exec_install @installer.exec_install end def exec_show ConfigTable.each do |i| printf "%-20s %s\n", i.name, i.value end end def exec_clean @installer.exec_clean end def exec_distclean @installer.exec_distclean end end class ToplevelInstallerMulti < ToplevelInstaller include HookUtils include HookScriptAPI include FileOperations def initialize(ardir) super @packages = all_dirs_in("#{@ardir}/packages") raise 'no package exists' if @packages.empty? end def run_metaconfigs eval_file_ifexist "#{@ardir}/metaconfig" @packages.each do |name| eval_file_ifexist "#{@ardir}/packages/#{name}/metaconfig" end end def init_installers @installers = {} @packages.each do |pack| @installers[pack] = Installer.new(@config, @options, "#{@ardir}/packages/#{pack}", "packages/#{pack}") end with = extract_selection(config('with')) without = extract_selection(config('without')) @selected = @installers.keys.select {|name| (with.empty? or with.include?(name)) \ and not without.include?(name) } end def extract_selection(list) a = list.split(/,/) a.each do |name| setup_rb_error "no such package: #{name}" unless @installers.key?(name) end a end def print_usage(f) super f.puts 'Inluded packages:' f.puts ' ' + @packages.sort.join(' ') f.puts end # # multi-package metaconfig API # attr_reader :packages def declare_packages(list) raise 'package list is empty' if list.empty? list.each do |name| raise "directory packages/#{name} does not exist"\ unless File.dir?("#{@ardir}/packages/#{name}") end @packages = list end # # Task Handlers # def exec_config run_hook 'pre-config' each_selected_installers {|inst| inst.exec_config } run_hook 'post-config' @config.save # must be final end def exec_setup run_hook 'pre-setup' each_selected_installers {|inst| inst.exec_setup } run_hook 'post-setup' end def exec_install run_hook 'pre-install' each_selected_installers {|inst| inst.exec_install } run_hook 'post-install' end def exec_clean rm_f ConfigTable.savefile run_hook 'pre-clean' each_selected_installers {|inst| inst.exec_clean } run_hook 'post-clean' end def exec_distclean rm_f ConfigTable.savefile run_hook 'pre-distclean' each_selected_installers {|inst| inst.exec_distclean } run_hook 'post-distclean' end # # lib # def each_selected_installers Dir.mkdir 'packages' unless File.dir?('packages') @selected.each do |pack| $stderr.puts "Processing the package `#{pack}' ..." if @options['verbose'] Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") Dir.chdir "packages/#{pack}" yield @installers[pack] Dir.chdir '../..' end end def verbose? @options['verbose'] end def no_harm? @options['no-harm'] end end class Installer FILETYPES = %w( bin lib ext data ) include HookScriptAPI include HookUtils include FileOperations def initialize(config, opt, srcroot, objroot) @config = config @options = opt @srcdir = File.expand_path(srcroot) @objdir = File.expand_path(objroot) @currdir = '.' end def inspect "#<#{self.class} #{File.basename(@srcdir)}>" end # # Hook Script API base methods # def srcdir_root @srcdir end def objdir_root @objdir end def relpath @currdir end # # configs/options # def no_harm? @options['no-harm'] end def verbose? @options['verbose'] end def verbose_off begin save, @options['verbose'] = @options['verbose'], false yield ensure @options['verbose'] = save end end # # TASK config # def exec_config exec_task_traverse 'config' end def config_dir_bin(rel) end def config_dir_lib(rel) end def config_dir_ext(rel) extconf if extdir?(curr_srcdir()) end def extconf opt = @options['config-opt'].join(' ') command "#{config('rubyprog')} #{curr_srcdir()}/extconf.rb #{opt}" end def config_dir_data(rel) end # # TASK setup # def exec_setup exec_task_traverse 'setup' end def setup_dir_bin(rel) all_files_in(curr_srcdir()).each do |fname| adjust_shebang "#{curr_srcdir()}/#{fname}" end end def adjust_shebang(path) return if no_harm? tmpfile = File.basename(path) + '.tmp' begin File.open(path, 'rb') {|r| first = r.gets return unless File.basename(config('rubypath')) == 'ruby' return unless File.basename(first.sub(/\A\#!/, '').split[0]) == 'ruby' $stderr.puts "adjusting shebang: #{File.basename(path)}" if verbose? File.open(tmpfile, 'wb') {|w| w.print first.sub(/\A\#!\s*\S+/, '#! ' + config('rubypath')) w.write r.read } move_file tmpfile, File.basename(path) } ensure File.unlink tmpfile if File.exist?(tmpfile) end end def setup_dir_lib(rel) end def setup_dir_ext(rel) make if extdir?(curr_srcdir()) end def setup_dir_data(rel) end # # TASK install # def exec_install rm_f 'InstalledFiles' exec_task_traverse 'install' end def install_dir_bin(rel) install_files collect_filenames_auto(), "#{config('bindir')}/#{rel}", 0755 end def install_dir_lib(rel) install_files ruby_scripts(), "#{config('rbdir')}/#{rel}", 0644 end def install_dir_ext(rel) return unless extdir?(curr_srcdir()) install_files ruby_extentions('.'), "#{config('sodir')}/#{File.dirname(rel)}", 0555 end def install_dir_data(rel) install_files collect_filenames_auto(), "#{config('datadir')}/#{rel}", 0644 end def install_files(list, dest, mode) mkdir_p dest, @options['install-prefix'] list.each do |fname| install fname, dest, mode, @options['install-prefix'] end end def ruby_scripts collect_filenames_auto().select {|n| /\.rb\z/ =~ n } end # picked up many entries from cvs-1.11.1/src/ignore.c reject_patterns = %w( core RCSLOG tags TAGS .make.state .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb *~ *.old *.bak *.BAK *.orig *.rej _$* *$ *.org *.in .* ) mapping = { '.' => '\.', '$' => '\$', '#' => '\#', '*' => '.*' } REJECT_PATTERNS = Regexp.new('\A(?:' + reject_patterns.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| mapping[ch] } }.join('|') + ')\z') def collect_filenames_auto mapdir((existfiles() - hookfiles()).reject {|fname| REJECT_PATTERNS =~ fname }) end def existfiles all_files_in(curr_srcdir()) | all_files_in('.') end def hookfiles %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| %w( config setup install clean ).map {|t| sprintf(fmt, t) } }.flatten end def mapdir(filelist) filelist.map {|fname| if File.exist?(fname) # objdir fname else # srcdir File.join(curr_srcdir(), fname) end } end def ruby_extentions(dir) Dir.open(dir) {|d| ents = d.select {|fname| /\.#{::Config::CONFIG['DLEXT']}\z/ =~ fname } if ents.empty? setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" end return ents } end # # TASK clean # def exec_clean exec_task_traverse 'clean' rm_f ConfigTable.savefile rm_f 'InstalledFiles' end def clean_dir_bin(rel) end def clean_dir_lib(rel) end def clean_dir_ext(rel) return unless extdir?(curr_srcdir()) make 'clean' if File.file?('Makefile') end def clean_dir_data(rel) end # # TASK distclean # def exec_distclean exec_task_traverse 'distclean' rm_f ConfigTable.savefile rm_f 'InstalledFiles' end def distclean_dir_bin(rel) end def distclean_dir_lib(rel) end def distclean_dir_ext(rel) return unless extdir?(curr_srcdir()) make 'distclean' if File.file?('Makefile') end # # lib # def exec_task_traverse(task) run_hook "pre-#{task}" FILETYPES.each do |type| if config('without-ext') == 'yes' and type == 'ext' $stderr.puts 'skipping ext/* by user option' if verbose? next end traverse task, type, "#{task}_dir_#{type}" end run_hook "post-#{task}" end def traverse(task, rel, mid) dive_into(rel) { run_hook "pre-#{task}" __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') all_dirs_in(curr_srcdir()).each do |d| traverse task, "#{rel}/#{d}", mid end run_hook "post-#{task}" } end def dive_into(rel) return unless File.dir?("#{@srcdir}/#{rel}") dir = File.basename(rel) Dir.mkdir dir unless File.dir?(dir) prevdir = Dir.pwd Dir.chdir dir $stderr.puts '---> ' + rel if verbose? @currdir = rel yield Dir.chdir prevdir $stderr.puts '<--- ' + rel if verbose? @currdir = File.dirname(rel) end end if $0 == __FILE__ begin if multipackage_install? ToplevelInstallerMulti.invoke else ToplevelInstaller.invoke end rescue SetupError raise if $DEBUG $stderr.puts $!.message $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." exit 1 end end highline-1.6.20/TODO0000644000004100000410000000016612261230137014107 0ustar www-datawww-data= To Do List The following is a list of planned expansions for HighLine, in no particular order. * Rent this space. highline-1.6.20/site/0000755000004100000410000000000012261230137014360 5ustar www-datawww-datahighline-1.6.20/site/images/0000755000004100000410000000000012261230137015625 5ustar www-datawww-datahighline-1.6.20/site/images/logo.png0000644000004100000410000042260712261230137017306 0ustar www-datawww-dataPNG  IHDRbKGD pHYs  tIME"l IDATxܽMm]v4\k}νꫲ]+DP`$#HAK @BRDn"H9VFb!-BH *W.31){kIcε}+SOzzw~Zc9Ƙ?__YUD䭜+UêgE'OѧTmUU5Q""?+"7ϫ}nkmsW_zNoޫmE}wwk} UE̼]_rsDy~}o#" h@ "A1a8=[?I|)|۾y N9BPif&;ޯBE k{_ 8⭇W8F$+M80< yWo0bHyY@Qd@f>?BT! tY]Jwlvľ׬1ZJAkq{]b~_1{Q>_sb벪2Hvmֵ_JL[ -b |DWC7-|7!"Wa`}{ T"f_lxGM>*z`mڽcG{_gkj\e@ 1ss@RFfep)m}DɈLFtٸ`?oD$+'Og<#1h&w-?B d<>$NUF" mL pR FDq~ -3(3T x !H )!< NW9#O3J sF|/('Sx"\"ʄ,@ k7ЪPN(<`. ξ D ë3>DiRFcAdB+8T';A( RTW01 "@.!Zp~(L)Tb*( CLj8 v*~`^Q(h")y!`ߗģY9C ; 0t:+T+bfƨT7 R9xa-XzQ}o>b U+Fu!ہ2^wfVZJF !zƁS 7fCrT Y 'V1 B덈0 1g-n_eOHHC^BbrSVPjTP"`*:[k8(ms-*;΂6ֿJܬGvroLߢ an.I]QI{{v5_o m{ϴi)8VRK{/HJZ8nA2mN%-k#;h߳YagR;=AE)w #/<]G?_"VU(OcE֖2[@K$ 则a#͖>bxoѱ{7{=&Y'[0}m3"c@r%pP}XAӄ> .G 8"dgֈdyX@{u;qL2cpP',E>-G?xF#RqĊa4`l.{M)9RD 10D לR|} R0_+_GHv?%g((3PXqcZ@k˱1t6v A"-z=]Cl8;}Pԥ гi<{@:u<^X6bt%od9`D}e^A3-د\28 80gg؃K ɇ*35rE\QJc+Hծ&uH$.~C! 1s?5WoH:WXtWH^!kڎ*a$ @E}XPl M#Z7 ]_O?_ꎿD~}E]mQo݃JVs/.UT|G w uLJ-RETFkrvOQ [ Rj "Dsw [7žկ@p@D ϸ}CdDŽq l:@ (P)feLYQ!b 4]Iq>'S*5>|4y "W(2(hcVƜO]qy(3|>Ɯ3i6I@(N?|LrA{o40lbbL tiBEM/98-(9#_'\^] Hl-:A kUKq3Z7bEZn$YBU@)oU3huՑ{zvj^F@x>gHQ!"!!"05ϠB^! ;31%lÀ33t>#y +(ץ(2lV0L~;^ N!AЂR2J O [`e{ໂwíAde-&2^q==SivT>:ҿ=wOw d7c9Rb@n-Y.6М%N#Bȗ"" ߱Mi[ٵ&a _g+4$ -@qfzjqaZb.fcA0.!#ς˄g::>SbJ%[:FP02!Q0d7 JVFJ1t-Pyvq$< 4\g{@7+Dc$JvxrYdUAFUt=Cq~0o$"Kۘu" 9пV)6KX)0A|Vv e$gDxڻ^;Uj" 'g VIথS^XKӳ,U9 ;-W؁NH1x*- [KB@%v U^$Y*INjN"Y4տU*׵Ѽ ^tWLyOJ'i_kW{ܐƊ=vS*n9]='O?ܿ!NU }W+j:imw{έaˌn5g/f- +_2h"g`\G戡~K7fy)_͒<[QBG  Tvab!O R8AI Hego E0W23øt 0bJI<4z9!"8btNLׯQ"<+>D(Ą(aXq~~?!g˸?`s:a3Ha2&'2#j 쌠̝]&I!ugm 3HPoK6:%-b̹r \uPeyq&i1u7 PLZoV,yPe7@IMZZEI /@R((%B`W%1Q\IM5Oe(Y'M6-YJJ dwf Y!S"89(Y;,䭌bqlܐm pv2׷+K@Ά ;U(3J\J}4w4N+6Dht:vVD0ճpSAwEʘ /tQ+u\;N^{u+;A ֙FJAp`:I1p\ݎFxD}]]}R˭|QQI馏E? ?cG#t[ -S[a%vo߳<8߂ӽM#^䛂׾%H;1èu -buCG21!x6!0&'diP6OEr3f̈9rc)g"1NXaל-# S:-~t;iR'<#+. H)zE`&D6V] Rkz+4!q8 X !iyBWOͨ Qp)B'0 S@z-H0M@}La'LӄRfcuc'6Q:! t/eB/f4#*,[\J@kƾ ~'Koik]{5#_+c%^Akmbriz{{ʇ~@Qe66FYv<*#KR fb̲*;]dY&xfoJR2Y4Djj;ciiUyLB3DWHu6[F]_6k'A5@4XzTB˕ZK7N cUBM(.F%_ !ĔLVD b-O~M^ ?TiѷDJfM6 F6,'_XWZ/3IYV½nAKNi  IDATT u|@푕0 t7 5gj-lVfB=;8!ٸ"eWK=>П!wW>*_7"NUpR3"Gvo=`5@v*Kɓ`L^yB/b=BI&YO14F!@4cgP0IM 1{O>1UU옧d)!~τj) HCBL#b)XWlZfH) ,c4HK]: ?y9ئQZBKV@DuoN\vo)0봅+H: 3k š-uy ~MRi'Km$uK+YA]Vg0)MMpZh)*]Z ji*JK<A2' 9#\;˽C2j2Rc1+|X=*]teFJ>%ے_]\ } Eծ8Ч8"g1NkmPR k3V;]cܿ/ Q"=vpmY#O^9s1VT{҈-Sexg !T}d}:YFgPdLڧ39L} Gvauim;ba1160=*b(FƷA<4#g4>b[^p:pDfa /yfjtcicNip=R~ŝ>zuiԬҞRTԓЊ8 W2Ku Oj7cz|r3&iDŽ/yrDԒ2)5wk*B RZ9]̨M;+KT nhHb N01H\9i0 &ҮbHDȞTU}I7I:ϨOD?o|MY? 3{K{`{я/Iu=Cct{r!VdMbq:*D}=Q 힩Mu{ad7i?"!|`Y/#e{gS55.IZ+jN:UUQ+ V̫OL1My}5b*"Ԇ+y޺XѬK&})<͘+斤U=BZbTNu&7K:^e)fnulљCLvP@PtBh_=%Zc=mz ʁoFhͱ/ nogw瘯ľ.6=g7P]MŨ_.k{ ~kIm-yDh `9$ftmpZr7EDmt%j7z`T?V)]UKxQ2﷣o^r[}XBRZv$&︧a~=OkYO)%Gi/go8֛䖃>{7a_Ġ)01Q!w`f60kI8 RL`>A <>Z&:&*!Dh.̱*o#tPiʘfӈӌ2M(O]6If L>ghP i0 \ èu\"-m  " I<#@9\.Efܭl\`RCyי!E@P 3( 4xCd`b6VUQ @X<=_D@eK|hlue.ȥC[ά6hٴ7Rɝɯ(;y][?ӀFP˝ÒF:wʈVM ]RĒjk$ޯI6YlW|Ύbp k1K^n?7ŏZH`r.nv y)&uRbQWY7FNQy-</Q1YrΜ'0 [foyJ@p:RO/.J*< J x}FEsy y6p1uXl3ͤKZ1HB[QdQ 珞l֭ vdP5Q|5p_=G72)赎4{NߗQG:#&w[0|*+2}M}JM<7,}W`OzrThmAXG$9wbs/5C:CU\[mNd,x5d6!C'Oldcb HHc$⦋5&5x/sAW і0 )Y<:4 e*x~)1bEM:P-fT<"2o0r)gE㫙2!ds,؀,%"kqsd>%0h,L1sFlHkbI*fC p<%4h@p]F-f(x D7U9#dRh]b{K7q1ӀEpͯ4IbZE涶}[llAQ3Ѯ2]P3ʍ1hE^DMcFnTN:_FRdm uXȋҚ4Ŋ`gS 6͔܌>u3ڰ Pt{.E OZ, ň12@_*Rg\DewY{?L{41Np;u.>͚lSbDAB0bϋiTAAlSY~Iw<^l[zLKZA3]ςJ})K7Xye"M'r]3aٺ;K;;uG`c^92Vd v J宵jg,7 H+P:Iq-A7HdϪ!Nwy}{{婿(|# KYGͶץǸ{Z>~.cYgӾqR M^+3. &yeU(̵I Jȳˌ_Muϗ y )|pN>=k\/&Y _)(Ⱦhc'Fi9Ԓlp:[0$ oNՌkSDq~'`y2-3 s Cnv,dauZ?$/ b44&!_:I]ִgw1xp >iَKuw#&5<[4n.uI\; ;pxѿFCj l%mznRnF/蒜ҕ=Dkdh"]W17{3X MZ{u+:iOݪQ~] 8?Ao2־=MGEjJl|3IJ?a_⃪9?DD鞖H3z-SU1Jq̣$-[^];+}"*z*Ťl1ao\v2xɣNy/-XGY۶5uݷöHc]#/rCf #kۣe Ւ,(P8)$iu1+T ^8+R!E|-Lӌi7\AZ[ " B p/d/2ᙁ<`d|tyQ1L'h;,q„a<)udp4(2e l2+!$@9%(8>^J07K.Hg^dl9p }<%YTF|2<[ZϧLk5 Z7j& xFL@F6Uw d: %[ cPƳaZw򢑙|MiƩ4W`Z%a`nR+@AWL&Wo\RB"#!*nb$kpJ7Zd @X7>BK3wiIO*IZq <ic,EP @i,mga;R Z3 d)<]Zr1.@}5Yw 1f:LCXy4KP)TUҐ,IʧV'ZZN.sQpHȳڠ:"N.MB!R e|y e;i^f-cMo*ZdRZ׍e5 Xz ~vÓZ:]8Ćہ,~=Z/ndDܪ3Ǹ𼺶(uAY Nrd6XmM䕟B )A7NrfVY"Ji  %hDD?DϏ?4ڱt7nu[,~UʲYX6fKuSs2ˤYW_c=GbѾt%LIm(HAFZbhEzL~j& IҺ"1a1.Shi{EUq+tWBɕpҵӿ݄]ǁ&쁑}RE4 hDRP(25`2u鐴p\]nqe=F%Չ2M9v.ӕw&\. k;JhXGii%~H+2jCL-@nuͥu,g`u-&P n(3kMej4uհ{~g } _~~sORpπ&o$߂=f^Vo8^m]oafWytyu;UD1gHQzMGї4[C꽮ͽ]t{#1qn"MTV-fT:9c*Ú#H} IDATA޲ѫٵ}A"Wi.yItO#cGǪ{aͦBDFjz4*l6n8 zan$U<+FNM^6^d*E!1 ;lh^|Rjc/׌:Uڲ&mi^GJ*dwD%(JZḓ='jNu@KT?ɻnZ]~ Mj.o 6 vwfj7E;˪sTqIj-mrjڦjKb;f^$jQlGTzet~v \oyn > o>pWKz̍GK%~T`鷏!Z4=KϾ\1=pGë򕪃% ^w/%_cw-\li\Q:gJYF 6mڜksU`]ە%ېVLAIۘwbB e4(cEcLRø5@)z1%PxI Z 1&cs1]tΡh ul<,~v@Y*tv-pՎ,(dS0C$jāq@ Cɇ'8[Q4 ѴK5 =0dB~|  ,̈́HR`n LȬ渙>լMm_XriՆ6/=og9 Ygс6L{XKMHW ~(7SMCLVv~)9 ʻxě^/@Wmeaad"@uB]a)bVHSrnJ";$Sct])+C~T/U1pv7 1Goh=IW ФÞ1{Ɯt n}~)6{J)imR yލA-pߓ[0kl?V^{Xw#3䔲|j 0\Պߐ-3JDfAPk T@Rg!: lX4ZpMI穁fc UslBw7H6/٦erݬKm1lCuL][`28+-g:Răkt3J) NcGdvjh4<6$'W Pf+A3!\};6siiN4*@JW (Ęz:ITPc!!vMCWK)"#'> 2maӾJtԭ]Y~3\)kqw S4@5iDcP'lK'QWvwWj\j?&A|P3z|@nk}r _uԱ} 40YXi61]3 c:ٹ!B>7ڀb̕t#]W%KQ˙J{֨̈́k>ۤl6 8YwlSqi/}#O DK8m~>K`kG3#.=!mni9ߩs9s.Tg#s"flpa'R+,r4vT]\r !: sÅADP{DxZl>u)jQ`g*Yn]i( $:g+֯8JDmiaKG\RSГa_&qur*u)hǣd& KMN>x4s5rs VQ:vP4- #}UpYߕu4奴]`a?uwnPv|ejbߝN^se TѫyvSJֹS{F(Ʃ=:yq*̿2r*Bu$/{N|>}ax]=|:P0rj2s\}Mu9#,!*^zd5ǭI P;#tᬩ`'15l^0 1( bbz%ֱ`۞?bD]ꩩ&hSGӎ4by<R߇m8JYҡ8:;{s.UMnnOj)"R3Q Pn=tK>,mm{C]L=XcZ=k'u?zk dX.ЍY.9*kuՂP-uti5}{^l,4߻# (Cč1t럧tyԯ4B:~9U#sm Dq8;iN]X>=uCV`TQ\D{n8\B=H^)Od0:Sz@|_;|~]^J[sTQh8朒InnI*V+&7R*uJpgz!$̣j2]y)vȉْBcQs"OaRuh?V(}UR s }(l^>JI#gJK\M#qW`zxk:6w\H',cU?MGҙʸ(J5f5lF%s.5|Y/GWEQ,y t}WWZx9 JZt6O8ws ktxjo6:߇\#Aх$kࣨĜ {NGW~3&v#婧dxBd9>|/~OIGdx_T;]uClw&J6X\`D 1ڶ'C,Vk7zߌS ;J#,+Oo(mHaHS8mA\E,yЪV >:*ntM]@EaE-y4!߆A:p{Ϊ  m?qKhmǶYJP;Sru,Q,Ҁn涏CT:jӶmZ@\{&  :Pӱ). T:?A2y LL8idG:`YvJ)Qdq*F4h%n,Q9kY[ ˞ i*^Ӷ^p>]FF, 𝬅rVBr8ne>~Bņ<%$!g_|TWu){a4] 9ՑצIO"CNut 9,WѴ{Noχ$|L{g!s<):31CWW Qտ藨I˝UWs,#!3|) /wSn<:]uu=SerS66zv{5yuܜ:,fx==Ż"h!7E7P2d R͚js@HL<7'FXna'i\hQafxPbQhSGV+[",kŲX׊aͮ v%ahiwg> WTIe$A =;^q+,en?s$  U@B )eQn)3 ol[h.wi&Fją%PԥQmb]i?>Lҳ5lB70Ij+VC UQIJxRGRf9eY7uY xG)p.G*h7B~Ss[ z # !L#~_N&Q7cqh&Jtj:rtac 9n}?7"h|tAhli}&U 6UJ ϸ w8]c\>t1lAr4*M?E۟Rr!%*;|xV9-DH{'ُQ"wWALq:W$gHҟ?9o W?:?\B _3ɴ0WԹpb_u\&w igyٌ|Gċۂv1h 3VܤdxGց"wrh* ۘ/Thhis^enZnnVtIt/V(ax zTבJQ %V9*7CMМEhf IDATnAqulq@uhe161;RR3se,}1Mhm'w@f k8._0 &Zoؾo =G@z\ ǂ>P<>ޱ>rY PV\`upQj]zU RZ! BRFKi@HXEM8l&ZJ jfT% ۳Ԋ ̐ SHӱwxwl'iT!?7u0C𐬸@JL&eYDv#ÐH߱[eAYUŒ:!%& @I' JS~3Ϣ(q4kz)=O3ġQ ,D!͖)3|ָyEޛ-5r\,SAeXZ3_\u8UؿGg=IG~U'5 }~_%Bc%>4ܹ|Q\QYzWDӜ\t3l\b=Qsw4zf1!ٸ]Y%Y3Fs4BbR-* ]wV7Wc,Nř#D)+`Y BkL1ŵQ r\dq2+W4DfEO3DqVR+F@V$mV[X܀{48$.Q*uEoW`LFQwq٘@3߾[zӂw9vw̱mߡ.̂(: xrMQ]ϲXmzs&4 }d[2H+KkX:/wۇ&$*ѯcfcUq_1=O+h:rן, n aoxba0o|ԍgۧC2m#2?K@@ dF[]?z|HTMm<ٱEFSnd 8u$/rlh>y;!<4"uJ>ARARqFf{{>i o2@8/VH_wh߀y=~LCR8`!J?Cf)ezrMrx<ݘ;Op G.{S:>WN09JEh5a {8}N|_^eG`H'X ~WNU`;\UAzr^!^c~9ER\*2̞%>W(@2-]Wl5}Es7@ 7IU'tgf\_ߝQg:Ё*S7\7N˃Q1d$6C,J797fEǨYLQiU n*o!7t>!EM; ʆ,}>RF4rBߨH9}s쵰siD~"*S&0F9 s)dz}us%YҞa~r -&j5+lЛ`oݍFKM_f*Nٞnmc'".5c#xSWjGNǩ R` $֑3ڌDؔ}#MRlY7 =wd` dq6QFc!"+ZV/؏v=DG>{X '#oBwHcY^=Zw"B]l:Q%iTXt$|h:0I~պQ93hq|Mi5{ T¾1 Z(x۔ghzݘa?1j/>"oQJd<&+Wk]%r5մ~ nf(zpa:\|˒+{Q:S@$iHk2N뤿k;'h$dL?{39K-!QDNN+$Œ zFTEd|=[t24Ytc2>T@:7+y֐Ld_v|NI]u֐d:I敟'UgvgC]ĝUazU?w]s0]|.64Ќ58O:eJ d;Wsq~?ߐJteLw/OS&9WCPUPwޙD/ݾVdY:X|8ZP#9oS`9}fCpqAX y^Hٹr~.h=ǡ:,z; sPSb?a> #<A`C@Nřx- Q'6@7V f1"e-;Շ Z&U{DR)źZPҾEzq?hy{FxށGZx< vH(k5ngMoY2 Œ,rF8ZT,5FL*8.543h">+-[=,ņOSr\4<%LfQdt+ \0 ͢eQ3''ܮ0.j:I % w+e?d)jYY2jC߻>T VF DRvԲBJ1ݓ4?zL)I{TeoG10m0J(ӁK ߀i}W_ƒX#|PĸS$OIa <<5b- jN%I?#Y\}eNͭ-j'4L UD+]TW?JVsb`J2sE* ?""/yuFh\TG~֦_YκWӃ+MzK9ί3$R;*PzZAwtAP.OB56e]G4|4XMdVm~?f2;kƸGE:eZ!dnwT/BKSZVfjDswIu0g^.u}HR+FWӒQGYk2f+fܒH43.; w~츆ᎊ/TQ+A:c*HKEo)(h{I.*Q:mPIFe/bJ>>=7s ib" aQXX@jVSCF -ώNٙI9&g4szަ._oDs6tn>g Irk[X Iy Z!SRPe0Ulo$e Z؞,rB?ȇ'.لCsmψ=>fO59 䟅Ҡ)]ag 9YC@Ƹih]Q7P2_Nߋ)]a `$T'Br ɛv@Ϛϝ_z8~^$"[ np<"NٙXEhLgnmިGj/"t%%9ZX>ȎE|R il,jv{}!10fz{*A/g!14LgFTgS745/aE̜ږEV P#-Ӑ# e`'rp [箛JҠ.ێSB" kS7`ՙ{{율SNhx3YoLM5 1r(NYTNS(Mp^<3hW-d(zݚ 0Jt: &%V0;w6js9*'fáRB+>d|כ7Sc?!t8cf^G0.ېߕ">b i"lo` CFٌF$?G .Kg_뻜LW˿bTyCL +`s<0b:ˌ] pH0 QxHHFԄיrQp@Vߗ*"GW2dswWl_Kg}ҞͳTR= wkqkgT;kl("J~?rsA}up:F>k^0s%R`{ʌ}f?HABw&}DYa}_M :3.ݜ&35/j߃K<]W(:2LVSOa5# X hBۃ@ RaA%5cxH ψ@#/C)gt8p^ Qw"UQ^h݈4~|#cD/EZ<r\hw xladmA}RǶ:&t 1l9Ç:q譡nomw]V(ʺ0Xd4>](T+ SٛQU(Q dTzLĸ_/ &BWyA 2Gvɷm~>q NJ}XL(ujYӾy0ij#RV0WYo/fX R>H|7{9-byh*<n{dOLRԠHCMз݋r?1vаnmBAŤbAiTj@]j*Sؒɽ-]}f^4 9랎F6hUg)9-WDFr1)1VbRH)hgB|+|$Oy-:9z0x_^˞ P)i(Iic  5[+%=^2=L\ٴg!X9R&vf= oW?}@w& =Շ}U_&Hq?W:;UWfۯ]U`ԫG* pF)zWZ ̝|2.ܹ?oXIJ c }f3^}\gޥ3OL¼Z%. RM:Ð=,R"\˅2;eĮGmǶG&yQ] ww9:tĽ̚@Iת<tX_R;eʎMo`3SWiƋ, pQ̇M46M/r'7< IDAT ^F1hYO?cZG[Ђ,f=3?1RABt?eeĤwl#HAPвZLqTK%fP G4Ci\:ra1K(ſ&#@mvLC~(XҪu0W;`>oO[g&uZ뤴x}ёQlF6&J3nO`7)9akdb :trw`kT&dfdBW;q{q8fžwrԞby ~ bw{SRԥBuFD/ε !N)Ⅷ;4a&=5XTSYƉyfVN:Xd/q2B6 Et#y8i|RB5f9pʁ-m]*!{qs2_twEګﺓP2Ǟi)wl :ү(L+} $mYJ?tTIh;5J~$=JQR3A8hB=S۹> 06?\ ) xнy6 ؋x%DF59y';cyְ O̢dE@`䗃sM"BΔĝj{l<Ӂ9晊;OmJ1*M 5 nv5JdpE,#mtm +yKk]y^q^dm7z&bRgLSvc"+1L"ϛ2 )hBOתR4컢 >&qMcņ ˴q-%fo?$et enP)`}Wf=Bu @P~ȧaQ91Ӑm>!8wϔ8% /E ڌaO:#>a̍`Qgu "d;[Ur%I3&*+]vU]Z]!xyW~{]+[pَא^|W_uiW\t5:dWҤ+C{B-Bmk(R{2u`(\dpThWJaF2b9Bqe8QvUP-x=wA _GRWp7݌>XԙNxnOl;#e]!(xvԺc}TŀO-9M;G;z}R^RI/򎑨A+ +T>?EJ(&^&:?~7Oh%ݻS#)> ^+#= Vѵ "NEW5/|`:z5J@ OK. *Ket:l2c |RA-~?S A8h]Eli֤kwH(^ر"z00wgѻuSULZYPcCEN=<3wRPuh:uԤ]"h Cc.(RGX΁-N1kgLkv+է% Z_;B/e׊VE,R{L?-|>Zf/"3~=Aeÿ) >:3t'9dp~ w|?=wwlŨP"AJs(%]:5Q ztݨS݌Vz%;҈8ϕ\E.E%lP¿чA7̑~Z2<iTi뿐O_xxhz阼 r ҳăSJLsJ&1UQ4i@pQP =Yf5.( ACZg4`<]cM 0^f'tL"(i;.)Th"04U5ihS)Az.#'e!Oi0<3xto&(q[#8wU'㊦rFuѯ }z"N~rWB^suxEB]C>.y`: wӃn_w(5JJӌF:Yɾ,FIlKet, zG1-| ;gX$csn d)wGiCxRPE{wV E4#90Y eDiZJRLR-q@S*C!A)HǞW45 6$(F։XXDTaźB8Ek)%P)^@ C)bT;;nvMw%g`LHkWOкgy@֙Rդ#Tf.ĺr4@IbwEzPV\3I`+R "y8W3uط} cv?*R&/j%F !2&+x9i) w%MoFuşAޡ]Wh膔1bDJ)l0?Aet(;]| x ]vtCmPPZ #4/ԣhO]uB e*X ̶ـ=t@~|+Scod`L \moYV,<@Do F2ؒSvA-JpUhho>Rte@;K?\xV9wsMN|V )lj{mz@ESh3D1'#Eݵ] z瑨fe&_CBcGeAWTC8$Ҝz>KEuC՗%_?xF==k-vd?B3Lh1uUk\'sW>sį W;J:t6WBuJg#;9'Wێ,/ga3U,mʹZG |౐pMM":t)pbΊx"K .car:Ǥb󤌏GskX [#N,ӓ\&BP2ǂV&T*0 $vXXTn=%Pd]hx &P M"5>['ɜ6:}s ljr"H̠'6XpJ-#gYSxN1h{gE}rdpKsADWU iLߞL\ }md;;:xC.cw X!̓MmڲII7ŧ\" W%2[ۊ V@lۆ1ZI]~A`3YV|W7?0օ,G7Id2 C2QS (u*m}=[@K#b09)YTN&٥tWtV[[AS6 ]/0kňUi*C?]eżtb8b H6XTq~xY*d{k9Yf0Djo=|&G#${UeU̩^cp*Mʤp! rL`HtdƐD L%{LgHAWA+_WWWW{U j_iH(wɯW\Czee6Ϟ_+Y~9:]5g9͕9zAֲy0kF}LGH"(]:w>w=,#kgl8ToFLCzVKOpڴm<5*a$lnݨx !boeM$ Jnޜ|u㌊3 f689kFs i 5.7/Y~QN#[;N뎵yD:ӣRXMte~mN-$fD!1#HHx0{~vp~ yK pƷoFZ}5zL/UUQ+ O$(3Y`R@|U<֚]JfoM&u,$X Q+ ThnnL+6c4}B|O01n2,޵(0/.?ٖe}D4F;ְA92j2;ѹ0 "';zo@W 1tVvL.9=JH+F:(M<>è̋^6([p)Z{sd0 9@Mgd (⍠by@7IRlN9^Fc] rLdE6qÈ MJ #7sՔs4bI:h񞀭2AL|>H8~"+8al[j:M:(&iSZ&5^42\~Ձ]a)Я~Ft4&Wɤ",OJ\ܥqW!\iͻχs9yl= Qug^<`(*cx4wWSqRAS-ޅ>$<<(@"xr:,[k=\j㑇9#qXaG(R j 3źGPQ@ԩ2<ޫBK\7bՍ}32G#Q!:@GpqC4Ħq=]k4óuc@"wRøⱮvTO?AqDx{5TXrݒ{<N,6n5$#y矠}X ;HLm2̡Q*Tz=<&[Dzmi4I]r(}i. 8%Hv.3i; }CAԡ;:\du% XN!\qX47c>BBH~oNL;2JvO۵W$L$%,GQ$[ɼ(߬CVv 8<b='CFaSLWz̘wsȿKJc3wJ#FֻT׻CƟ$a__ޮ^YZuÿķ=g`!O!۪kKY@Z' : pT+#xEE! /灩 Ck/Ӭ(qY7 *(_&! h%IdFwwlޠL`R"Fo.GepS{HB F UA&37ى+ﱰd9IиF/JiBA1i>gbzi,͓NzM' qߐ .phn5J̔`Ptpσr)Q AhC%%`Úf@Cs5ؔ0Y"zO *e1@Tݬ&]Kh{ömsAI+b a4&փ_@Jܰ}|;ުPM-]6kM`\VێwpϙϽyy5FڎmRG2Gp}rҽ>l"|B`]>yo_+I;S1 *IH]fs@!m|a0]v3ͅrh\bM9d .j B61J&LNb{tTC:/~%YyJ՟矛._u$U|mW;!Z){ﰌ W֯t_u^%SyiΓs{컠 tA7KL&21@ uDFtcıpa A-P1 9d"%''plnMT@Z\B#{ic~q,$6h3Ğl6]x[XF(K}kc|eϞ\UYյ!p5 ͑a#s*Ϧk̸G/ g(&7/~!RYpAG"g}L4iXPD46"2 wMhgBVcG ݹ݄XkH%lٲy'.ɏpeȉ.(61j2 IDAT}ov(`m۰=V,*.!;ШV%~Ov]`fmzн Bs6ʐ7a4| Kطelfm{"1{9̾ 0@S\kFž4C@3RƄwĉ#l:#䌲vʚI)0)r5ý7g܇I5;ߟx^Dd?.X˂WSH.kqwm\EE6W,AhijތV o4Gg/Z/T5rl tbEWB6(!$XJyjw#C$'ĂeP* F kfFL伪dZlYW1 ``6kD TH*!C|KJ TT?I%,8"6xE .9M22%stU*LOsc&j{(;=5^6u):i*ENZv| 2="T$eيF!UHڄR.U `RDR b~&yR pLO E?A.=Sk(*a[G5Qz;1KxI>ͫH*tԵᇲVkw udGd#A62Іղ|-x 厼 p8D3 ,L5zG<)t:B`As(VQ˛n3OA6V$0^е` P@æWNN4E6A/dyj(vn&mx:G޺Vjr>ce {"cB%\œC69N,&n!P#@H#ZU)D !hFiA>s&M@:nIŇDPF5 8k*ʎ6"PȖ>Z$GZ|9_ʊM;LA|&," }ٗo%BtT[5U"Q|aJ)1(&;=gHd5e.q g*M[ 8]R#Rt̥O$Oꌎa\ 쏛Qz1JTq>IQI 97W"As=605rǵ6^ WlJT8 gql8b01^^b םR惓KޛbEM4NSsЖ;&"ŵ.,t6i*PEpm@2Z} g͇~12c`n.мXkZxŸ`yOp`J},&T D:.\Yӝbը'B1VEȅ{jPRˤ `` n&!F #a8hUTa $~ +/9Q=% E@f/AԐg-ecm%y*4`N(JiP 萓pc+5)\2ZT.1c aˇ*@'bL\$װ4x C_ Tyrg!L2.UX.%. "~FbRhӤ߻<|٥6K4&IVKqs=n6J9y. (y5P@ЗPxuFbF=9ް2=Pa R**RTڊI'6buX9ѢlxI˖"B- I\xdة_&FUk%4(LǤʅ~R,` ?`2٩wUu ZȆQQUb R"QwXB*Cy0btu֩p<؄dl.c.ƨVʡa}L6(lT$M7>($ʊ+U#xH!"N1ʤpfgD ͛a*cXip7n.Z֬ÞRC)?.5_X@ك17 -R 1Ht$3Ul@"1RNC.OQK@%[CҲ>MTv)oo)$.|II.TQ5Fu^d}e\\Ki ]r)y$$$υWGq2qq30Z&'e`G朇kH_R y;/N  uY(?c;M`l[Yx߅PZtїN9f<'1{StK$PLC8Aix \LʢB^T:M Șug#D/ǡf(RcdNKPCM {%l𧘒:,Cp}<)&ax<6S TE*ۛ^VG&@'/:(Lbr׌?}PFPڢ*вdcc:nWpKټp&mַkNǠc4jGe;ڢg+tѝ"P@ĥ@;47h!tE: MB1^I/vX\(>FsA(&7J1'ʴؤMi TvC;;N/ ,cH-lU()u4˂TN!jse:0o#.P"eft"hN_ۮ߳DBhC# Az0DL*$$)U Nځ :&!O ZL) j~>Eź*QcH[%CM4cds!"Z) =1H.JVB*3Fk'i'!!G+'*ND\g)迍KKD4nf&/2I19|jGv\jRX ~0—'MbObǂ}pp`'qD7 pGqPu,.ƶ W_kIq ;/+ii!0yS9X6pq粪$51 )۫e0c B: όc4ԑ t( N)LE"q%a Z*+v:myjA+WJ _Y$ ͻ E*"eYҮtDƻ1q5i Bm$$(F jLO\Su[;ZQKg}Ng'Gj" (ŻDhy:%ZY̤ l(xxr0rhA@'υ4͡??XS},[6ΞAXkQu* u n7O<[te[?Y8'2}֚|ފ@"[³sgCqLr659͕Mto!sm%_\N>we I *l{7.DCKbUTpR1>Z(PQ)2Rg9")lꐂԈ#O}N cgR/CjMrRѼt,+ )e4H%:_+.VXΔ-f 53ҡsAbdk)U Cڈ0%$%8z|A7**&@Q.?# Qz6I)ryR0ЅȖ;KV=SGB) qZI8Z8dh!|I1rQ$+)dcd4̌&*- jnDVħ'uijx򰶒<;DI1wnvX>vܹ99wi !*c`;7:=yfU˦f0?ϛ٬F7zDtMb4&)TgR/r )Em|&Ibb_Qr Hm4$H5|~RJU;ഭA-EKfҐۜEa%*A @-BaS闆lLDeO: 1u"d}w-Ld׋%'91aWI0$h PtV ;^Dׯ HZIXxL*$ kr HتWt}ә):Ef1 ?Ca.qH| H@bl)dYcHG"ɫDA.Uf*2I^YG  8wfggUW]5Q2!{YDsiٳgqW/zCqGώ?v̙3E]׸ZAOO~z=lٲELm.Ҋ1/J>}N©S~z\zPǷ?O`ƍǩS077㢋.`m<q8˥< m9pgOz`0;Be{0NՅyI. a NXoHS) UZqа#]4 UU۝°vP;VVjpn5fEܴfh_eL 1Rx `aYEЦ5go֓ b F-WqJD4Q醇5@ۨsᛰ`hjb[a4؁"dIPIIs@+ãa.Pjx磁h|ĚN2 A}"F}%P';~- <>@C52)hd"5k4DXA™*i|etQ#FHP^/x%`!Q]x]-B1|+Eڌt `P%zK+]te3sYtwrHAo_8E""/40$:cmy 6y1 A[gƽ)Y2BA& pY|(mҪ f .N љ^Nw 41QFd elI`M*Jv\&f3{SOY;"ÈF-eA5i.U[c L~lye*&%(kyc/qL:)37?,]$1 r.t!y<0kŕW^9gy< ^{5]'r^$uZO>ĉ˿ O 2y /??ꫯ"Ԣx饗`?#^z(lTAH覍 %e,@t̽hjuݤ "|d9he(s0 hMdI>c"N `])bZFgui3!E1%7V6hBv͎.hE^\0HT C~ѿY+Z&l9FWJLIV"0lA JokN,:Z<9|'IK^z%;v _~"[oŏc;v,.zD-j~x衇PK|8bs\:죋gk+_=wbKNj9.-[6X3.|==ك|Zk֮]~nٲeG?{ iӦ.bo6>O/zPo30.%u>~F6~t=bFe#͉79 ``C@3-ƚĆ3G";cdCjHT^ʿYkIkQDW{j 5J @LhBm+X+ ha&cH6yY*L$^EM;,RWNUU< ` ,ZԩR- (*IfC q7>`8GPN6VF8`@.ʹ@{.UX]nM,!p;~?  0Znŷ (S,+ps,Prra(V@XLx&R-Ytt4%j&PĘr[iĮ3jT%i=D+PʔY !]!k]8Мq3(ئ?_tP^ÛG=N" 7^ڊFc֬Ywx׻ޅUVavv_W{n:u ^{-9ٟfff{xGK﮻¯w^}'NWUk㦛n?v؁//_{Ap8q7˗/Gi ={i<K_Dw_eXk/vJѣv￿5YMؘ$f׀'4'c[.`E` 0&JC2؊ ZNk, |vk^$^(O˄ьA( TLdC '*ͅt9pd?tp^xį9Bp m}dڡ2'{UqZ$RKYH)EP%DEu3b$ s5 4H۔94jX@kHJIl%H7jA l|@!q磄gq0Tkc Ѥ |!&(PCDS2׎sC4uWaJ;^RpA#>ssLs<:UOT| E,w#SZB7fȒ;p hPY+foyA{7QZe45)I#69to)xqQ@)sLkA* *0\T SA兽hY/s"Vy)-ţL>u~A̦T U$ '=1TIX r̿%F,Lu)IoAZC|=J !Jƅ1eVh>橃/ c24t ?wؙYl'xfb'WIjՒHy:(O-tRM*#s~Q]cR2+$c:21˴I1("h`Lg!Q)qpڸq_C<\{i;v :RطoN<*"/bXkl2r-뮻@D8z(>իWq KLS Y_Q%4eeOAJ#:vvDӱ:5RԨ6ҳ :XkUA+9KLB$"Zk=z BxGpYc=z+V,7 ֭ç>) lݺvg?YܰFSb.PFZA+C gưCڪtmQf U8)1gZ %SY-XI,])x"hcaa%3!-RRyg:k6G]~!๋ T\$(+6r (P;Jq#&a4 }pBB[. C')m*V7%롬5bZFzbGڲ0bqS7),&~7PjY^T&TJ+_T3N'J^Ƒ(KbauS>5 l2)h!63v6xJQ1[ҨKh:LMMC9J qI_SlW;b<]Ĵ6ؘ,,~RoH/ʆShkS*%-3lY&U A?;.Q|R*/PJᩧZ?!%ttu/R{w}k8{,a2h*jժdl2<Әx㍰ֶ~Ǥ]w֮]ǁ/2^}U8pbŊ$Yr%6l؀n3gp (penZlٲw}w sssعs'?g3$6UUajjj4b)q)u8sM8:In 8y$ci&n7 7~aQSU.rl߾=v3<{{}݇7.ҿǟ B!P!uH+#ŐGz.w| K]my\ 4!h˩ߚ<,]a;'Qu@8r)Yv!UR + * 2C&UqP̎yJ̘ҧ*zڣ!ЪB  $f::}O}ql|Z9Erj(:X-L2Vu,BSh:uIuby!S0[^BC0bAzF-2L- I0C#2'Wnj|*9`*tehS]^w4#,2'_Zq ^*kP=: _7bFr)M-v|>\o!6{(ʀ\{c Tq`;43ىJ'%RT Е)GGER%lB!iq^`&tQ_ $M TꌣԧumtPFiq v (m$TYRQi$㈛H/mSBS:L\Un ,}.}_$cPU-[jf92Ngal2 Nn,߁9H`{G3`8I;71!^EdjQmOb# Cf{^X^IIiw^_ǰj*;w=~ᩧŽ;vK75N>gy{__җa{݈aذan݊Ç&Μ9+WqlhZ Ѣx~wkv:XSŅ 3/RoǷm<!-oy l21Dkt9gϝ; Z9ܹ;wĦMp_2;6XCC|ַW^y+VoNǪ܈B;Zh2բ lT0ם߿wHYukG z`C4a/;9H$FLⳄeo|J)LL[樤\O ~H-FćoQPHwT"ime py*Mr(7YoH499bHdaDdcZI19ZxxO2&lM jc,ZpȔeR}PSCFPi-_HA!5|z.E6zύ~R&6Oh, u1Z|A(m^ šSk=X8wytmW6nR /InBb*[?RQ4)8!USSk(N4ڢ ,{ߴd)Y˱@E*xǶ/1 ׍х#5k),JCx#g%hEt:"EtmeΧI$+<1RE52G8ȳ@D*'^b==_4pP4xrG5*HB(988#!S9P+M"'r f55>:+DDbc(`IoqS5 )}$Iւ&+uy НIbV(rxk W6G"fY-$ IDAT ij:7 1' ^XXb׮]ؾ};.b\|Ře]c N>'|x;033<={6yIfS5x ,ۇ__ƍcСC[qWn|۶mw]hyf\~hN ӧQ5fggfnHZXr%mۆSNxd_;uN< )ǏƍO<֬YK/+V[VyXbE>GEܹsiɓ' k߿k֬C=(>k֬IԠ4 @DtMزeK*Ν;yt݄tɓh&O]q~۷qצiDe,7XX;8F]K&MٜNd(Ga5H]%!(ҖaPFB̰L"VBJ#Tt0YgGySf6R!;1@J àw{&-H@- !Ur8(SMN32!DXTv[uyXI8t㴋ZeL4 eetLҶ[:`,Q4 \Ri@+cym3='FѱR!=F$+RPpWK˃1Fs.bFeqLS)('זT5߫,Ϭ2Y6n>H=E3K:tBnsgѭ,zSS,%V$N,_k+83\Kih, Pu(m-Ҕ7JI&$5I&ǻ"ۅ55i:d`Q_(01B\l}QefRns}89h75qY9s{\s5ؽ{7q={!kW_`0mr V\ =î]pرܿ?zXblpʕ+k.mo{aϞ=uV(vZxqaڵ O?4^~el۶ pcvvw#G9cǎa۶mXz5~__Q5Ξ=>ߙ3g/cժU7.ڵk} PJg4 87MWsa͚5زe .(p)LMMazzu*øKn:| _'P5^uvm7˗_#<ضm>b͚5J' DK|*KSx,Bjfx5a8fTQHI &޷]]koKY)mȣnke=PAЩ:Q<8w4\]C˱:[WC,uSں`8IJe){ 4'JB]sh@V9lK!OXr΄Hg"6nC!rҩoOEZ%TXJI殲D)w-ZcZ Ţ*)DS8ۭUU6'c##z0ˁ$v so ]NP=>gFp"4N%.{޸ m,py30<;1 G=$JLF ifne#DwMwsC T L<F<(@JmZ)#"ؔRAFo2R4ҏbJJ'b:mJ~*m{hkZMJ3Ck+(9 ?emT۰a~~)}mZc͚5x׻׷\y啸Bzjlڴ)G{6oތwp8Ė-[055}Z^e˖}oznnÚ5kpAz=z=>}MӤtc?~^{-.|#ڵkqW[oMb \wu8~88055 ,_%1ھ};n7Z=;5rJ|ӟʕ+pqTU [nEq7Zz5Ž;~z={֭͛w^4M/n{/& 6a֭Zho,SR FCbHLhw@αyOiVЌF$HeTHDgPU҅JQBHE"x@۝bqn++iHҐ(x )$)l&RZEy P/&6מn$}eDD^;PeNJ/ 9T܆ؐf3s"A (*&ȸxFY,"ش;m*6y *6_җ¸"}|0L2O>+9.4ބ~D 텅T??ڡ Yǽ ̔3 4zF2b<16|_k-,,೟,oN8_~?OpOOvډB= =bJ(g8iLOOZTz8WBǣ>*<}s=Lt,cf) 6 D"ؘ&蝄(#\3qNl긁noSeJEH+t:tۃ50 Q~v۫3'&w&uGUyxxTDA&F5Mdk<\#y cBZW)FqNYX ;5C#Z:PU,I_͸13330}NM0kSLMZϹq~ZweJW@uh޹!/PVi1(bьm/@,J~D?N@BhkevMP@JT j]%8BQcMuN*mVK!OV{AIQaĈ0 =u춦,TeJ qgIc}d4+t)j1ax)+[Avg(bzBo:xEXfƄ&%i/g9x dBu#FrO$; +E2=ΓkV'hb!-jAUH>&۲K3)R,0i|Η:Z3.r\Ú2y` |MMXD#Ǜ4GG<:%qqAXɱMȑ#Xn.rl޼ xcӦMBN:7,=oXk3*le ]!CwWnKNH}s;F{(%p ]mcxlCMSәko?:%YPugk}}|;wt{ /"g>H-ST䪵n PdZ9 RB:Ar g?"J(J(I*acmP;NTJ5#2jC Rن[dGYꈱVPA&p0$#BA_Rc)P<#ș;"#MT+cBȻDX)|f2%B56pQd XC+n ZeQ@iKZEb'Qu H 8W6TC]'5q'ʏQ*cbsla3 Ps2C)OS[ 4G#ja  y#"^$me˥QcJcqi^Cj8KZ2`ףIu(zE0?7!ǰ ȌM+LcuX@XGtbuD] c- ]@*  ՇT%zS8<:a]y JJeGDj]%T,rEv\!lM]r&(`92l jǡeR BX*,hREnoHJxlttB`#OMBp"͇`3dF3Y4KHKPq `Z&"Xz/t՚T$fRWq)ZG"MBz l !QHs$ϝWiN DCz$I \t,{SIJhg+nYXK롖>:iSЅLoe$bjPBG_e"| si%+gHrI-*k{ܼ-XGn\^dw6ތ r"|Lb3v I0NFiGRNԡ{XEɧjv^i" k]wՒ uXmy9$ u✞{ǎ>2w5룮%d%R 5>b`Hˆ'1[mD M3ۦН 䘜I2.!)cS,X]L$%ǞǰC-%] @FIxr`HIT^`B{.Z(jeZ*ƍR8G͟Xك4CFD B(N5u5f"INt֢i78sCsK~r^@J޷$;"#FCEx-9o0]PgYTB)Pw0A `H{0uLPnde33Ebz8tsi ý@dӚR;kc!(R?væ]{ >$< P!+LfL;NYiM<}!BhMV%J&8%L`bïC˿/b:,[vRjbiVuèK)H?_aoVX*T׍n]Zjw ѕuȟSܳֆ^@.t4B,K{ク+pQ E[b߾}زe˚rNFMDΗD#|OÝwމ;Q캉ea/ ==P,Yc4=)8CdFWZű"G)|O,iE \ (MР ņ\`3D~PqRL5+p20EbB{fisDas:uk2ug0 Cn!XӚO 1Y1$::w:?Y7&\@9Kq;= ]Uyp'4l]Q&u;hC0tc Q+\ GmJt'SS3͛VGemxX Pe!*EB%2~j~++x MUSw\h~LfM*5R*0 Iv#5If2c|YA(D"0E(#R7?$B'˳p5߫쾇_bQ 0:oJAJK!!Nk7:;]$CVtp2ue fNA@Az m\!;$Z꘏Xk!-S-6$O t9c#"57*Pi|z@rcB@)1ZF}ҁg:o(ECU4s0BAON7Y؀(@XG}@"3gΐ"򹐭-Ŷ@n%sDAs"FE;\gTnJ(95_̺a5푌kܺ7+G lkGDB\2&zQ1G=QZQ2Q,d5ģ_y啸+G& kFkkIdEzX,LOߝ:EENQ:ib)FJ Wms}*j{b|HN\\!Iox 6ao]G_;YNC4eC-|k!{f>5 U<$IFq&4 HRֽ:2ٸՅ '+Jq"$$TYbvvlڴ ˋKbblQ{L-y&b8di + >LX:xệ{:L .\t @hzDPEgv8T|{:".6}*6nJ& RL"eoH(xBv,fZlܶ7*T14ͤ3d &ARE`Ma"HouUd1ޅY>!jp=q(G$dE4 a\а77iFX3r)>G ׻\n9v7;*^ ߜQFUERXFLߊwFm2G:tܨ3ΦLAN븈#Xv?6%ƱK^RDR,YV~978p`á1:JRȅgݴҳ~hzS'4%uϩ(3@&8FBdT&y x޸$=q5ڼe!@c 6}# SBFUzA{n(A{M1 CxÒlA(QnLݩs-P<L({|+AL3\Kq*+,/ 1\xX7=G9(UEJm=(v"DUujc TQ_aATw͕րh OІTd擬%UP!QL9g:,)qQ]p1=15_,ű BB钒^l4r5dl2R?icMa^ JHcp XgJqeEr[1"Hn"zc5դUDeBfyĤ0fg~dvyWМpje zbYdCSZOdP>4TX?CD ՘ke$H8 g %.V8N|2Ǐ1NqDt`Ba^U11a4tfBM#C!s~~J)l۶ [n}[VU;w˸kWuGoVKfĜI6*G3v}y>@W>s%eRo2نE^揻VHk=QQ2|qI(P!5/‚84I- 2MI1H )4oRD uy31@D8ڸ+!euzם4ea+R7'+$*kES"mdr퇻d=EiÑ-UL0Jݾ{:jE`"EA:G"V9GLqQ %ﲠFRPq7ᰮjma@+8a'Tb'xzb9 )F#\T$? gƚy(NECw u.03h|F(m̯6+K,/c 4 #'Ig-p)8vZeۘ$ k8畡Ьi`@(aL'c*TM ]jH'ׅ(T֢〯^jEBlS:4)qWp.c[R8G$"nH y!ulLhs΅A9T;߄ :R$’18%2=vwgQ"t&fy8MU")Gpyhj#v=A^+ ^t>HkcCJfMQ23L՚h&D&ˑ4MfJZ+#ƃE1SO' x{ߋ}c-ɨzs/~ȑ#X\\WՑϵL#ٌto$f,ε:kp[tټ]SZQr%p䇃7;Lkv֢qɔ>CJA gWHx4Fu D= k,f\ox'q1q=K"iԮ8w h x{+zn |_rkgH;ĥ765awm,˴XZ<ך-d%:"]&2rrKi43FYmi_FbSK0ʀ=_Eӥl]GiC|A)8Q75D Ҥs !`[^3  齤WAlZ %e_(:%ͱLs/>Ǡ﬈! *F& ` H;g;(!MslWdtNZkt`4B#A"a5zNV( OaWa!x2@SMbmTCHE#Gc~~w}7~[{~--"( ;wNB;ik|]^#¼_;}~A*.{]jpL|uLVLkQkތ璏ߌZWgB;6Q#BBq!!\@0P)E) Oɋ:.d>0 |t4.NkTٱe1Oq6g~ψaf ySh\$潅WRS3bTC2ކ}jR r-gk%[,A=pʍ[a` Q45V:#L6إ-Zʉ>{$2BY^ǔhdi"[~β)O"ќ Сu{qiLMMkh `7SrәR ^z)wرc# .{cp 2tG(%v=7io}[0$3# M]C[+%z]p|֒w?n(CqNjԈ浦 ]d:FRqglV{h)ZP[8EϭqJi}C w> 'z(֤6n^Jo uPBRq>dHt RRPb] CZVsli:]ZHL$RLZuGA̵?^c9Hos1JedR>{6o8t׍@JW6\d;=DNI rGݚd:d*2EJfn73[g+Bl]S*A#>|-L pMd>]hj"{,̟j*ZPE (櫢x5"YgSQ=$A6XL.4W"_PJCBzF(+{4h^Z?Rf)ka|:TzB*佱ctpjئBRn+% !#0H].iv/jU=4PnDjßI4dDz];CX2ZN^r:Ɔ;2#) 6v ,Ev!G֨L=g;9(Bǖ}6AQ8>4\q_R#ADkb4.v\*E  P%Ix%͡H,XbXO sRDldMπNmo?.)Y%2- 3U 3"u=Pt (SQ1ۭ a<g?=G?q<կ~k-vލ}199#GpQu+/:.㛬FkUUgC=#G@]vᮻ/~/=vڅO7g}ro+N> nf|uZs8|0uV={[l?q\uUߏ^z ػw/fff099}s8|0z!>|w^|_^k->x/"pONN+ /_݋'Objj w:_~u]cÆ [qALMMWcǎayy]tqY߿\p~_ĉ>s=X\\___"كe`߾}ؾ};>Ǐ,Kq?7W_׾5;O} ۷o" O<9;w( 9sg/G꺆1_=Od"JGM֢`LBj!i({!!n,)6H땘CW@()EĔXpy!aMР</jƭy(#C:)ܥwM)(ve05If/M)HuG2 ˥i r\pxm#r+rD */J6oBc>.P:C}B,*P3Dru$K,UHM>@Jq@t"TV"#{IFBґ'!kQ˘R +HHLMbîi f-nYKI}braM^0+X <.6ze Z,ʢ00 7ժiSq}o+[6ǦuP'bi) zl ڧιdU!K0Dij@'cLïE5\Dy]f(-"(Ȱ> QCu;7$ZthZI>MObQ8r| 1IE,Ik-nH@:ǔc· f]nk+$mO#4m:r'f|pl ! G]"p)(PYCGfAn،rr.R۹o 63֘ΧfUD_%!j#S w %'aψ@ʘ+Q) bR-9GNɮ]wB~&s011^x_ױou]cvvΝrcL4_}U|cp]w*/s6: IDAT4]Z.M3îbÇm|0aɜm{g *2|<񘮅N=ϖL&$UKoD3-!.L4D2 --Iw gg0\Ԗm߸Z{C:*74wzdҫŇVN;W:J1Ar@'1eUH ĀXK~~ u]cӦMXny`ޓoݺuqa<8s z'&pw.? k-oߎ{;vΝ;qwk_ذaʲN8p`{M6( ^W\z Ǐǫn O,Kvmعs'?C׾5xquaee/B|K/q1׿7ߌ< ʲĭފ-[ğ-܂;vY;} ~ #7ty C]'>x8 xꩧb>رЇG;qáC9uV8p{}K>7 jwIJ! Yu]h1[\jͼg$ &RDVupJBJeArI[gd 2;t.`RlׄXލpaX:ca|\6o-bp]FmE-.tWfstch,*"@b4c,`XIGKb:lH@ @qN@J)RP2Bcq"Dwu=8ka -_-it#" ] ]J=FpHJJk BbIA]兤@Y, QUCqם Pe(1J`q@EFK}!! ઈ0-s;[%S!p8.݃\%&m}W_سp,r/KXLj:;H$bd^'"K<i]15>?G܀ɱg:O ix bYB9!xPor:m^!oӁ\fx(S>'}~cwbi FJ( ':J&>gzTΫTǃG0Ohh[b4&.sfHjoCHApX0 `jZ,9j6BOo 34}p9,iPa-``j8'ԩZ#O?cW3>F':1 AFUU-L-:MO333-[`||{޽pibݺuQSvލ.-]pwСCyԼфv% .w?\uUj?ѣG_ O>$oߎkf۶m[Kk=11 baaBTJa۰o.(v/R'P5֘p̙@||5v؁oZmoC=@ɵue?n{zz:\:{8q,,,'LOOcrr2>M6۴inFx111^z 'Oă>sW]uv7q~+'bBR)Mp+HDR=IKBMc) /R )Ca )e`PN &{ڸ1X[@SpuT\zHyeuRA̵2Nid;r#4Qy?*LHKN88b'3sBbY<-|ێh} {(>(}OP50چ[!t?vBZmԨ6Hv+)`MJ+hy&EZ\ )"6%H-D967dŘ:F[cX& h3IIfȥuM@AK2iΟ9g"t!v0JQzH2ׅB C@H*Cc 195jLLS\Ԭ6…BYHOk @:s*q(<5to Z&'&~jʘz-#IeT13'Bx߼Z˞kazS}@yXc`&0^PBlBr$& \<2>YIRGihqY^#UIö 7_)RfIs?,-p&H[!71!Z"; 6w}v8`sx X$+¥T`􁞒6g=@6I@*-Գ 9ȳctz`UF+4^5Dq fVƈ 5SqsQȦ2j%DL & D'çLHYH:ݔ`Q[ ENUU)u}-I(Μ9BTU:a-V3>33C^{~WcOG1{/<#عs'.r\p8pz!<'?Xu0`0@Q1:H)I4Ml۶}o݋??T^{ }#y7+s((aӧ-@E A<;Yxd RPZ`$F2#茭$UY>&QJ7h O.jpXc,yaVBG#U6s#; Shud1mR)%^8#"@ !#8 efswx], Z^AP%ŭM_o rZłұ"'=~YOKv,tg21sn3sٛ". MNy% 8)a >C2BH|z&MtNMCGNJ^ꮌ& ΝRs+oW^y۷oǺuSOA)혞J"7MǏc˖- ;w.RI?cǎ_17ps w^\yCc|5'? ? /7tӛJuB!4 ~_صkWQy9 Chq7׿5z)ٳ~>(u]i<wcǎǕW^}G]ב?$0>x;iӦQ/..b0@J99s!Ν;=z4~ӧ1??#Gm\|y߿?8N>GyW]u}Yw:<9I&Gv%"/q05FYZ.]t3ݨI &aH=% Pr2Q{l~N S4ɪ fϞ#bcתsavfN]Щ^#@{4CM+ )a=ΐPYpd% 2n‰H6nh *A,ȭoeoţS lZ{̤QE$&&Q5*Li8aR 0P0uͯE!Dld Oc}EaH&Tg e4YXX(pqƉ04Z8CrkBn FL4ưT*u[07E:51ccǎO )%p9[9 cbѣG+N:[V<0x0??>}wɓ'wv‰'3,!:d]4)ˀR\Orl D֛hVϿ*Ӹ겋pE;Z=_v4s? G\g#D`i|MLMZ ]]!d99%Td:Jj~$N:aO+ɳX)!4`C*\jASʂ%HϺF%+Ӵ%L(Nr1DIMNpgˎEΥ;R kMZEIalYŽH XǗXd{ QV,@JeG>%/Qǻ>y~R.4K3]!Znqed|&7nĝw2m߾;w177=b=Ee9@%T++N5tQr/0l JF03*4 @S\(ʲR0^QiȡJUS($/ H T X۸[w,KX"HJahrO 읇$F~%ð\%cBhgN9.PpMU 7EW/D  Q1qftϣ@V0G,ua0*}34߃m>Ǘ_~<mۆؿ?~eY4 W_}g|_hRBJ47cff~;8{,^yݻ|_|q"!&_h:C+;dR͒H^DSJ2V+8XqcR5xEYB:eҊ b L, _W@@Gz++CAgQ-,Yzy3gPB4^̓l[7aݻ9w,BJ B"0C]!vJŕp SNIp*`B1q1HHHWtus{Mu ԭ>g}Z)>Tai2Ƅ511YV<[i ׬X]~S4V)59?`:(P42},cч3<7pX>ഢ=~#/ұ~(B[Zk)t]ܑ ÈQaY51x֫)P 5~ir1vC?T@Jtݞaߑfݮyӗ7E}]q"~,371׿}ww>(?sg$!LkK .u*kad.ai{)hi\E?zr^0b!kz*'rZ"1+P 2Qcfj>65c?}|7Y^Y!\q,24-'Y)RȾEW{n@o|Li&m?go{<>Zgcci՞r*3R91e k !&\]h'b^@JSvW@NWZM:$M D Ku68Nidsz,̓ɃOfN><Bsj!8OsbK:Y@/m"W+MS.K y/|Y~2plK12ч;QkR"kf%)aLtO![QQ[1OtLiF1f],)Ke.0 AISy*d )wnvNVKǘ -K!xDSXD -ťmNb%@Nxc)nӴm{G$zHM>яx~x.#?#sqs?sbtgÍ7x_Lf#<ƒ> -꺞%D]^z>_Xђ~^*#`粈[ 4*-g:OԕŇvNyJ#̗w-#vqVb^RN8cSWcD+D%LyGBш  D9'4&XK[*x2(CߏCt`$1Deen&(:ɱM`64 :,(Ib4eU.N{e5>~՚{j+hj7X-b#Gu0v޼w c,]>U<Y+-N)VGJDo3@c4{_;ڍk`]O?,ܸ W\xϼȾrJ a!kU!fRPUT24+bq =wvَ{vg(Q낏B!`sZ9ZW(eX*Bl+G̊jƘԝL9:[*ѧXC"RYĺme I#OHu0S)P֢A)p]Y )i~1 ̮4c#F+nԵ#eE'6h*9;R >ʐgOgG6_Ob⏿-KLS_m]_ $K4C/}ۿw|ŗ~,LnI 꺢Gڶ&\ؑFB}|_׿ռer׵-J)>}W3=ŏПŎo|y⩛MUS؟N=u}Z5oEC&/ =pK-kL@c|'@[WמFx+RUY& =y+prSOx;8>'hTd~W>Ÿ?L"STm䙓Ldz)'xw~b:3SSsw+w>*x[|c?|n, $.m>e:fvӓ#^׽e{)̳|_׿/_goeF,mwtd_͆=3l6/]ajpձg~g1a@k= iwT9Z||*T&|$tx z: ^ձ^ mbn ڗ[09V_L+?(LdNsbFi4~c֐ϲ3 9D :kyHӟTq3 ֢S.Z]UEg mU "Vah"6c+ TJgڭtEJ-IS–Zω*R T|XU҇(9:ڰ=9mju4&(Uj{µ=g8uŭ3~InY֬O0~y'lf\!c`$:%prq'tjnZ5m0̡2wC6mZcXaiØ3++Xc )&GJww]sM|iOӟ9$y3:4kC  :?Pi!i卑#mU&Kbh,A)ڦ.E먴&!}$"PTVg1i]M#S8þ:ʙ={λμՊtSr`QZ+1дm] CmBjFg8kPz6bw[U)gg֜ě=EQS: ]?]9rTh Z)F컁) ƬFsz|DU[u#(MUTf zKy۾k +^ #}d$~l+bLZ)+lvg6/tx+_!uo_mo|U3+)e[]7i܋"H*)Dъ׽!Ÿx7_WN׼K_7?'>O$h/.hT+P DVcՖx&IJ)r%f: Q{{iZ(F@-%_ :+y Z>]1(v!~oJP/䠫UW7~wh+/}|_[7τnr53Dޥ򎷽?ǿ/.:k[^w}5/hbdj4XvGn3HZ{Z I[Z:.il NiK!>/Wuz!~0wc?J)NOOo@as]z/~??;ܸq%s=_dy(7/cy=H[Zy.ؖ:,_s`lYMᶞ32Tp$>U,'4VVkID5Z-ǐi+H \yY!'l(Q饢 VZhtB4l1}b=M&XJ nЅwpnBĪV詜'M[kzpz! at}jV놬Z'Vڝ֖Gv;\UYmۖv~=89n9ܺɑ54њS^~JՄnzyV'J)q.].E'S Z3ڀ1bIfR"(C]:*%I:J~Q1R5ۣ u%χ6Ju5ԕ cJe"Q\mHG*AQk.D7ĈՆ>e'" /p5M%c k @m5)$Vΰw#GkFi,)sr"ȺmC6` (RJ:%S buu%i늌,g=׶[qnf~t{VMCUWcY.zh#i($)QW?߼P릵Ykt ZP9|?`*æM2%+Gel*1ukGO JkVΕq{2o}-OJNB/ܕRμ?NNժFr@ i>م9=9tMW羅#SB *kfՒj=_cJ]7ۚ#8nY' g ڌh4&3u[E磕XއJwI-SCb!9QJbB#* }C/^@;Ӻ1%KI#_DAHiC99I5GTRY"@5$`a.;&I:H>Ӌ̇D[99Sk߮[R0 CV>Cc5ݾj ![|\Ca7Ѝemh Vo׸^ =p Tƒ21[+N:f{툓6G[uʑS'b4G-.% $h͍klS;KVPH2as_"$=S&B-q%EeSbAYO$O#zyB:m%*+Q X h\ #b솞kۊ4F!+ہc2[*b5zVMU$y0bqa`UY6~d,g5hcd틞2 cxpbϪ3E4QW5MSCqI8cx-W-/vjVMG5>fU ӟ8 %hpXGSs!@Ŝ9ZSYmdNO6+]`[@tF6!B]Ux0FeЙ<4_x9^pˤtb3jBlZafyGlڊ=oyq=Z8+ӧ's y!_?e|gWWquMmSJ)߱c#"naynGӥ+[@.SœO=?_BC+y^bN ⢔od,n8ZIfxK[is]7>)nݺ(I(|w׼vb<-z&]?PW{΍Xc _׽~F 0_ޣ[Ɋ6]S;gfW-3W Y,{[ڡZ#XDh XKbVЕV"҈HJKX$uGmee¬3j+v@XON͔-іhl-֟Ӗ U̓'4]&hnbqo=e$LR*iN֜6|* mMC5sn/a(В±8Wqw*|嶖 *qU]4AY%K|psMDZ޳êƐӶq97kKzK}xWM:c\8`=O*$GZ$jfL.ւѧHa#q9P+KH,8D4AIw"TRTEԨ&ktV5ѳ+~60mZٴ-q`YV!Qӈe]S1^(C(|XsƈJ7unheblVu1\?{e]U3䣖&8nw]DdJTMհ>:b^& 4qUj\k4XEqOY$$fGASd ©%F3Ay(UxX)E묤[iSD 'Gn)7oޢic>@S'ۻ=medUŎkGkƺṋsnlZ:?2gVgz*m8?߱ij.w4U-Q.zH#ÀQrm]s{Hں;jŎu]qcvH[9w{:pb{N6mCӶbQi =VkatGM8zoFtfM?Zb}'>(uJe,{ wu uӰ^!rmuþ8Z7캑Uc;՜ }s]'!F_O|qm7|W~rZ3џu94\k~14&0/9F)xW' "x黁yȵo7v綩 1rtFZN+R‡E^"3}/(fD_""uhqFbvG>[j~?:>/}̃5SVwb9YZ+' 9yw{;3{s׍c~>CUW8x'VB/G[?jwd{i>wm9˰;i.詛z1+X0d+y"ci.8i\AizOBBV"hL =PcX몥\)1>^0ʔ⢵|m!Qˀd2\dѴ+3[@hW2>$bP vJDR4tZ&}~~N] 0%`;ViJifisaxΉ>KuwF.ysm⪮[r}3 Wm{.0]c)Ȝa~Kp{xއ%>r8X%c[v×=aaWQ(Sfε*i\:1<-󬠶b(K,":UM&q/I·ĉ5%1mbNlz)|G+ƑXZC²1,8JpI9a-0CN4OTmϑͦM[*!&KLVu.&BU;ƌ>bdeScf̢&8&xFkVFDs Hg4Q,lSϹe@E&QYZ\̌e<k=[;:N CmcGIS '*JgqjEu#YFJǫVQ2ynWs8F,'w1O>‹wU4:*Ya^lXC1=wRa_6#lD\OABGeR%u3(bεx`/JnE'Έ.|kNs,9ObE}qֈۺ}ܧ.סtL\Gvnׄb%N3tW'_7)]cOr7fwLq<5oXl__x}$L+gIAgak-~I9q5_:>9Cߍ4mMI9*uKY[W"w#33@Cc\JLXn*SՎ>PU]ж!*wSYr @s|mP|'C"=Mm0FJrQJ eV6S׆\λ.ϧQ>z,N`0 cG@X MC~w0:eI3 [>. v3ӱ,;痶8S"FpZ"..i8և.i7Wy_uއ8PKP}$t((>tiYj":z%}*kIUșn/"};HYw7hZ^K}뢛lec`7EĦF)5S]1d%9S֩,#poiA.~D(V("^=;Yw}ӄ d0Xt6Ǒ8Eī^4"'QwHR^v1/>̺mJ ρ߻_mu=(Wk:+5rnW sc@Ir絖{7p֛8c= }?S2zn(;Y(QbA)U|L0պasXkFlO͔3g@|?j:;|_rպGKJ\;=nTeaHVK C=Ue麁黁v H5C)8Ef{1ciTchV=M{lTuE0*vF(#{0nAW4톽ezUE?;MY'mKC1 :4wq˘X&쇾zkILR^R<@ti, RJmb43xJ;U"%z> aQ0lhylT^L%oW",rR<KqH?Ҏq1&(;Q&,N;qr97S\+t6uOQJ"3(-#ZԖ8%tk,>80QNIO̚"Qզc+AS(' g* FhƋyeic(=d![5 XV=:F| Mg%v·6E̩u&fX\ӈUXocP)p 0nFĕ![#ޏ%#fC[̠3)⴦WXEUĥF)H,lPLYaKw̐e)hI蓢FVe|ᾇ1:1DqҰVňjh2"am+*H7 "8Αb]$ymjob7R55H96uvhu< >mZ|RlũpkwAnojib?l }T&6+bT҉<)jcg)TFM-Skusi9hjG}iJˆbh4yǛ ~x橛g b.Z4 ,OUKS[@( `JPrF//yE߱^Ֆsٴ cPSLbBmǟxZ`0(\e))ntQerJ&O4R*?/X[NA:)fqe0AJe뙲2-A2j%&KUUxg>gH?'p:/4!XVz]/#KE9:,{ ƗҾp^/'霗|,kwIpz(>7%YM|tmtiCjt,0⤲Li=E`b ksuo”Ϣ*TCh9#<;ɦQ5UI MXU;w]w{q PV,R!PG}#UJ:B}8u-X"ȠpۭEX!(}~d9ڄNncx٫RfDJ u R$ET*-$`)Lt [ \JAQIo抓8lW-1eF{bB(G떮1VS׎5TeMh'}tra8وLwЃwc޻}?ziMU9tT׎,)@5qrfM#JA*!X8nDB!PNGk*3/YuM Agg\ݾi6w.ٟ])xɟ MmUBwm+T"c5}7 B{+='fu2Z3ƒTHTD6ҏThƜ3]^(<,?݇6S, Ӿt:L ~ĶP.),var#Β6,v' KU~!M1QNhKV‡ j%B&, ;YȾs*,N]G(M$JpKD ^eQ!iK¬2J XH Z'G/e2lVQB~'y*%(fQԘrO1#1ҕ"E.3]Cմ:1XFI:ҥ6't3IDEs(PP# JIșl{ߒΗH3N] azSSG4eP%\#og§m5" d'5NgX[cK.]%Slkrv,VBΙdF3_r&u~I78)i-c7`%$Eb3{쇑KfPc3 ~FiVuCHҝ 1aK%9 _1tHTGaҒBtϋ-0Je  /ݣ4JbV8-JpRɌq 䌌-x/ 2 *?mQ1&r Lˉ^RIYo`ty>+0!'AWBLozq-g*G!<}2قG<-J jՖT`ެV5UeqI2V*rїtWG]Y[^ns_JLJ>q|mLҡgݮ+~왪\[ɖX2MhʟU#z6F4&ְl6b+)Rr ^X!&\Uqq LhW5ݮmJ܌3 }T.Fb(AwTTgk׉9ж+w=S@u׍ԕצэv]`݊C7DVM$3i.eVîX#Ϙ]/S2kn nm]h1jM LC9u'u=7?ZM\,–]exa^.UU@mz`BaI>_%&w GC6`p/ C;ȉ>ӲPZe~x?u Зӌ%8,ArpL]ܗet {(f]^i>B0`G⚤SeLJ48eE,RJ0y(f[]#ts )#X$T, LIG!N-?ڙKkFEVb(v!'B )`S(ԅ(`{2OC✪kJ~Pi0XXn]ƵpRfȉ=Mf$яgYE9i h^yt F'~o/ }J_.? Q2V} ~:%1| Y~ \Afr\sfftd54 !fZew{κ$ գ*}8zch*~ %4HIĜ컀26W)q 3 #hvF"~ُcIt >sT1ƈ(>)B+Ȳt}'OB~˦MdD7(D& ̮Ϻ=Z mLe`0RthٮLbw9R:;ʽl˔wc.yw cz_<貴9ozga0 !"ARK8rRIb;rR哵" IDATr%%SJ"U";(q,Y`  aett{.Ox==}T3-w9{~ px x㾗$ 7>Ǟ|52I%\7co{ZSߵ,lExtS^>Ų>1(B+XeX,A"T0&W{4U%H]zt?/+,mmapc 8+Lptj @5l(EevُS9|i(uKnOk4Bb{#z[5/cG֙Wa=|N)D;3{[WyJi,LAnW\/\pSE/{ySu5ɉH+s6{(y`4\7n=VkYZvFHSU劓Jc :I,6Z˹&XlOتʠP)YuCɉ;SCfjcV=XԆ2z8֐e使!g*LzyIiIQL=KHYMAiK4Z3qK~&Dv6EIHSjzΩ^ 3}/cNOnJ-sX{W~Jә'w &m9M54 Ee#./!X))mrep1ŤpyLPaDϔ19dF"|S+0GAfadB}w,PNJUbb/Y.Q#ab14r ~2*xvDm C oyhLE !83V?\Mp+D/!(ȁ?#Y8M7%R TYA 8:i{9poϧ* 4nASJ* 'JBΓц*&s|hM+>d+F0SF փm)][ OhԓOaXm; pcG׷qQ1u.+ĀHu9QqbF  Ӑ)f"t W!!xv} abbkK)m[Ά (q/]!<6aTo.;y'gUJ&Qc5_`_u=Hi{B#FdY]%;c~aܻ߁Y(+/|㡯?ފxZ'鸽 byvqNTq]ZABqY}D JT5Fn*s~ػ'B3r^&=s#.0 j)0ً;Wia3 C. \4ZIH_4$]ŀ~pbȈtu(,uwsq۶-?_Ԣogpx@b qҬc?x۾~=1ݏ?Ԉ `lH{vW\Ccֆ91JZ5dp@]@?c>;\@T|ܲV Sx"X٣]k~ D࡭âP:-GOhʓ s0D˔iDCh'4V˵&_W}&:KD %M,JDzh/E%b|ܧϑ T|ҩW't*"_ʼn^S"S7at ˛/:咅'UinT`d6S:l*ri&4aL)iXی>) h!e*zOMt}9u~)')&j*Ꝿ^hT6L5RS"I/(Jr*&JB,eؙS΅*2O")54w$@,C"/:VWJn.90@E-$4a`jJ#j.nm mlWĮ! ĈY$HZz֑@\S (FhAļ@$&I5LP|ȯMdY} R0pVyCȅEy152_7(A{a( m$H+rcY-&A" EPphVx H@dJZO$ S68!-+Ppy8?ZC Y7 hmbյ]\D]c*(T5ĎVvkjڰ϶\kMj+ xsw.9-x޷w#vw1q8OX-0shTU"8`j;o=jHHgOdZWhlw${[s=KW(PIIVw5OpO!E :nzp8{ ^+FBRJX!ȸ[ E0.l fD,[_5HwTRh2 ?Ha_o[Qibvx[Yv༤9F>} 0@*_Z{oָމsbk/q)qnoue9kpˮG>)1N }硔+>ZWfaьlnIGpUS# y9xëE ZI`VrL?fTSlVk Z`w>DS>%x^ž|1a~٬ 1;} #1BUWa+1 hsrI-)4 ON8,J#eڔ&4~ZȲل@OiSS)1}]>wٜ4L?i@(SXkdSCX>v|Ԩ輒D\!M]cphè7)B /B*5^`ɲ r M>p'SbC:\"K- r;4{/ F bNcP*((Z^Ue` Cн36XH§W(٤#ŌE"yǨ7eJP$(ژL B \m {"( J.ăxGG t+ğGP)JqD ׆7!3H#2;,Le/X"3Gy"Ĕ8PO?c걵5|](`\¹AŠxL_Il+ϡO>8^h1x,;"Tj jwBa\qk,cowT 8S_?x W/_a~=9sEJai<%.`Q3Wuޝޣ90Me%ET.Z ȿ$hqpbP _~[_O7vEyy|%}3w זbdt8{ g7_xxgOgʭ8׿e7ߏ}mp3?6?uqc4}w[@,_sЮx>obogks׬I5x7_LQ)nGeHKAO}ao~CBSWh`٭ ~C?Gc]w>?_W7TZlgO.N _~Q9%ٮ8C-WX1j2 AXF!׵xzkFùym(W6OʕVhWOkX)ch"|a05;Eh[lVUhWcAR Z@&`{A%Y]\)nn4wtdS%,Kosa>getBstanYPTo 5J&,VK>,KXk1ͰÜ]I̴6+d.Z-UJ|o~d,7=fx1>t1m:'KǙi?}SAfJGRF4_cX(X4YQRAh/(("XR\Gj@Odӥ(!46 Rv W:wQs@) ه^-|@쯭2 UmaBD m5泆'M-(;&P"rm؞R \:6#-$5%1ޏbP `~o.恬e@dogF(ץT(b"c%"\%uj(KAq P`.ewPYh'ϖF67Js H >x4/=C+~.`tS!d>*TOFcp8=]hü:;FubqntpH\-iױ" D ktw\>`wg}ktSY\P&!o>p?vp* ٻ;[({S9 ٣z| n|l>Ge,*L_S58k7]5p}o~o/8fA/g.ˏ|mήgqmiXN!)iFe>Jk*_ҽ\!);'Y @Q401XyAH94L+Ý0VϿ0~O}ޑrM'|}w$.5yq=#$~H{#-asJ^z=W1x6i+} ~wһn n\ȶR=x$R!%\=8§?Us8wC|_{ KOzvx/qvWjM "_yu+/\ 7%OH8ܛ5DfGo5g^d3x}>i4݋{g~ wyb[،S~P#O|Ǜj鍯+^~7.v;shGx+x~ŷ>*+B`kj'Tm+AT1Ԣ⵹G>d"|e o2BL%SZõ&NG@m>,;==UwD]Z0H#ǐRIO>;Eq/c@'״R+nK:B8V+}s=|ɲN9!\EWYhL"cBg)^:a6eO°kcX.8>>(|>G4k\fSZNi1\&NӢ:=W9(BqO,SD Mt~gZ'=~|Me6%Nҙ|cS~+"SlT5ǨEؼ`p3hVPJ Ƥ'-edgz.Z;]e27[i5rSКy*%\48ÖZ@py)bq9@41Fe !Uݠj.dUžԹgcy/GHHEFiy󨫚~7yBbk ZB`ggX;g}];`gg9t]Ӳ}c>VfH ]`Bۮlel+7k SS8{E mգc,W];/]f'}7 xe\;<15so?s{;b=[m ?셬HENqŽB5+akWd- %ujމwB^*-#vp3%=F/?Gs/ ><[:?N}5u׏x*;~Hpn?wr0 ltd4L$>xeDqH|[ϟs\1eƔ>%c<ZvI~ Z}ǭ*cy^05ҥ+*B{3L*ժ߹r!=r眀YS)i8haGGK/GW,;(c;*ʢl6i#):B*~=_m69͡\%A6NFu>*i 5Aszzkn(ite T +hcAP 9G%QjŎlf{%'<cxbi8wCuNRɍytt5yNLD1I)]RVg<=;I)d-)%}f+T]K{dQ XB.bOoקo @uExk+AϷQ흂O^Z-}$Tٱ):= MIaO/ZxXVY:-B8>>Έw*SZ N0%TS:L MڔC ET vx@]9ЩifBNiIԗ$.ٟkNEaS~mftatR3pB"})2_J)܆XR9h2j4P8c8dl6FS0&Ai&1#4)ĶPjvsԵR1kPuvVjh2ciG"D75yt9G)Ɖ؇[0`;< ]fV+t0>%3&Bz֘~wtRQMdȒ1EӓLBKF YFOC\O*,IS_埦x H0x3!m&Mh&JIԒM/thu )+7I)uf떍d˂7JWl.912w=whi4]8 bB sGT(5m4{GMghJ%4yӉ1CTP!/RL6@ HAE@T np19IsaEGryP<}095U:[o&"?d،~$n!E5.YHWlfb(hK54|`ğQfR$W<|mCsV+^v} ŤΜwX.V|݉ǴVº +qX6n X1xoagwJG^P.6PF2~@7t:Fٵ+("^[n.^g4N( I#.]pppCF[]y'!cw|D ݪl530n;Q/0Fv"Vgwe4(tG p䮁36u )!8!tSx;0֢mWl atc_}N<ݭoA_dK0+\ ^~l~p}} 6-@/4Z+Jg_~T( N[408؇k!a)R3B12fARӜ T]*̘ݜDhNiI9 }&,I-+,cƎ ۋē4.nR0+X y0A墘L`c˜Ao3+|zrS&Ϗ&;1'E kϗl:#Qh.m~ti@ɔ&a0[K=\'-lšJA8D2!Z\dQzL 1#"+][Vx1LESbRH0mhNBR;E\RRo*Hڛ MYoBָBN*K唏_67+Z7+_ )YbI&{$fH^Oⱝp)Hdad1H@E|4%8HtUV5m*Dqtt(lC1u-3k(~v;Nmxgx՜ 0F=j -rn>gje]~@w /\}MSۃ"̚Wzh]3Ρa2}wwM1mqT^ (Dԍm*nQ*. \Ak[4M^ԢQU]\2Ei*c0!rU.PHgnֈpnϿ?{QeOxWy/g;s5uJ MQ63Z\y*9ѥ 72%Ot$ScQ AE2sBz srtv>w12ElN2T% 3=in k瘧l[68]͟'Og y#*'c,49JIhZ}n;ZO&(4h=)h(Md"DנWZLn[  XO~wV"c*Z,[҂R$rı.5 H씢%I" i=mFK4rjbl9ƛQB^ !? B}s%&)뤂~ ROjnN|75y'D1?|~e N["P,B :Ѓm9v c vwjX,0FcW[u!Z.\9ΞCߣ*UmGA ͬrr!ꊃ45vHEln ;fׯ:4MXZkhOR=4~@۶4z U]T umer*V]׳(:Dlomt9V0JV`28MAgl`+A9i+Sah-DOz<?7ss|?(O7@pky>I}j kAOf'BAO.6Hh¤$ A+&Ai,AKeYJ U1]2^JΝ όSNx/ <S#_ƦHhz@juT)7(QƑLD1XWɵl}ç8sY"Jr/ũ7CwWGZ8%: P@ ~ia`Rכ΍kJ/Yǭ_))*XagZ@,- 1pKT F*&+2bIv'|,J!0"L*!ş= Hk-{kd[9rL兙MMDVpyM M"(7/ZXi [apYQ,9 J捯Mx2)xtFȂtߕ.W)yPDy@t*y"y5GZFnyeĹ{DI; ]#BaM1@QY`kר#F-XgKۭ9%N:_!~;^s}xͫ_' /\l2up6b?kWǬX~#ԄtĴfMvykC%;/\?OqQL"&p"xn8{!ͯQEH_g!3+1a4 FihDu!*g;(^m#46抃o*ad-O.~s5ڍZ<2Ȣp|"(s2}YSƥ=u15I*)E/"`5ݼU1$8B䜦 *HCY~h}Y瞗uzDZ3f@I@ߔ݀cjt![Boa>i!;(CO'T گj/M*ӿ75nꜦ)ǽF':Lɘe16m BSԃ~ ~JϴeK4&ZSr|#e⹩N6l:M\X}3O2=5fSd5Z<pG DF'r㩀?geCV3% 96U.߳+ys NJЀb1"y D}DyUR"S#$G)t&\1'd#IU~X4s48dÈ c@2DЩ]uGJLa0_k. 8 v)@"hHM۩5 A@L0ǘS$+˦#O0ko Ec9ky~b $*F# M]m[}aZi14.WhuȔ̺e` V֢kPȳ;U T4 U?§ڗ;/`wkc\x𥇿{1Sɘx !8(U^%E,HAZnb0 VedZ#Xt.t֑֢D#ʾމxe7caש )Em8$() Q)ggz w٘>c|u{L/XNSnqt"?j0s)@DhЦ"za_ɝ^HB֮q7sKO 6'ƛFepR~3ikZ8o*|7 SOz<׼BNo:/kAVQt؉/a=[XY4#$BrGõQjl)5Tʅ2 g$Ԏu)'/e"d rV4UFX0EC.4.14XŌR|TJ8T&13" K& .{~d/"9uSNnPh!!f= BB׏eCao };{b@)~8j5H}aA<ھCe,Be5_= IDAT.]B0ߚc6cfTGb| NJgϟC+PQo*o ĵ|p*8wvv8kBӧUquPC=;bO;nV5XZɩ(48iW< S8pͬbjy?Ϸ )ʢijnZJ[sTEZnaׯ\fa Y `+ #dB1!yQI#6v)?JNhjcdb!3ы L ^3 [$.`0qߘ&h, p|h46(ɭ&:JBQ$S$ i},^Z>2fC_xUD)-f-Qt~{ڀtVO \ kdlJa/)Xȳd:U{Z w#8YM]g SAi֊v$w}.E1'.UUU.H7Vn%]^`BsZx2%e!n2| ԡ)oyz{Ml$ğ$o?)%$ӹJǺqdSA.sRh&fIn-'5'=ͼاlo6Id9M͍I D&v ;HI>͈$T6W= UJ(!H4(1-`*!" Be*P&qy 𒲩?cd@[sY2Fe!DƬ"zTU;kLsî\LX7\x|y⡡@_Seh8z"fGy3ڂ$%**i0`zvXXѵ( 2D^cq|aŅ[5~`Qd4pc>yVUm.Wّ,d݇Zt>-xC@m,n9'"px|,uZCLTةg]V w\3i [n=k5^W칳r211slq.?,f3t gΞz [~w"(㣫XaXulI@-yݯ5|-\?ijaN+2@Ty63 7*1z.@p:V\0}m0~:A1xx'EZ@[dV!f7ڎtm4BɤB=AhK!%eR&$avΥVJ ;OYkD0=jl.- IA΁9kb]W(6d@,N4EV';-mF! 1A&J YO)yݤ6*s)E'1幥u BȢ[5-؂ػegYԪh8db棗aX,OH t'zx+〸 >ux]Ƭ`2*DIRjO(O|ԇh?!%r 1FV78LYLEtjשh?=.5#S&wz/S1&KM9Qx"1@QJ!8'q_ Pvz9_L8׵`Ssw2Qt J)\Y[W;YT6 >xTMsΣͱj[@={._|>}gW[ΟDž ڵi*2qr۶-oYs_s;"D&QuLBDJYId.jjIy0i+$!Dk ڲ JB :;4JN"5¾P\BL9) H1FNMbSi%bp%)E*Q$8UJޠCh]P5T~&FR4%: NDoB;&$O!ѲF)B*ӔpqI1(Ғ20u h.H\$@.2V#%Uq)M؎fI[4CZliFԿ`Fy$_a4$g 4 j@r!;_'Vj?w_k7>1¦ve&TY 4Ϭhf-5!҉zRmKԵDq1!,ŶӀ/ cK8Sc#;^z'cK'!Ǜ0'<ݬ^EbvSx&ʓ~礢f/F^ +Sߺ\ބ!T3XS&@ Q֔тTz˗hQFVXX]1|Fʒ $2=kK:8a K,Nx5H?XX]y\9g*hqݰ10ZL"xON]k,cV EQ5 ]4!v; KK/}/)c}uZh!yD@-5Lo3Ija7$n;Ł>z.8M%>x횒JW50P=|OZV4pU`mᵂPVP>Ν{¯}<}iE+,RԔ: hPJΝ/WWn6(2Z =Bq*5$)} 6}C5ꝁȼ,I2VE*^mS5w5E\Lyj\.F2"3k*7İ$ "J+3]сBpոL2+:~h@"HZ4E2b%]n|_#dF)R?N'6Qp^hH=X9 \HzIʁA])#O*>!! Ǭ1)G#S,4BN?N D,R94j^'(9I.; n~ ,ɓ T"p,+hEG#Yd' _=/rsL!?dy D:>mr3d$hq|| 5ѳpb. ܔ_c\n>~]̋\>1yO->n5VaL!*M1>J~|˾/ufRq_ԡY.t>qf0727,%2bBB =S"" JD’E-BFɡY;(x ?ɂEdd!YU܅v$' )J| m3BF#B dF_%-laL,u;oT5hA ZK9 %С* h)ѳD3x/`Z>=C>ױZGOwI>v;˓:_t]jxO%FxB$#N-̐.CRQk]*2Ib*ޗEQPeIf"ziFrN?TErOl2H-ForqZvHAB  * TBpې|:5dqZg4Ă>#&-D3@RcB&j2^\`bWFJg䯤l,T 8]C겧.d6aB\a8QIҭKҮEwM =]|SW !—*OWly=GawC@~EsΜ&1'_;ךG)ȮsJN|| "=p a/1O:\-oC?|YfAf:뇦6x)]Fc—B3B mbL&d!5ϢEEZI%̵/5ftmL;Jѩ wG˾P61M i,^#gmڔ|^- <)mch>ME_u㦑R``ѵ;QGv2נZSB1fK4Is0"H`xPDvT(s ꪄ@y{_U 0!,J>o-x ` (̟ۖZi8nZ,K]C'YCbE pG)b Ck{^_/1CُS} k~T8QPe0 AGcPٱ%j6!L"(aDɚ,1q:Ac+]Y i3=i<Ѥ 6ɦ 2O7-y& ]P3Xa= Tw?Rw­wk!5s*̾xޡͻww-U9|!>O#ƍǏXpsjGYMN79$C?Jd^WQ}CO!c&}M?7e?m?ĢM{?q| Ė1V9%38# ^g@rS^iq<^iEYRR g\ ):d,R&Nn'zKhx )EB.ND&PlI@|\~O:L2cHfiQW<'1ȏXqt|`m0<֦b*\h-~ؑIЛTRC pou`(UR[KtyBEM@l8CG,pz\GY(y>u5M@ oO6 ؘ޹ kY{I9%n5UUm[RK\__S ~Qpu~BSx!451y (BiJvT`B̪1M??EYVxH-S!/B)ZoׯXC@T_iBMB9QVOn{l8P¤b)1;'k1&0Y3ǏrjG$ӏQ2>#x1.g)u<"q̙ 386ybJЈ(Y64t3"%{uEߖ_K`ojFV`V_HH1l1"\czwkdƽ1A ~:k[lMz(k2#%&~O/^ !3,λya-Q'.R>{y!}9G撈 9%\t#9+}@IBw xK8/!m,PGT(· XBBg98ϼdG IXc^ ym :0jQ(wDQRKFD)>ϔ@{?<?QIΣ|zU2Ѳ5h| 4 n3D":3J ,wGm;I83/ 3ڹ3+RrdW1]%wa@%jFʨE: u4#NtQ JGGG@ڶE8>:55/:XgiQuMRcDRкvEY8jV:xॗ^zRXv-x >v;Jz>=9vVL%__vU :@퀪(eу< eCe;$4$!%sSjh@w|>'( ##cЊ̷Ӆ˪SIg;%B K_!X{HEkPW%ԩ. uSeBrJ P d6Sw)jXTyN*aRIv+yti ͦeq:tK i;wD3p@g>1gHhplhN73Jy~Iދ^d`)E`zpD4cC0|*MB~By1o|cO!qCq m*uL@ׁ;!X8;`Z(Bχw~l23j I- IDATq0jiRT%v&Itx> YXgQ75<[,G8999shz,,8u]zGlA]ӷ xݶha¦nkZ fH:::Bu \%v;ev;eZQJ(trDlX/͆vc{X%KfX*`EH@)imn$Eq8b}0Ǟ4)(QzzO:~]c=i1RF)o-Rr)9vD`4altJI^kͭM47Fn$ё;8Igp%!ue&}T-NÞDLB4T"uG!$? )AF6"20"8 2Na>K\Q' S?&F՘=-GwM &Hxd h::˥Go~ZW{yo Fʋ9fyoKO&PW h |&iPωxOIޏXŐ=r6f@3B BC:G%]?0Sӭ̼?{|sY`m7Land.=D&/Dm9|G GbM<11F͐@{^^+o$h^HPpA?nGu:o_}K5r^Oeױ\UՄsԚw,˽XEgsP[,~sN\&֜okړ>Ӕ`T(?:yZkD{f_=fByV!̡I¡ob4Gw&h,Th 9!9="~$ ?h>Bb7"bhR`72"Y%wC$ׅ,Y8 1nuchQI*a3{bp#0aӘ:{dog]JktQk]fxoayoe!PW%nv-ϙ @?lR J25<^0B#c`!mWWv USo٠Kc 8;=EY8Ә=BbnH_%VGK-\ M|Ԁ ¡(%Ja d@QH, g,{,XZJ.J~B 9RP,MRRCJ_hM? ANBeUo#zH8*KTe `5^\l4..HLWx1 A)Kf*UO2K)i^67Iʢnvm EÅ;i bXQiCIB`׶sHeKd1D*6"B)-%d7%TWEA5mO|p9?4caY @!E1" %=XTY^/\J3N^%.D4sW76И|_i>If2}Zƒxg8uMRDs.I#,Oi.b:&#Ԍz& T29ؘ)&:sμM1+uKo2%NY9fLxMfۓGiT bQ~"ho+cB }9Pen/yQ5mNw|H XUU5Q%2e2E{Qz" yqc$DNg\z101 .qLYcxq1B*TUul{[P,-h/i쇨26tM~wثw51SL"Ȁ;ЕR"pe* bA/Sg3 9CM0a< %Cs>vl0(AfѴA 芯]ss֣qS̷O8"ͮy`Rī ~%~%$Vsf&JF,1R4 &Kͧذ{^  LףY,xOdi{5EB(-8S21*6@ˢ@YTJ(J?FUK!ڋ4rޢ*J3jѝ&ɝ";r]!LA:uefk f\*b%bMćQ Q'i/7 ]wX֝g@RQ$eDqnHS&E`If8*k!!ܰ+oxݖ4nϊ׉( BMIG89tD7n: 56%.cJIF |ى" -!N} !m8@%ubkKtqG3Y 7|3|K_z{b(7撔eܜlx3i9vT&!Y;B,܃NN=rCQ k=V05h-Qh. pzskquu}c VI*MG\e!%5X zzz-Cףnv\v;HPZl-CeY@)f `\`Zj(MݦiX,A[H T!҃ h* cBE *L#qR+ba%,焂1:֍9(6za3ݮEUb%  ƫ$7Y.xsPTi\.1k8JRq:%'{Qi\( v;/pTEHYۡjjH譔m25*1ZNE!KuX"YTU!liQa S6 @iL.iǘ蝳w|@J% y^NwHbd&!פ 3QkENoM$ciC"315;%c&bϙr!ÏT؊Fx(a K-xgrED'Rd$L'cnI4 0^)btI{C{nBs= wַe/BR8ґ\>ש|.o/`n :M:Q’kcD`=?H'v'1NxdǮL\ⵜ>utnA'NuݜX>$CžЦC]2UXY+!NYYӢ}4ď8FxI :b8-HH!G.dS E1rfMs9L /CFFs-MuZHK>awLPv@TI3,@o<:r8;p0/J4P(Tg$$ z~Gkɢ,p4n5JBmTW?Eo`!蝁e ;tpv;x(UJay|fJ TuS|vD@jTMn -8K/ m%,5XYZ׵膎Z}PJ^"=(L3#t3)@~c"`l*gN{*&v9"󣙜q"#^C/B "??#Sk,suf̋ܤMCLXd%@b:䲤O&sT!3h>{p0G_\?yx?kEEDp=>~g] %b$B:>Ihwk[]`׶À 0 Tsݢk]KGG*\4[X?0mԥ6A[H,Dњ<A3$48;Mwys's$ 'q\$8=#cRKN,jǃd%Z(^<Edtji*Ja+MZ3^HI$D9&sF93C'f Usb 9E~J9IFAپBHFr$LX'>}%sl0=/<&7]zOӦt LLrF~z>*e â=$_AVHD;"eCjtM> 4N}l5$K0N C5c|M,5ygh8*ܹx8GBF8 ༸3ci*s<(}<4*4%jЫJ\QbВ |mr&|=~M>m0Ơ독}܇|V9ˡۇf~n'>g/}m>7&f)iS4kG>uø p'w>C7fҙNU92ς[ )2  =tAj;w,*,-?}V+H6ƴJ)ɰw}}su`>]viP%C۵xw=.c V%|kF.RRsC2`N3SVWi펂u2# :l6v]JuR_-_G#NMTte| \2|A@^VPPՌVM*((1yD2ϓ_0]3 b5-.tNwtO 3+B !- D$' d6ErhÞd;fzO_{'L; 'RY&3p6G1?!)37Fm}߲ BN:zDOf\"M2n w7|/5\Vs% yRhwn IDAT.Q#yba"7y懆\I7't"~M|/0$o~0_ 5ʋ}nx #wg66mhUχ(7DOh}ݟ؏7|Z)9i?1U0CUAdh,)ńl=F~Ď{1.+)GlZ"N-y#ߤT/xxE`жk C MS}vb~ܞͼ / g=gXGdl]G!O]>:yApS|b-dc).6 Äf3GiO$, ,D)YO:7Mj5A<{k-Lއd0~"|mf_bPѿKew(^iUwhʒJN_b㞴x>Q&lT^ó9&L,}>"-~QG/s ^Ɇ8t,&1{o-vBnjċ/Amɗ?~.ݮg^ǣG`ۡ`"5Σn*4p7&EQ:Te )$o(}8;;vmGJ!nZB}?{ݻwNOΰYoqu&8;=î\0h`E]ר 2MKP4kc)[ǟU5׶gg̀OBS , yt0<.JXpzv%=FJBcREǝP.) tLɁއҒH[`nw] UU"x- vK/;$uf$Cs * eUCHICAzMސi^Ұgd#qw0mZ&C,pzzÇ>@4#1Ќ+cw8! 7!Ey# 4Ӧ PjLĥr:I+s"|,܅ >&+5O3W[eH .6ԥM]\(cNX4=c|2nN>?T3b v5&H$6Vvԕ'_NZcJ -&tĄP&LӞ Z2ʀR!8'_&pb_al<+ܔ˄x}f>X>ejN\,L 2^K??폿ۨo6BJ78ra/)wZb*,˄:̻EQ( }S! DdOg<N}4 (ɋU-$Rєw˵֩R{m@zs}^f׸PWUty=" 7ڧ?mc{)!vg#b15ސQũIV],@NFyDzCƛڏ2݉%˓К9L\a"eQ@"u`/B(E>H.hjLj EfO!˯#@z.JRruIo|{ˣuz{9z^*a ޠ, (.5Kl&?x'g'g8;}OðVz כ5I!Xo뜅%ЂMs d.U++yytkl7[H)Ke ܘ\$ a0Z{}JWm;:T$<5iffv>ٟw/Ѷ;4y%EޗE6u]m(}CXA70X(MIYiT|E[: (KAKp5pr.Ԅ;!(bmcnvEQx z]t>+x6 sEQ X,Wx vɼ||tz]b۶D4jJ4)8WJXM#ߤbDP< z'"WG v4&# =L5CE '^\cu>@*9HI> u0PײT8= RYh81*b5Mhz&%eJ=s= |Ȥ|h"ӨLR+KO2Y?Lt葼bx2"FP,>(@RhN913f6/.%!fb*tR.FLe=t=\kc(Y5tR32 ' >JA4-qL=<+$]+>ӡsl3bbg)k,~+2pKx9(s{.3B]X)59סo6;9:9%js2N4ǎ}F1v/n>N |y˼|]n܇ }C7` 3_Oi% 37R]D&!2èO}ˈy|)ԡKIC]cVTUCh`쀳pzzr|z uӠXn89:M;#l9EQL~|`WeG &SyHZkҽ*j 5Ke9\q8C;?WČky<}%uɛfSM"N^L\4wP!+ę;yF k-O)oQc'[Io2EAD/Mg#SJL23ʩ%'H&{]I㎱MNR LC<2IvMfw0|S{H$OsECj  wÿ_ _WBx{!R !yqKU">/8 M:߹.!DԐ}n5ˌ v{rnZJ+33[o=1M468)81css}c<a8Z{w?u b C2HPV%677q.FhD#ךF}[%ȱW#L3TU{`1 3@ e "$"?C*ԥ%XRwFcצ8HaV#Crx#wHߟ P " ug_>ER)ϦLOӢTf>Tk:8c8Gux;wh.d;q?MZ`A}g~i^:p[G鑄4vZ}:id(c=#C6H3A:H#4p\`>?/`>|~ %|{_!͆8s[>)!6F#y,KuR i(rgsFb400g(4&187GnP'Iݻ(/7n\p0dh.]3>(p^|Epq=+I}Li]W#`U]"Nb GC_PJ)E_aooϟ8` +G'],W+,+{rB#MT,"$ ?aQ 8l` Cj}^kʏ8 l#)F^W5D@,d"olԪ#ld֖-8{ Ɠ ~y ??zJq5 0F/FJ ~kOQ$kDd\c0P,qF.TYXeF>e?Ќ^fr3p$"#QNjĘWXM5&h&vS)K1#fDj⦉PU)cKBs B/6 ޤ a޳Cb }!K7n< 3R"( 8L}S#){\`2=@*ܣ#n޾ _w˜5߾_ }ֱ3ǀlc/ LeHTh!"Cb6u5mڮ3t:UL\+()@k/j{]ZƿV{$Qi!EDw  "G]|/4 ]读 ~+nj*kVkHIDATGZgRWIZ0|*hd1zt|^Lc4ⷞ_I1XW+pP+DQ48Zu]ۘ.G(%YH k-qMlno?2(E4FDN.ֈ"@Slk$#h4HSM)-4ZJ$FX\ͩ N 9~=}L&80=!M3# $8@'H Bd4 D Q&{6ʪ`&pw~70hd=&׬"Qk`%CH!8c-޳Z/S-%Q שy먪5w v]<} ^( JK4x`v4˿|FMyBS&amؓNd7 l!"lo*רʵ=Q0̒,H\&TBIk%SaqĂOm$M6)ܞ51k8^-D="4WH (4ZCՆ4؀T lP+ GHShbe@hHҶT7ަn>uiY՝7h(`"yddVf;[N (#f~/u8 \9ɦwZ2L߄NEs, !T$Dk{o(ɹMҝFfFYϓ7"Y;.cgP&,8p{a~t7'G2L[Ii1CX=EDֺ"5>'iK }r*|_%x*IENDB`highline-1.6.20/site/.cvsignore0000644000004100000410000000001212261230137016351 0ustar www-datawww-data.DS_Store highline-1.6.20/site/index.html0000644000004100000410000000303212261230137016353 0ustar www-datawww-data HighLine

HighLine is about…

Saving time.

Command line interfaces are meant to be easy. So why shouldn’t building them be easy, too? HighLine provides a solid toolset to help you get the job done cleanly so you can focus on the real task at hand, your task.

Clean and intuitive design.

Want to get a taste for how HighLine is used? Take a look at this simple example, which asks a user for a zip code, automatically does validation, and returns the result:

zip = ask("Zip?  ") { |q| q.validate = /\A\d{5}(?:-?\d{4})?\Z/ }

Hassle-free Installation.

Installation is easy via RubyGems. Simply enter the command:

sudo gem install highline

and you’ll be on your way! Of course, manual installation is an option, too.

highline-1.6.20/site/highline.css0000644000004100000410000000165512261230137016670 0ustar www-datawww-databody { margin: 0; padding: 0; text-align: center; background: #abc; font: 13px Trebuchet MS, Lucida Sans Unicode, Arial, sans-serif; } #container { margin: 0 auto; margin-top: 10px; position: relative; width: 785px; background: #fff repeat; padding-top: 20px; } #container #header { padding-top: 20px; height: 141px; width: 785px; background: url(images/logo.png) center no-repeat; } #container #content { text-align: left; margin-left: 230px; width: 500px; } #container #sidebar { text-align: left; position: absolute; top: 181px; left: 0; width: 170px; margin-right: 25px; } #container #footer { font: 9px Arial; padding: 4px; border-top: 1px solid; color: #aaa; } ul { list-style: none; } li { border-bottom: 1px solid #e8e8e8; padding-top: 10px; } a { text-decoration: none; color: #15528a; } pre { padding: 5px; padding-left: 20px; font: 11px Courier; } highline-1.6.20/checksums.yaml.gz0000444000004100000410000000044712261230137016707 0ustar www-datawww-data^Re@EfamL,51V -6YvjG'po]/ׇoç/OOg=?^]3uc=#q3nݔk&6:f$ L%vS vr3L691Ep`vl218WzL2]S| uicy5Lo`"AbE鍒,Nu-(5Tب´N1%R];Bu`highline-1.6.20/COPYING0000644000004100000410000004311012261230137014446 0ustar www-datawww-data GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License.