diff --git a/tracks/doc/CHANGELOG b/tracks/doc/CHANGELOG
index 103cf754..f04924f1 100644
--- a/tracks/doc/CHANGELOG
+++ b/tracks/doc/CHANGELOG
@@ -30,6 +30,7 @@ Wiki (deprecated - please use Trac): http://www.rousette.org.uk/projects/wiki/
14. Add ability to sort projects alphabetically
15. Add "starring" of actions
16. Statistics page with graphs
+17. Rake task to set password. Usage: rake tracks:password USER=useranme
== Version 1.041
diff --git a/tracks/lib/tasks/reset_password.rake b/tracks/lib/tasks/reset_password.rake
new file mode 100644
index 00000000..743378eb
--- /dev/null
+++ b/tracks/lib/tasks/reset_password.rake
@@ -0,0 +1,25 @@
+namespace :tracks do
+ desc 'Replace the password of USER with a new one.'
+ task :password => :environment do
+
+ Dependencies.load_paths.unshift(File.dirname(__FILE__) + "/..../vendor/gems/highline-1.4.0/lib")
+ require "highline/import"
+
+ user = User.find_by_login(ENV['USER'])
+ if user.nil?
+ puts "Sorry, we couldn't find user '#{ENV['USER']}'. To specify a different user, pass USER=username to this task."
+ exit 0
+ end
+
+ puts "Changing Tracks password for #{ENV['USER']}."
+ password = ask("New password: ") { |q| q.echo = false }
+ password_confirmation = ask('Retype new password: ') { |q| q.echo = false }
+
+ begin
+ user.change_password(password, password_confirmation)
+ rescue ActiveRecord::RecordInvalid
+ puts "Sorry, we couldn't change #{ENV['USER']}'s password: "
+ user.errors.each_full { |msg| puts "- #{msg}\n" }
+ end
+ end
+end
diff --git a/tracks/vendor/gems/highline-1.4.0/CHANGELOG b/tracks/vendor/gems/highline-1.4.0/CHANGELOG
new file mode 100644
index 00000000..97bedded
--- /dev/null
+++ b/tracks/vendor/gems/highline-1.4.0/CHANGELOG
@@ -0,0 +1,204 @@
+= Change Log
+
+Below is a complete listing of changes for each revision of HighLine.
+
+== 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].
diff --git a/tracks/vendor/gems/highline-1.4.0/INSTALL b/tracks/vendor/gems/highline-1.4.0/INSTALL
new file mode 100644
index 00000000..c22f0414
--- /dev/null
+++ b/tracks/vendor/gems/highline-1.4.0/INSTALL
@@ -0,0 +1,35 @@
+= 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.
+
+== 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
+
+== 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.
diff --git a/tracks/vendor/gems/highline-1.4.0/LICENSE b/tracks/vendor/gems/highline-1.4.0/LICENSE
new file mode 100644
index 00000000..ff6f232c
--- /dev/null
+++ b/tracks/vendor/gems/highline-1.4.0/LICENSE
@@ -0,0 +1,7 @@
+= 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.
diff --git a/tracks/vendor/gems/highline-1.4.0/README b/tracks/vendor/gems/highline-1.4.0/README
new file mode 100644
index 00000000..f28478bb
--- /dev/null
+++ b/tracks/vendor/gems/highline-1.4.0/README
@@ -0,0 +1,63 @@
+= Read Me
+
+by James Edward Gray II
+
+== Description
+
+Welcome to HighLine.
+
+HighLine was designed to ease the tedious tasks of doing console input and
+output with low-level methods like gets() and puts(). HighLine provides a
+robust system for requesting data from a user, without needing to code all the
+error checking and validation rules and without needing to convert the typed
+Strings into what your program really needs. Just tell HighLine what you're
+after, and let it do all the work.
+
+== Documentation
+
+See HighLine and HighLine::Question for documentation.
+
+== Examples
+
+Basic usage:
+
+ ask("Company? ") { |q| q.default = "none" }
+
+Validation:
+
+ ask("Age? ", Integer) { |q| q.in = 0..105 }
+ ask("Name? (last, first) ") { |q| q.validate = /\A\w+, ?\w+\Z/ }
+
+Type conversion for answers:
+
+ ask("Birthday? ", Date)
+ ask("Interests? (comma sep list) ", lambda { |str| str.split(/,\s*/) })
+
+Reading passwords:
+
+ ask("Enter your password: ") { |q| q.echo = false }
+ ask("Enter your password: ") { |q| q.echo = "x" }
+
+ERb based output (with HighLine's ANSI color tools):
+
+ say("This should be <%= color('bold', BOLD) %>!")
+
+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.
diff --git a/tracks/vendor/gems/highline-1.4.0/Rakefile b/tracks/vendor/gems/highline-1.4.0/Rakefile
new file mode 100644
index 00000000..26a68f00
--- /dev/null
+++ b/tracks/vendor/gems/highline-1.4.0/Rakefile
@@ -0,0 +1,82 @@
+require "rake/rdoctask"
+require "rake/testtask"
+require "rake/gempackagetask"
+
+require "rubygems"
+
+dir = File.dirname(__FILE__)
+lib = File.join(dir, "lib", "highline.rb")
+version = File.read(lib)[/^\s*VERSION\s*=\s*(['"])(\d\.\d\.\d)\1/, 2]
+
+task :default => [:test]
+
+Rake::TestTask.new do |test|
+ test.libs << "test"
+ test.test_files = [ "test/ts_all.rb" ]
+ test.verbose = true
+end
+
+Rake::RDocTask.new do |rdoc|
+ rdoc.rdoc_files.include( "README", "INSTALL",
+ "TODO", "CHANGELOG",
+ "AUTHORS", "COPYING",
+ "LICENSE", "lib/" )
+ rdoc.main = "README"
+ 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
+
+spec = Gem::Specification.new do |spec|
+ spec.name = "highline"
+ spec.version = version
+ spec.platform = Gem::Platform::RUBY
+ spec.summary = "HighLine is a high-level command-line IO library."
+ spec.files = Dir.glob("{examples,lib,test}/**/*.rb").
+ delete_if { |item| item.include?("CVS") } +
+ ["Rakefile", "setup.rb"]
+
+ spec.test_suite_file = "test/ts_all.rb"
+ spec.has_rdoc = true
+ spec.extra_rdoc_files = %w{README 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@grayproductions.net"
+ spec.rubyforge_project = "highline"
+ spec.homepage = "http://highline.rubyforge.org"
+ spec.description = <!")
+ 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) %>")
diff --git a/tracks/vendor/gems/highline-1.4.0/examples/asking_for_arrays.rb b/tracks/vendor/gems/highline-1.4.0/examples/asking_for_arrays.rb
new file mode 100644
index 00000000..6c62a0e4
--- /dev/null
+++ b/tracks/vendor/gems/highline-1.4.0/examples/asking_for_arrays.rb
@@ -0,0 +1,18 @@
+#!/usr/local/bin/ruby -w
+
+# 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
diff --git a/tracks/vendor/gems/highline-1.4.0/examples/basic_usage.rb b/tracks/vendor/gems/highline-1.4.0/examples/basic_usage.rb
new file mode 100644
index 00000000..60ecdc18
--- /dev/null
+++ b/tracks/vendor/gems/highline-1.4.0/examples/basic_usage.rb
@@ -0,0 +1,75 @@
+#!/usr/local/bin/ruby -w
+
+# 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
diff --git a/tracks/vendor/gems/highline-1.4.0/examples/color_scheme.rb b/tracks/vendor/gems/highline-1.4.0/examples/color_scheme.rb
new file mode 100644
index 00000000..6d1e0a76
--- /dev/null
+++ b/tracks/vendor/gems/highline-1.4.0/examples/color_scheme.rb
@@ -0,0 +1,32 @@
+#!/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
diff --git a/tracks/vendor/gems/highline-1.4.0/examples/menus.rb b/tracks/vendor/gems/highline-1.4.0/examples/menus.rb
new file mode 100644
index 00000000..e31c11df
--- /dev/null
+++ b/tracks/vendor/gems/highline-1.4.0/examples/menus.rb
@@ -0,0 +1,65 @@
+#!/usr/local/bin/ruby -w
+
+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
diff --git a/tracks/vendor/gems/highline-1.4.0/examples/overwrite.rb b/tracks/vendor/gems/highline-1.4.0/examples/overwrite.rb
new file mode 100644
index 00000000..1ca2db52
--- /dev/null
+++ b/tracks/vendor/gems/highline-1.4.0/examples/overwrite.rb
@@ -0,0 +1,19 @@
+#!/usr/local/bin/ruby -w
+
+# 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) %>")
diff --git a/tracks/vendor/gems/highline-1.4.0/examples/page_and_wrap.rb b/tracks/vendor/gems/highline-1.4.0/examples/page_and_wrap.rb
new file mode 100644
index 00000000..3209a4ab
--- /dev/null
+++ b/tracks/vendor/gems/highline-1.4.0/examples/page_and_wrap.rb
@@ -0,0 +1,322 @@
+#!/usr/local/bin/ruby -w
+
+# 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(<@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 it's 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.
+ say(@question) unless (@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)
+ 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
+ explain_error(:invalid_type)
+ retry
+ rescue Question::NoAutoCompleteMatch
+ explain_error(:no_completion)
+ retry
+ rescue NameError
+ raise if $!.is_a?(NoMethodError)
+ explain_error(:ambiguous_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 amoung the provided items. You might use this in a case
+ # statement for quick and dirty menus.
+ #
+ # However, choose() is capable of much more. If provided, a block will be
+ # passed a HighLine::Menu object to configure. Using this method, you can
+ # customize all the details of menu handling from index display, to building
+ # a complete shell-like menuing system. See HighLine::Menu for all the
+ # methods it responds to.
+ #
+ # Raises EOFError if input is exhausted.
+ #
+ def choose( *items, &details )
+ @menu = @question = Menu.new(&details)
+ @menu.choices(*items) unless items.empty?
+
+ # 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 color( string, *colors )
+ return string unless self.class.use_color?
+
+ colors.map! do |c|
+ if self.class.using_color_scheme? and self.class.color_scheme.include? c
+ self.class.color_scheme[c]
+ elsif c.is_a? Symbol
+ self.class.const_get(c.to_s.upcase)
+ else
+ c
+ end
+ end
+ "#{colors.flatten.join}#{string}#{CLEAR}"
+ end
+
+ #
+ # This method is a utility for quickly and easily laying out lists. It can
+ # be accessed within ERb replacements of any text that will be sent to the
+ # user.
+ #
+ # The only required parameter is _items_, which should be the Array of items
+ # to list. A specified _mode_ controls how that list is formed and _option_
+ # has different effects, depending on the _mode_. Recognized modes are:
+ #
+ # :columns_across:: _items_ will be placed in columns, flowing
+ # from left to right. If given, _option_ is the
+ # number of columns to be used. When absent,
+ # columns will be determined based on _wrap_at_
+ # or a default of 80 characters.
+ # :columns_down:: Identical to :columns_across, save
+ # flow goes down.
+ # :inline:: All _items_ are placed on a single line. The
+ # last two _items_ are separated by _option_ or
+ # a default of " or ". All other _items_ are
+ # separated by ", ".
+ # :rows:: The default mode. Each of the _items_ is
+ # placed on it's own line. The _option_
+ # parameter is ignored in this mode.
+ #
+ # Each member of the _items_ Array is passed through ERb and thus can contain
+ # their own expansions. Color escape expansions do not contribute to the
+ # final field width.
+ #
+ def list( items, mode = :rows, option = nil )
+ items = items.to_ary.map do |item|
+ ERB.new(item, nil, "%").result(binding)
+ end
+
+ case mode
+ when :inline
+ option = " or " if option.nil?
+
+ case items.size
+ when 0
+ ""
+ when 1
+ items.first
+ when 2
+ "#{items.first}#{option}#{items.last}"
+ 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.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
+ else
+ items.map { |i| "#{i}\n" }.join
+ 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 = statement.to_str
+ return unless statement.length > 0
+
+ template = ERB.new(statement, nil, "%")
+ statement = template.result(binding)
+
+ statement = wrap(statement) unless @wrap_at.nil?
+ statement = page_print(statement) unless @page_at.nil?
+
+ if statement[-1, 1] == " " or statement[-1, 1] == "\t"
+ @output.print(statement)
+ @output.flush
+ else
+ @output.puts(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 determing 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 determing the rows available
+ # for the @output or use a sensible default.
+ #
+ def page_at=( setting )
+ @page_at = setting == :auto ? output_rows : setting
+ 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
+
+ #
+ # 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( )
+ @gather = @question.gather
+ @answers = [ ]
+ original_question = @question
+
+ @question.gather = false
+
+ 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
+
+ @answers
+ 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
+ completions = @question.selection.abbrev
+ Readline.completion_proc = lambda { |string| completions[string] }
+
+ # work-around ugly readline() warnings
+ old_verbose = $VERBOSE
+ $VERBOSE = nil
+ answer = @question.change_case(
+ @question.remove_whitespace(
+ Readline.readline(question, true) ) )
+ $VERBOSE = old_verbose
+
+ answer
+ else
+ raise EOFError, "The input stream is exhausted." if @@track_eof and
+ @input.eof?
+
+ @question.change_case(@question.remove_whitespace(@input.gets))
+ 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 if stty = CHARACTER_MODE == "stty"
+
+ line = ""
+ backspace_limit = 0
+ begin
+
+ while character = (stty ? @input.getc : 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 or
+ (@question.limit and line.size == @question.limit)
+ 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#{ERASE_CHAR}")
+ else
+ # do nothing
+ end
+ else
+ @output.print(@question.echo)
+ end
+ @output.flush
+ end
+ end
+ ensure
+ restore_mode if stty
+ end
+ if @question.overwrite
+ @output.print("\r#{ERASE_LINE}")
+ @output.flush
+ else
+ say("\n")
+ end
+
+ @question.change_case(@question.remove_whitespace(line))
+ end
+ elsif @question.character == :getc
+ @question.change_case(@input.getc.chr)
+ else
+ response = get_character(@input).chr
+ if @question.overwrite
+ @output.print("\r#{ERASE_LINE}")
+ @output.flush
+ else
+ echo = if @question.echo == true
+ response
+ elsif @question.echo != false
+ @question.echo
+ else
+ ""
+ end
+ say("#{echo}\n")
+ 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( lines )
+ wrapped = [ ]
+ lines.each do |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[@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.gsub(/\e\[\d{1,2}m/, "").length
+ end
+end
diff --git a/tracks/vendor/gems/highline-1.4.0/lib/highline/color_scheme.rb b/tracks/vendor/gems/highline-1.4.0/lib/highline/color_scheme.rb
new file mode 100644
index 00000000..e7cbdf99
--- /dev/null
+++ b/tracks/vendor/gems/highline-1.4.0/lib/highline/color_scheme.rb
@@ -0,0 +1,120 @@
+#!/usr/local/bin/ruby -w
+
+# 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
+
+require 'highline'
+
+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. Key's 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
+
+ # Allow the scheme to be set like a Hash.
+ def []=( color_tag, constants )
+ @scheme[to_symbol(color_tag)] = constants.map { |c| to_constant(c) }
+ 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
diff --git a/tracks/vendor/gems/highline-1.4.0/lib/highline/import.rb b/tracks/vendor/gems/highline-1.4.0/lib/highline/import.rb
new file mode 100644
index 00000000..579a9734
--- /dev/null
+++ b/tracks/vendor/gems/highline-1.4.0/lib/highline/import.rb
@@ -0,0 +1,43 @@
+#!/usr/local/bin/ruby -w
+
+# 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
diff --git a/tracks/vendor/gems/highline-1.4.0/lib/highline/menu.rb b/tracks/vendor/gems/highline-1.4.0/lib/highline/menu.rb
new file mode 100644
index 00000000..ad992ac0
--- /dev/null
+++ b/tracks/vendor/gems/highline-1.4.0/lib/highline/menu.rb
@@ -0,0 +1,395 @@
+#!/usr/local/bin/ruby -w
+
+# 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?
+ update_responses # rebuild responses based on our settings
+ 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 alphetical 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?
+ 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 a literal String, also adjusts
+ # _index_suffix_ to a single space and _select_by_ to :none.
+ # 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 @nil_on_handled and not action.nil?
+ @highline = highline_context
+ if @shell
+ action.call(name, details)
+ else
+ action.call(name)
+ end
+ 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 = { :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}).",
+ :not_valid =>
+ "Your answer isn't valid (must match " +
+ "#{@validate.inspect})." }.merge(@responses)
+ end
+ end
+end
diff --git a/tracks/vendor/gems/highline-1.4.0/lib/highline/question.rb b/tracks/vendor/gems/highline-1.4.0/lib/highline/question.rb
new file mode 100644
index 00000000..a3d89cc6
--- /dev/null
+++ b/tracks/vendor/gems/highline-1.4.0/lib/highline/question.rb
@@ -0,0 +1,462 @@
+#!/usr/local/bin/ruby -w
+
+# 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 recongnized by
+ # Question.convert(). If given, a block is yeilded the new Question
+ # object to allow custom initializaion.
+ #
+ def initialize( question, answer_type )
+ # initialize instance data
+ @question = question
+ @answer_type = 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
+ @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
+ #
+ # 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 to 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 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}).",
+ :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().
+ # 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 [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 a english explination 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 included?()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 whitspace runs to a
+ # single space.
+ # :strip_and_collapse:: Calls strip(), then collapses all
+ # whitspace runs to a single space.
+ # :chomp_and_collapse:: Calls chomp(), then collapses all
+ # whitspace 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 @answer_type.is_a?(Array)
+ @answer_type
+ elsif [File, Pathname].include?(@answer_type)
+ 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
diff --git a/tracks/vendor/gems/highline-1.4.0/lib/highline/system_extensions.rb b/tracks/vendor/gems/highline-1.4.0/lib/highline/system_extensions.rb
new file mode 100644
index 00000000..e08b4772
--- /dev/null
+++ b/tracks/vendor/gems/highline-1.4.0/lib/highline/system_extensions.rb
@@ -0,0 +1,130 @@
+#!/usr/local/bin/ruby -w
+
+# 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.
+
+class HighLine
+ module SystemExtensions
+ module_function
+
+ #
+ # This section builds character reading and terminal size functions
+ # to suit the proper platform we're running on. Be warned: Here be
+ # dragons!
+ #
+ begin
+ # Cygwin will look like Windows, but we want to treat it like a Posix OS:
+ raise LoadError, "Cygwin is a Posix OS." if RUBY_PLATFORM =~ /\bcygwin\b/i
+
+ require "Win32API" # See if we're on Windows.
+
+ 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 )
+ Win32API.new("crtdll", "_getch", [ ], "L").Call
+ end
+
+ # A Windows savvy method to fetch the console columns, and rows.
+ def terminal_size
+ m_GetStdHandle = Win32API.new( 'kernel32',
+ 'GetStdHandle',
+ ['L'],
+ 'L' )
+ m_GetConsoleScreenBufferInfo = Win32API.new(
+ 'kernel32', 'GetConsoleScreenBufferInfo', ['L', 'P'], 'L'
+ )
+
+ format = 'SSSSSssssSS'
+ buf = ([0] * format.size).pack(format)
+ stdout_handle = m_GetStdHandle.call(0xFFFFFFF5)
+
+ m_GetConsoleScreenBufferInfo.call(stdout_handle, buf)
+ bufx, bufy, curx, cury, wattr,
+ left, top, right, bottom, maxx, maxy = buf.unpack(format)
+ return right - left + 1, bottom - top + 1
+ end
+ rescue LoadError # If we're not on Windows try...
+ begin
+ require "termios" # Unix, first choice.
+
+ CHARACTER_MODE = "termios" # For Debugging purposes only.
+
+ #
+ # Unix savvy getc(). (First choice.)
+ #
+ # *WARNING*: This method requires the "termios" library!
+ #
+ def get_character( input = STDIN )
+ old_settings = Termios.getattr(input)
+
+ new_settings = old_settings.dup
+ new_settings.c_lflag &= ~(Termios::ECHO | Termios::ICANON)
+ new_settings.c_cc[Termios::VMIN] = 1
+
+ begin
+ Termios.setattr(input, Termios::TCSANOW, new_settings)
+ input.getc
+ ensure
+ Termios.setattr(input, Termios::TCSANOW, old_settings)
+ end
+ end
+ rescue LoadError # If our first choice fails, default.
+ CHARACTER_MODE = "stty" # For Debugging purposes only.
+
+ #
+ # Unix savvy getc(). (Second choice.)
+ #
+ # *WARNING*: This method requires the external "stty" program!
+ #
+ def get_character( input = STDIN )
+ raw_no_echo_mode
+
+ begin
+ input.getc
+ ensure
+ restore_mode
+ end
+ end
+
+ #
+ # Switched the input mode to raw and disables echo.
+ #
+ # *WARNING*: This method requires the external "stty" program!
+ #
+ def raw_no_echo_mode
+ @state = `stty -g`
+ system "stty raw -echo cbreak isig"
+ end
+
+ #
+ # Restores a previously saved input mode.
+ #
+ # *WARNING*: This method requires the external "stty" program!
+ #
+ def restore_mode
+ system "stty #{@state}"
+ end
+ end
+
+ # A Unix savvy method to fetch the console columns, and rows.
+ 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
+end
diff --git a/tracks/vendor/gems/highline-1.4.0/setup.rb b/tracks/vendor/gems/highline-1.4.0/setup.rb
new file mode 100644
index 00000000..0807023d
--- /dev/null
+++ b/tracks/vendor/gems/highline-1.4.0/setup.rb
@@ -0,0 +1,1360 @@
+#
+# 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
diff --git a/tracks/vendor/gems/highline-1.4.0/test/tc_color_scheme.rb b/tracks/vendor/gems/highline-1.4.0/test/tc_color_scheme.rb
new file mode 100644
index 00000000..cb5cbd0e
--- /dev/null
+++ b/tracks/vendor/gems/highline-1.4.0/test/tc_color_scheme.rb
@@ -0,0 +1,56 @@
+#!/usr/local/bin/ruby -w
+
+# 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
+
+ # 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
diff --git a/tracks/vendor/gems/highline-1.4.0/test/tc_highline.rb b/tracks/vendor/gems/highline-1.4.0/test/tc_highline.rb
new file mode 100644
index 00000000..0ab35f6a
--- /dev/null
+++ b/tracks/vendor/gems/highline-1.4.0/test/tc_highline.rb
@@ -0,0 +1,815 @@
+#!/usr/local/bin/ruby -w
+
+# 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_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_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_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)
+
+ # 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_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_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
+
+ 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("#!/usr/local/bin/ruby -w\n", file.gets)
+ assert_equal("\n", file.gets)
+ 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_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 )
+ end
+
+ def test_mode
+ assert(%w[Win32API termios stty].include?(HighLine::CHARACTER_MODE))
+ 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)
+ 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 << "6/16/76\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(76, 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
diff --git a/tracks/vendor/gems/highline-1.4.0/test/tc_import.rb b/tracks/vendor/gems/highline-1.4.0/test/tc_import.rb
new file mode 100644
index 00000000..005d5a92
--- /dev/null
+++ b/tracks/vendor/gems/highline-1.4.0/test/tc_import.rb
@@ -0,0 +1,54 @@
+#!/usr/local/bin/ruby -w
+
+# 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
diff --git a/tracks/vendor/gems/highline-1.4.0/test/tc_menu.rb b/tracks/vendor/gems/highline-1.4.0/test/tc_menu.rb
new file mode 100644
index 00000000..fee18714
--- /dev/null
+++ b/tracks/vendor/gems/highline-1.4.0/test/tc_menu.rb
@@ -0,0 +1,429 @@
+#!/usr/local/bin/ruby -w
+
+# 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_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 << "q\n3\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
diff --git a/tracks/vendor/gems/highline-1.4.0/test/ts_all.rb b/tracks/vendor/gems/highline-1.4.0/test/ts_all.rb
new file mode 100644
index 00000000..735dccee
--- /dev/null
+++ b/tracks/vendor/gems/highline-1.4.0/test/ts_all.rb
@@ -0,0 +1,15 @@
+#!/usr/local/bin/ruby -w
+
+# 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_color_scheme"