Permalink
Browse files

Merge pull request #37 from iconoclast/master

Feature: Typing verification using multiple matching entries
  • Loading branch information...
2 parents 4ee064f + 75c28ca commit a79df02b25e9dbee14e7a23a9f4bf1c04bea5eb9 @JEG2 committed May 14, 2012
Showing with 165 additions and 40 deletions.
  1. +21 −0 examples/repeat_entry.rb
  2. +55 −31 lib/highline.rb
  3. +2 −0 lib/highline/menu.rb
  4. +13 −4 lib/highline/question.rb
  5. +74 −5 test/tc_highline.rb
@@ -0,0 +1,21 @@
+#!/usr/local/bin/ruby -w
+
+require "rubygems"
+require "highline/import"
+
+tounge_twister = ask("... try saying that three times fast") do |q|
+ q.gather = 3
+ q.verify_match = true
+ q.responses[:mismatch] = "Nope, those don't match. Try again."
+end
+
+puts "Ok, you did it."
+
+pass = ask("Enter your password: ") do |q|
+ q.echo = '*'
+ q.verify_match = true
+ q.gather = {"Enter a password" => '',
+ "Please type it again for verification" => ''}
+end
+
+puts "Your password is now #{pass}!"
View
@@ -691,44 +691,68 @@ def explain_error( error )
# Raises EOFError if input is exhausted.
#
def gather( )
- @gather = @question.gather
- @answers = [ ]
original_question = @question
-
+ original_question_string = @question.question
+ original_gather = @question.gather
+
+ verify_match = @question.verify_match
@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)
+ begin # when verify_match is set this loop will repeat until unique_answers == 1
+ @answers = [ ]
+ @gather = original_gather
+ original_question.question = original_question_string
+
+ case @gather
+ when Integer
+ @answers << ask(@question)
+ @gather -= 1
+
+ original_question.question = ""
+ until @gather.zero?
+ @question = original_question
+ @answers << ask(@question)
+ @gather -= 1
+ end
+ when ::String, Regexp
+ @answers << ask(@question)
- original_question.question = ""
- until (@gather.is_a?(::String) and @answers.last.to_s == @gather) or
+ 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)
+ @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.pop
- when Hash
- @answers = { }
- @gather.keys.sort.each do |key|
- @question = original_question
- @key = key
- @answers[key] = ask(@question)
+
+ if verify_match && (unique_answers(@answers).size > 1)
+ @question = original_question
+ explain_error(:mismatch)
+ else
+ verify_match = false
end
- end
-
- @answers
+
+ end while verify_match
+
+ original_question.verify_match ? @answer : @answers
+ end
+
+ #
+ # A helper method used by HighLine::Question.verify_match
+ # for finding whether a list of answers match or differ
+ # from each other.
+ #
+ def unique_answers(list = @answers)
+ (list.respond_to?(:values) ? list.values : list).uniq
end
#
@@ -389,6 +389,8 @@ def update_responses( )
:not_in_range =>
"Your answer isn't within the expected range " +
"(#{expected_range}).",
+ :mismatch =>
+ "Your entries didn't match.",
:not_valid =>
"Your answer isn't valid (must match " +
"#{@validate.inspect})."
@@ -33,9 +33,9 @@ class NoAutoCompleteMatch < StandardError
#
def initialize( question, answer_type )
# initialize instance data
- @question = question
+ @question = question.dup
@answer_type = answer_type
-
+
@character = nil
@limit = nil
@echo = true
@@ -49,19 +49,20 @@ def initialize( question, answer_type )
@in = nil
@confirm = nil
@gather = false
+ @verify_match = false
@first_answer = nil
@directory = Pathname.new(File.expand_path(File.dirname($0)))
@glob = "*"
@responses = Hash.new
@overwrite = false
-
+
# allow block to override settings
yield self if block_given?
# finalize responses based on settings
build_responses
end
-
+
# The ERb template of the question to be asked.
attr_accessor :question
# The type that will be used to convert this answer.
@@ -152,6 +153,12 @@ def initialize( question, answer_type )
#
attr_accessor :gather
#
+ # When set to +true+ multiple entries will be collected according to
+ # the setting for _gather_, except they will be required to match
+ # each other. Multiple identical entries will return a single answer.
+ #
+ attr_accessor :verify_match
+ #
# When set to a non *nil* value, this will be tried as an answer to the
# question. If this answer passes validations, it will become the result
# without the user ever being prompted. Otherwise this value is discarded,
@@ -236,6 +243,8 @@ def build_responses( )
:not_in_range =>
"Your answer isn't within the expected range " +
"(#{expected_range}).",
+ :mismatch =>
+ "Your entries didn't match.",
:not_valid =>
"Your answer isn't valid (must match " +
"#{@validate.inspect})." }.merge(@responses)
View
@@ -310,7 +310,19 @@ def test_defaults
assert_equal( "Are you sexually active? |No Comment| ",
@output.string )
end
-
+
+ def test_string_preservation
+ @input << "Maybe\nYes\n"
+ @input.rewind
+
+ my_string = "Is that your final answer? "
+
+ @terminal.ask(my_string) { |q| q.default = "Possibly" }
+ @terminal.ask(my_string) { |q| q.default = "Maybe" }
+
+ assert_equal("Is that your final answer? ", my_string)
+ end
+
def test_empty
@input << "\n"
@input.rewind
@@ -398,15 +410,72 @@ def test_gather
answers )
assert_equal("Age: Father's Age: Wife's Age: ", @output.string)
end
-
- def test_lists
+
+ def test_typing_verification
+ @input << "all work and no play makes jack a dull boy\n" * 3
+ @input.rewind
+
+ answer = @terminal.ask("How's work? ") do |q|
+ q.gather = 3
+ q.verify_match = true
+ end
+ assert_equal("all work and no play makes jack a dull boy", answer)
+
+ @input.truncate(@input.rewind)
+ @input << "all play and no work makes jack a mere toy\n"
+ @input << "all work and no play makes jack a dull boy\n" * 5
+ @input.rewind
+ @output.truncate(@output.rewind)
+
+ answer = @terminal.ask("How are things going? ") do |q|
+ q.gather = 3
+ q.verify_match = true
+ q.responses[:mismatch] = 'Typing mismatch!'
+ q.responses[:ask_on_error] = ''
+ end
+ assert_equal("all work and no play makes jack a dull boy", answer)
+
+ # now try using a hash for gather
+
+ @input.truncate(@input.rewind)
+ @input << "Password\nPassword\n"
+ @input.rewind
+ @output.truncate(@output.rewind)
+
+ answer = @terminal.ask("<%= @key %>: ") do |q|
+ q.verify_match = true
+ q.gather = {"Enter a password" => '', "Please type it again" => ''}
+ end
+ assert_equal("Password", answer)
+
+ @input.truncate(@input.rewind)
+ @input << "Password\nMistake\nPassword\nPassword\n"
+ @input.rewind
+ @output.truncate(@output.rewind)
+
+ answer = @terminal.ask("<%= @key %>: ") do |q|
+ q.verify_match = true
+ q.responses[:mismatch] = 'Typing mismatch!'
+ q.responses[:ask_on_error] = ''
+ q.gather = {"Enter a password" => '', "Please type it again" => ''}
+ end
+
+ assert_equal("Password", answer)
+ assert_equal( "Enter a password: " +
+ "Please type it again: " +
+ "Typing mismatch!\n" +
+ "Enter a password: " +
+ "Please type it again: ", @output.string )
+ end
+
+ def test_lists
digits = %w{Zero One Two Three Four Five Six Seven Eight Nine}
erb_digits = digits.dup
erb_digits[erb_digits.index("Five")] = "<%= color('Five', :blue) %%>"
-
+
@terminal.say("<%= list(#{digits.inspect}) %>")
assert_equal(digits.map { |d| "#{d}\n" }.join, @output.string)
-
+
@output.truncate(@output.rewind)
@terminal.say("<%= list(#{digits.inspect}, :inline) %>")

0 comments on commit a79df02

Please sign in to comment.