Permalink
Browse files

initial commit

  • Loading branch information...
0 parents commit dc611c28b528f897e8968f1d3d751707e852be4a Alex Suraci committed Oct 25, 2011
Showing with 832 additions and 0 deletions.
  1. +2 −0 .gitignore
  2. +2 −0 Gemfile
  3. +30 −0 LICENSE
  4. +102 −0 README.md
  5. +19 −0 Rakefile
  6. +22 −0 interact.gemspec
  7. +436 −0 lib/interact.rb
  8. +5 −0 lib/version.rb
  9. +13 −0 spec/Rakefile
  10. +201 −0 spec/ask_spec.rb
2 .gitignore
@@ -0,0 +1,2 @@
+*.gem
+Gemfile.lock
2 Gemfile
@@ -0,0 +1,2 @@
+source "http://rubygems.org/"
+gemspec
30 LICENSE
@@ -0,0 +1,30 @@
+Copyright (c)2011, Alex Suraci
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ * Neither the name of Alex Suraci nor the names of other
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
102 README.md
@@ -0,0 +1,102 @@
+# Interact
+
+Another interaction library for Ruby, with an interesting twist: the user can
+go back in time to re-answer questions.
+
+*Copyright 2011, Alex Suraci. Licensed under the MIT license, please see the
+LICENSE file. All rights reserved.*
+
+
+## Basic Usage
+
+```ruby
+require "rubygems"
+require "interact"
+
+class MyInteractiveClass
+ include Interactive
+
+ def run
+ first = ask "Some question?"
+ second = ask "Boolean default?", :default => true
+ third = ask "Stringy default?", :default => "foo"
+
+ fourth = ask "Multiple choice?", :choices => ["foo", "bar", "fizz"]
+
+ some_mutation = []
+
+ fifth = ask "Multiple choice, indexed list?",
+ :choices => ["foo", "bar", "fizz"],
+ :indexed => true
+
+ some_mutation << fifth
+
+ finalize
+
+ sixth = ask "Password", :echo => "*", :forget => true
+
+ p [first, second, third, fourth, fifth, sixth]
+ end
+end
+
+MyInteractiveClass.new.run
+```
+
+After running this, the user will be prompted with each question one-by-one.
+Interact supports basic editing features such as going to the start/end,
+editing in the middle of the text, backspace, forward delete, and
+backwards-kill-word.
+
+In addition, the user can hit the up arrow to go "back in time" and re-answer
+questins.
+
+Make sure you call `finalize` after doing any mutation performed based on user
+input; this will prevent them from rewinding to before this took place. Or you
+can just disable rewinding if it's too complicated (see below).
+
+Anyway, here's an example session:
+
+```
+Some question?: hello<enter>
+Boolean default? [Yn]: <up>
+Some question? ["hello"]: trying again<enter>
+Boolean default? [Yn]: n<enter>
+Stringy default? ["foo"]: <up>
+Boolean default? [yN]: y<enter>
+Stringy default? ["foo"]: <enter>
+Multiple choice? ("foo", "bar", "fizz"): f<enter>
+Please disambiguate: foo or fizz?
+Multiple choice? ("foo", "bar", "fizz"): fo<enter>
+1: foo
+2: bar
+3: fizz
+Multiple choice, indexed list?: 2<enter>
+Password: ******<enter>
+["trying again", true, "foo", "foo", "bar", "secret"]
+```
+
+Note that the user's previous answers become the new defaults for the question
+if they rewind.
+
+## Disabling Rewinding
+
+Interact provides a nifty user-friendly "rewinding" feature, which allows the user to go back in time and re-answer a question. If you don't want this feature, simply set `@@allow_rewind` to `false` in your class.
+
+```ruby
+class NoRewind
+ include Interactive
+ @@allow_rewind = false
+
+ def run
+ res = ask "Is there no return?", :default => true
+
+ if res == allow_rewind
+ puts "You're right!"
+ else
+ puts "Nope! It's disabled."
+ end
+ end
+end
+
+NoRewind.new.run
+```
19 Rakefile
@@ -0,0 +1,19 @@
+require 'rake'
+
+task :default => "spec"
+
+desc "Run specs"
+task "spec" => ["bundler:install", "test:spec"]
+
+namespace "bundler" do
+ desc "Install gems"
+ task "install" do
+ sh("bundle install")
+ end
+end
+
+namespace "test" do
+ task "spec" do |t|
+ sh("cd spec && rake spec")
+ end
+end
22 interact.gemspec
@@ -0,0 +1,22 @@
+$:.unshift File.expand_path("../lib", __FILE__)
+
+require 'version'
+
+spec = Gem::Specification.new do |s|
+ s.name = "interact"
+ s.version = Interact::VERSION
+ s.author = "Alex Suraci"
+ s.email = "i.am@toogeneric.com"
+ s.homepage = "http://github.com/vito/interact"
+ s.summary = "A simple API for command-line interaction."
+ s.description = "A simple API for command-line interaction. Provides a novel 'rewinding' feature, allowing users to go back in time and re-enter a botched answer. Supports multiple-choice, password prompting, overriding input events, defaults, etc."
+
+ s.platform = Gem::Platform::RUBY
+ s.extra_rdoc_files = ["README.md", "LICENSE"]
+
+ s.add_development_dependency "rake"
+ s.add_development_dependency "rspec", "~> 2.0"
+
+ s.require_path = 'lib'
+ s.files = %w(LICENSE README.md Rakefile) + Dir.glob("{lib}/**/*")
+end
436 lib/interact.rb
@@ -0,0 +1,436 @@
+# Copyright (c) 2011 Alex Suraci
+
+# Helpers for the main API provided by mixing in +Interactive+.
+#
+# Internal use only. Not a stable API.
+module Interact
+ WINDOWS = !!(RUBY_PLATFORM =~ /mingw|mswin32|cygwin/)
+
+ if defined? callcc
+ HAS_CALLCC = true
+ else
+ begin
+ require "continuation"
+ HAS_CALLCC = true
+ rescue LoadError
+ HAS_CALLCC = false
+ end
+ end
+
+ ESCAPES = {
+ "[A" => :up, "H" => :up,
+ "[B" => :down, "P" => :down,
+ "[C" => :right, "M" => :right,
+ "[D" => :left, "K" => :left,
+ "[3~" => :delete, "S" => :delete,
+ "[H" => :home, "G" => :home,
+ "[F" => :end, "O" => :end
+ }
+
+ EVENTS = {
+ "\b" => :backspace,
+ "\t" => :tab,
+ "\x01" => :home,
+ "\x03" => :interrupt,
+ "\x04" => :eof,
+ "\x05" => :end,
+ "\x17" => :kill_word,
+ "\x7f" => :backspace
+ }
+
+ # Used internally to clean up input state before jumping to another prompt.
+ class JumpToPrompt < Exception
+ def initialize(prompt)
+ @prompt = prompt
+ end
+
+ # Print an empty line and jump to the prompt. This is typically called
+ # after the user has pressed the up arrow.
+ def jump
+ print "\n"
+ @prompt[0].call(@prompt)
+ end
+ end
+
+ def self.handler(which, ans, pos, echo = nil, prompts = [])
+ if block_given?
+ res = yield which, ans, pos, echo
+ return res unless res.nil?
+ end
+
+ case which
+ when :up
+ if back = prompts.pop
+ raise Interact::JumpToPrompt, back
+ end
+
+ when :down
+ # nothing
+
+ when :tab
+ # nothing
+
+ when :right
+ unless pos == ans.size
+ print censor(ans[pos .. pos], echo)
+ return pos + 1
+ end
+
+ when :left
+ unless pos == 0
+ print "\b"
+ return pos - 1
+ end
+
+ when :delete
+ unless pos == ans.size
+ ans.slice!(pos, 1)
+ if Interact::WINDOWS
+ rest = ans[pos .. -1]
+ print(censor(rest, echo) + " \b" + ("\b" * rest.size))
+ else
+ print("\e[P")
+ end
+ end
+
+ when :home
+ print("\b" * pos)
+ return 0
+
+ when :end
+ print(censor(ans[pos .. -1], echo))
+ return ans.size
+
+ when :backspace
+ if pos > 0
+ ans.slice!(pos - 1, 1)
+
+ if Interact::WINDOWS
+ rest = ans[pos - 1 .. -1]
+ print("\b" + censor(rest, echo) + " \b" + ("\b" * rest.size))
+ else
+ print("\b\e[P")
+ end
+
+ return pos - 1
+ end
+
+ when :interrupt
+ raise Interrupt.new
+
+ when :eof
+ return false if ans.empty?
+
+ when :kill_word
+ if pos > 0
+ start = /[^\s]*\s*$/ =~ ans[0 .. pos]
+ length = pos - start
+ ans.slice!(start, length)
+ print("\b" * length + " " * length + "\b" * length)
+ return start
+ end
+
+ when Array
+ case which[0]
+ when :key
+ c = which[1]
+ rest = ans[pos .. -1]
+
+ ans.insert(pos, c)
+
+ print(censor(c + rest, echo) + ("\b" * rest.size))
+
+ return pos + 1
+ end
+ end
+
+ pos
+ end
+
+ def self.censor(str, with)
+ return str unless with
+ with * str.size
+ end
+
+ def self.ask_default(input, question, default = nil,
+ echo = nil, prompts = [], &callback)
+ while true
+ prompt(question, default)
+
+ ans = ""
+ pos = 0
+ escaped = false
+ escape_seq = ""
+
+ with_char_io(input) do
+ until pos == false or (c = get_character(input)) =~ /[\r\n]/
+ if c == "\e" || c == "\xE0"
+ escaped = true
+ elsif escaped
+ escape_seq << c
+
+ if cmd = Interact::ESCAPES[escape_seq]
+ pos = handler(cmd, ans, pos, echo, prompts, &callback)
+ escaped, escape_seq = false, ""
+ elsif Interact::ESCAPES.select { |k, v|
+ k.start_with? escape_seq
+ }.empty?
+ escaped, escape_seq = false, ""
+ end
+ elsif Interact::EVENTS.key? c
+ pos = handler(
+ Interact::EVENTS[c], ans, pos, echo, prompts, &callback
+ )
+ elsif c < " "
+ # ignore
+ else
+ pos = handler([:key, c], ans, pos, echo, prompts, &callback)
+ end
+ end
+ end
+
+ print "\n"
+
+ if ans.empty?
+ return default unless default.nil?
+ else
+ return match_type(ans, default)
+ end
+ end
+ end
+
+ def self.ask_choices(input, question, default, choices, indexed = false,
+ echo = nil, prompts = [], &callback)
+ choices = choices.to_a
+
+ msg = question.dup
+
+ if indexed
+ choices.each.with_index do |o, i|
+ puts "#{i + 1}: #{o}"
+ end
+ else
+ msg << " (#{choices.collect(&:inspect).join ", "})"
+ end
+
+ while true
+ ans = ask_default(input, msg, default, echo, prompts, &callback)
+
+ matches = choices.select { |x| x.start_with? ans }
+
+ if matches.size == 1
+ return matches.first
+ elsif indexed and ans =~ /^\s*\d+\s*$/ and res = choices[ans.to_i - 1]
+ return res
+ elsif matches.size > 1
+ puts "Please disambiguate: #{matches.join " or "}?"
+ else
+ puts "Unknown answer, please try again!"
+ end
+ end
+ end
+
+ def self.prompt(question, default = nil)
+ msg = question.dup
+
+ case default
+ when true
+ msg << " [Yn]"
+ when false
+ msg << " [yN]"
+ else
+ msg << " [#{default.inspect}]" if default
+ end
+
+ print "#{msg}: "
+ end
+
+ def self.match_type(str, x)
+ case x
+ when Integer
+ str.to_i
+ when true, false
+ str.upcase.start_with? "Y"
+ else
+ str
+ end
+ end
+
+ # Definitions for reading character-by-character with no echoing.
+ begin
+ require "Win32API"
+
+ def self.with_char_io(input)
+ yield
+ rescue Interact::JumpToPrompt => e
+ e.jump
+ end
+
+ def self.get_character(input)
+ if input == STDIN
+ begin
+ Win32API.new("msvcrt", "_getch", [], "L").call.chr
+ rescue
+ Win32API.new("crtdll", "_getch", [], "L").call.chr
+ end
+ else
+ input.getc.chr
+ end
+ end
+ rescue LoadError
+ begin
+ require "termios"
+
+ def self.with_char_io(input)
+ return yield unless input.tty?
+
+ before = Termios.getattr(input)
+
+ new = before.dup
+ new.c_lflag &= ~(Termios::ECHO | Termios::ICANON)
+ new.c_cc[Termios::VMIN] = 1
+
+ begin
+ Termios.setattr(input, Termios::TCSANOW, new)
+ yield
+ rescue Interact::JumpToPrompt => e
+ Termios.setattr(input, Termios::TCSANOW, before)
+ e.jump
+ ensure
+ Termios.setattr(input, Termios::TCSANOW, before)
+ end
+ end
+
+ def self.get_character(input)
+ input.getc.chr
+ end
+ rescue LoadError
+ def self.with_char_io(input)
+ return yield unless input.tty?
+
+ begin
+ before = `stty -g`
+ system("stty raw -echo -icanon isig")
+ yield
+ rescue Interact::JumpToPrompt => e
+ system("stty #{before}")
+ e.jump
+ ensure
+ system("stty #{before}")
+ end
+ end
+
+ def self.get_character(input)
+ input.getc.chr
+ end
+ end
+ end
+end
+
+module Interactive
+ # Allow classes to disable the rewind feature via the +allow_rewind+ class
+ # variable.
+ def self.included klass
+ klass.send :class_variable_set, :@@allow_rewind, true
+
+ klass.class_eval do
+ # Accessor for the +allow_rewind+ class variable, which determines
+ # whether to enable the rewinding feature.
+ def allow_rewind
+ self.class.send :class_variable_get, :@@allow_rewind
+ end
+ end
+ end
+
+ # General-purpose interaction.
+ #
+ # [question] The prompt, without ": " at the end.
+ #
+ # [options] An optional hash containing the following options.
+ #
+ # input::
+ # The input source (defaults to +STDIN+).
+ #
+ # default::
+ # The default value, also used to attempt type conversion of the answer
+ # (e.g. numeric/boolean).
+ #
+ # choices::
+ # An array (or +Enumerable+) of strings to choose from.
+ #
+ # indexed::
+ # Whether to allow choosing from +:choices+ by their index, best for when
+ # there are many choices.
+ #
+ # echo::
+ # A string to echo when showing the input; used for things like censoring
+ # password input.
+ #
+ # forget::
+ # Set to false to prevent rewinding from remembering the answer.
+ #
+ # callback::
+ # A block used to override certain actions.
+ #
+ # The block should take 4 arguments:
+ #
+ # - the event, e.g. +:up+ or +[:key, X]+ where +X+ is a string containing
+ # a single character
+ # - the current answer to the question; you'll probably mutate this
+ # - the current offset from the start of the answer string, e.g. when
+ # typing in the middle of the input, this will be where you insert
+ # characters
+ # - the +:echo+ option from above, may be +nil+
+ #
+ # The block should return the updated +position+, or +nil+ if it didn't
+ # handle the event
+ def ask(question, options = {})
+ rewind = Interact::HAS_CALLCC && allow_rewind
+
+ if rewind
+ prompt, answer = callcc { |cc| [cc, nil] }
+ else
+ prompt, answer = nil, nil
+ end
+
+ if answer.nil?
+ default = options[:default]
+ else
+ default = answer
+ end
+
+ choices = options[:choices]
+ indexed = options[:indexed]
+ callback = options[:callback]
+ input = options[:input] || STDIN
+ echo = options[:echo]
+
+ prompts = (@__prompts ||= [])
+
+ if choices
+ ans = Interact.ask_choices(
+ input, question, default, choices, indexed, echo, prompts, &callback
+ )
+ else
+ ans = Interact.ask_default(
+ input, question, default, echo, prompts, &callback
+ )
+ end
+
+ if rewind
+ prompts << [prompt, options[:forget] ? nil : ans]
+ end
+
+ ans
+ end
+
+ # Clear prompts.
+ #
+ # Questions asked after this are rewindable, but questions asked beforehand
+ # are no longer reachable.
+ #
+ # Use this after you've performed some mutation based on the user's input.
+ def finalize
+ @__prompts = []
+ end
+end
5 lib/version.rb
@@ -0,0 +1,5 @@
+# Copyright (c) 2011 Alex Suraci
+
+module Interact
+ VERSION = 0.1
+end
13 spec/Rakefile
@@ -0,0 +1,13 @@
+require 'rubygems'
+require 'bundler/setup'
+Bundler.require(:default, :development)
+
+require 'rake'
+require 'rspec'
+require 'rspec/core/rake_task'
+
+RSpec::Core::RakeTask.new do |t|
+ t.pattern = "**/*_spec.rb"
+ t.rspec_opts = ["--format", "documentation", "--colour"]
+end
+
201 spec/ask_spec.rb
@@ -0,0 +1,201 @@
+require "interact"
+require "stringio"
+
+
+def ask_faked(input, question, opts = {})
+ before = $stdout
+
+ $stdout = StringIO.new("", "w")
+
+ opts[:input] = StringIO.new(input, "r")
+
+ yield AskResult.new(
+ $interaction.ask(question, opts),
+ $stdout.string
+ )
+ensure
+ $stdout = before
+end
+
+class AskResult
+ attr_reader :answer, :output
+
+ def initialize(answer, output)
+ @answer = answer
+ @output = output
+ end
+end
+
+class Interaction
+ include Interactive
+end
+
+describe "ask" do
+ before(:each) do
+ $interaction = Interaction.new
+ end
+
+ describe "questions" do
+ it "returns the answer string" do
+ ask_faked("foo\n", "Foo?") do |x|
+ x.answer.should == "foo"
+ x.output.should == "Foo?: foo\n"
+ end
+ end
+
+ it "skips blank lines until they enter something" do
+ ask_faked("\n\nfoo\n", "Foo?") do |x|
+ x.answer.should == "foo"
+ x.output.should == "Foo?: \nFoo?: \nFoo?: foo\n"
+ end
+ end
+
+ it "accepts blank lines if a default is provided; returns default" do
+ ask_faked("\n\nfoo\n", "Foo?", :default => "bar") do |x|
+ x.answer.should == "bar"
+ x.output.should == "Foo? [\"bar\"]: \n"
+ end
+ end
+
+ it "allows censoring output" do
+ ask_faked("fizzbuzz\n", "Foo?", :echo => "*") do |x|
+ x.answer.should == "fizzbuzz"
+ x.output.should == "Foo?: ********\n"
+ end
+ end
+
+ it "allows overriding input events" do
+ callback = proc do |x, ans, pos, echo|
+ if x.is_a?(Array) and x[0] == :key
+ ans << x[1].upcase
+ print x[1].upcase
+ pos + 1
+ end
+ end
+
+ ask_faked("fizzbuzz\n", "Foo?", :callback => callback) do |x|
+ x.answer.should == "FIZZBUZZ"
+ x.output.should == "Foo?: FIZZBUZZ\n"
+ end
+ end
+
+ it "guesses the return type based on the default value" do
+ ask_faked("y\n", "Foo?", :default => true) do |x|
+ x.answer.should == true
+ x.output.should == "Foo? [Yn]: y\n"
+ end
+
+ ask_faked("\n", "Foo?", :default => true) do |x|
+ x.answer.should == true
+ x.output.should == "Foo? [Yn]: \n"
+ end
+
+ ask_faked("n\n", "Foo?", :default => true) do |x|
+ x.answer.should == false
+ x.output.should == "Foo? [Yn]: n\n"
+ end
+
+ ask_faked("\n", "Foo?", :default => false) do |x|
+ x.answer.should == false
+ x.output.should == "Foo? [yN]: \n"
+ end
+
+ ask_faked("y\n", "Foo?", :default => false) do |x|
+ x.answer.should == true
+ x.output.should == "Foo? [yN]: y\n"
+ end
+
+ ask_faked("n\n", "Foo?", :default => false) do |x|
+ x.answer.should == false
+ x.output.should == "Foo? [yN]: n\n"
+ end
+
+ ask_faked("10\n", "Foo?", :default => 5) do |x|
+ x.answer.should == 10
+ x.output.should == "Foo? [5]: 10\n"
+ end
+
+ ask_faked("\n", "Foo?", :default => 5) do |x|
+ x.answer.should == 5
+ x.output.should == "Foo? [5]: \n"
+ end
+ end
+ end
+
+ describe "multiple choice" do
+ it "can provide a set of choices to the user" do
+ ask_faked("A\n", "Favorite letter?", :choices => "A".."C") do |x|
+ x.answer.should == "A"
+ x.output.should == "Favorite letter? (\"A\", \"B\", \"C\"): A\n"
+ end
+ end
+
+ it "repeats the question if blank line received with no default" do
+ ask_faked("\nA\n", "Favorite letter?", :choices => "A".."C") do |x|
+ x.answer.should == "A"
+ x.output.should == "Favorite letter? (\"A\", \"B\", \"C\"): \nFavorite letter? (\"A\", \"B\", \"C\"): A\n"
+ end
+ end
+
+ it "can provide a set of choices to the user, with a default" do
+ ask_faked("A\n", "Favorite letter?",
+ :choices => "A".."C", :default => "C") do |x|
+ x.answer.should == "A"
+ x.output.should == "Favorite letter? (\"A\", \"B\", \"C\") [\"C\"]: A\n"
+ end
+ end
+
+ it "accepts blank lines if a default is provided; returns default" do
+ ask_faked("\nA\n", "Favorite letter?",
+ :choices => "A".."C", :default => "C") do |x|
+ x.answer.should == "C"
+ x.output.should == "Favorite letter? (\"A\", \"B\", \"C\") [\"C\"]: \n"
+ end
+ end
+
+ it "performs basic autocompletion" do
+ ask_faked("c\n", "Foo?", :choices => %w{aa ba ca}) do |x|
+ x.answer.should == "ca"
+ x.output.should == "Foo? (\"aa\", \"ba\", \"ca\"): c\n"
+ end
+
+ ask_faked("cb\n", "Foo?", :choices => %w{aa ba caa cba}) do |x|
+ x.answer.should == "cba"
+ x.output.should == "Foo? (\"aa\", \"ba\", \"caa\", \"cba\"): cb\n"
+ end
+ end
+
+ it "complains if there is any ambiguity and repeats the question" do
+ ask_faked("c\nca\n", "Foo?", :choices => %w{aa ba caa cba}) do |x|
+ x.answer.should == "caa"
+ x.output.should == "Foo? (\"aa\", \"ba\", \"caa\", \"cba\"): c\nPlease disambiguate: caa or cba?\nFoo? (\"aa\", \"ba\", \"caa\", \"cba\"): ca\n"
+ end
+ end
+
+ it "can provide a listing view, and allow selecting by number" do
+ ask_faked("B\n", "Foo?",
+ :choices => "A".."C", :indexed => true) do |x|
+ x.answer.should == "B"
+ x.output.should == "1: A\n2: B\n3: C\nFoo?: B\n"
+ end
+
+ ask_faked("2\n", "Foo?",
+ :choices => "A".."C", :indexed => true) do |x|
+ x.answer.should == "B"
+ x.output.should == "1: A\n2: B\n3: C\nFoo?: 2\n"
+ end
+
+ ask_faked("\n", "Foo?",
+ :choices => "A".."C", :indexed => true, :default => "C") do |x|
+ x.answer.should == "C"
+ x.output.should == "1: A\n2: B\n3: C\nFoo? [\"C\"]: \n"
+ end
+
+ ask_faked("\nC\n", "Foo?",
+ :choices => "A".."C", :indexed => true) do |x|
+ x.answer.should == "C"
+ x.output.should == "1: A\n2: B\n3: C\nFoo?: \nFoo?: C\n"
+ end
+ end
+ end
+end

0 comments on commit dc611c2

Please sign in to comment.