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"